SpringBoot相关知识

1
SpringBoot相关知识

Spring常用注解

springboot的启动类的@SpringBootApplication, 控制器的@Controller, @RestController, 还有与配置相关的一系列注解.

   查找XML文档与配置<bean>元素这两个工作同样可以使用注解来完成. 使用@Component, @Service, 或者@Configuration注解来修饰一个java类, 这些java类会被Spring自动检测并注册到容器中. 在类里面使用@Bean注解修饰的方法, 会被作为一个bean存放到Spring容器中.

@Autowired, @Primary, @Resource

@Autowired默认按类型注入, 找不到就按名称注入.

​ 还可以使用构造器注入

1
2
3
4
5
InjectBean myBean;
@Autowired
public InjectApp(InjectBean myBean2){
this.myBean = myBean2;
}

@Resource默认按名称注入, 找不到就按类型注入

​ 不能修饰构造器

@Primary注解 注入

​ 如果容器中配置两个相同类型的bean, 我们在想要用的Bean上添加@Primary注解, 我们再使用@Autowired自动注入, 就不会报错了, 就会自动注入带@Primary的bean 了. 如果有多个bean都带了@Primary, 就会报异常.

  • 例子

    • User.java

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      package cn.lacknb.beans;


      public class User {

      private String username;

      public User(String username) {
      this.username = username;
      }

      public String getUsername() {
      return username;
      }

      public void setUsername(String username) {
      this.username = username;
      }
      }
    • BeanConfig.java

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      package cn.lacknb.beans;

      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Primary;
      import org.springframework.stereotype.Component;

      @Component
      public class BeanConfig {

      @Bean
      public User getUser(){
      return new User("ccc");
      }

      @Bean
      @Primary
      public User getUser2(){
      return new User("bbb");
      }

      }
    • 测试类

      1
      2
      3
      4
      5
      6
      7
      8
      @Autowired
      private User user;

      @Test
      public void test03(){
      System.out.println(user.getUsername());
      // 这里会输出 bbb
      }

Scope注解

   在配置Bean的时候, 可以指定bean的作用域, 一般的bean可以配置爱为单例(singleton)或非单例(prototype). 配置为singleton的话, Spring的bean工厂都会只返回同一个bean实例, 而配置prototype, 则每次都会创建一个新的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.lacknb.test;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
//@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class ServiceTest {

public void Hello(){
System.out.println("Hello world !!");
}

}

AOP注解

加入依赖

1
2
3
4
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>

AopService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.lacknb.aop;


import org.springframework.stereotype.Component;

@Component
public class AopService {

public void saleService(){
System.out.println("要代理的销售业务方法");
}


}

ProxyService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cn.lacknb.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class ProxyService {

@Before("execution(* cn.lacknb.aop.AopService.saleService(..))")
public void before(){
System.out.println("业务方法调用前");
}

@After("execution(* cn.lacknb.aop.AopService.saleService(..))")
public void after(){
System.out.println("业务方法调用后");
}

}
1
2
3
4
5
6
7
8
@Autowired
private AopService aopService;

@Test
public void test02(){
System.out.println(aopService.getClass());
aopService.saleService();
}

根据结果可知, 我们的业务方法已经被代理, 因此在方法调用前和调用后, 都会执行通知到的方法, 输出的代理类为经过cglib处理的类. 在springboot2.0中, 默认情况下, 不管是代理接口还是类, 都使用cglib代理. 我们将spring.aop.proxy-target-class配置为false, 这样在代理接口时, 会使用jdk 动态代理. *据本人测试, 无论false或者true, 输出的都是cglib处理的类. *

ComponentScan注解

ComponentScan注解主要用于检测使用@Component装饰的组件, 并把它们注册到Spring容器中. 除了使用@Component修饰的组件外, 还有间接使用@Component的组件(如@Service, @Repository, @Controller, 或自定义注解), 都会被检测到,. 在使用Springboot时, 我们一般很少接触@ComponentScan注解, 但实际上每个应用都使用它, 它只是被放到@SpringBootApplication里面了. 在使用ComponentScan注解时, 要注意过滤器.

高级Spring注解

限定注解

@Primary注解, 当存在多个同类型的bean时, 可以指定优先注入的bean, 如果想对bean的注入选择做进一步的控制, 则可以使用限定注解. 限定注解可以与特定的参数关联起来, 缩小类型的匹配范围, 最后一个选择符合条件的bean来注入.

例子:

  • 需要到上面的user类

  • QuaConfig.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package cn.lacknb.beans;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    @Configuration
    public class QuaConfig {

    @Bean
    public User quaBeanA(){
    return new User("quaBeanA");
    }

    @Bean
    public User quaBeanB(){
    return new User("quaBeanB");
    }

    }
  • 创建测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Autowired
    @Qualifier("quaBeanB")
    private User user;

    /*
    * 测试 限定注解
    * */

    @Test
    public void test04(){
    System.out.println(user.getUsername()); // 输出quaBeanB
    }

自定义限定注解

当存在两个相同类型的注解时, 可以根据bean的名称来指定注入的bean, 如果需要根据特定的属性来指定注入的bean, 则可以自定义限定注解.

例子:

  • 使用上面的User实体类

  • 创建自定义注解TestQualifer.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package cn.lacknb.beans;

    import org.springframework.beans.factory.annotation.Qualifier;

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface TestQualifer {

    String type();

    }
  • 创建CustomConfig.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package cn.lacknb.beans;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    /*
    * 首先先定义一个TestQualifer注解, 该注解使用了@Qualifer修饰, 并且需要设置type属性
    * 在配置bean的时候, 需要为相应的bean设置不同的类型
    * spring容器提供了不同类型的user bean, 并使用@TestQualifer为它们进行了标识.
    * 在使用这些bean时, 同样使用@TestQualifer并指定type属性, 就可以拿回相应的bean
    * */

    @Configuration
    public class CustomConfig {

    @Bean
    @TestQualifer(type = "jerry")
    public User userBean(){
    return new User("and dog");
    }

    @Bean
    @TestQualifer(type = "tom")
    public User catBean(){
    return new User("cat");
    }

    }
  • 创建测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /*
    * 测试自定义限定注解
    * */

    @Autowired
    @TestQualifer(type = "jerry")
    private User user;

    @Test
    public void test05(){
    System.out.println(user.getUsername()); // 输出 and dog.
    }

自定义bean的生命周期

前面介绍了, Scope注解主要用于配置爱bean在容器中的生命周期, 常被配置为singleton, 和 prototype (多态)的, 在web环境下, 还可以配置为request, session等值. 表示Spring容器会为一次请求或一个会话分配一个bean的实例. 如果对bean的生命周期有特殊的需求, 则可以考虑使用自定义的scope, 自己写代码来定义bean的生命周期.

需求: 一个bean需要被使用4次之后, 就需要获取新的bean实例, 针对这样的需求, 可编写一个自定义的scope

  • 创建MyScope.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    package cn.lacknb.util;

    import org.springframework.beans.factory.ObjectFactory;
    import org.springframework.beans.factory.config.Scope;

    import java.util.HashMap;
    import java.util.Map;

    public class MyScope implements Scope {

    /*
    记录使用bean的使用次数
    */

    private Map<String, Integer> beanCounts = new HashMap<String, Integer>();

    /*
    * 保存实例
    * */

    private Map<String, Object> beans = new HashMap<String, Object>();


    @Override
    public Object get(String s, ObjectFactory<?> objectFactory) {

    if (beanCounts.get(s) == null){
    beanCounts.put(s, 0);
    }
    // 第一次使用, 放到实例的Map中
    Integer beancount = beanCounts.get(s);
    if (beancount == 0){
    Object newObject = objectFactory.getObject();
    beans.put(s, newObject);
    }
    // 获取对象
    Object bean = beans.get(s);
    // 计数器加1
    Integer newBeanCount = beancount + 1;
    // 使用次数超过4次了, 设置为0
    if (newBeanCount >= 4){
    newBeanCount = 0;
    }
    //设置新的次数
    beanCounts.put(s, newBeanCount);
    // 返回实例
    return bean;

    }

    @Override
    public Object remove(String s) {
    return null;
    }

    @Override
    public void registerDestructionCallback(String s, Runnable runnable) {

    }

    @Override
    public Object resolveContextualObject(String s) {
    return null;
    }

    @Override
    public String getConversationId() {
    return null;
    }
    }
  • 创建ScopeConfig.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    package cn.lacknb.util;

    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.config.CustomScopeConfigurer;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Scope;

    import javax.annotation.PostConstruct;

    @Configuration
    public class ScopeConfig {

    /*
    * @PostConstruct注解 用于在依赖关系注入完成之后需要执行的方法上
    *
    *
    * */

    @Autowired
    BeanFactory factory;

    @PostConstruct
    public void customScopeConfigurer(){
    // ScopeConfig初始化后执行本方法, 创建MyScope的实例
    CustomScopeConfigurer configurer = new CustomScopeConfigurer();
    configurer.addScope("four", new MyScope());
    configurer.postProcessBeanFactory((ConfigurableListableBeanFactory) factory);
    }

    @Bean
    @Scope(scopeName = "four")
    public ScopeBean beanA(){
    return new ScopeBean();
    }

    }

    这里的@PostConstruct注解 用于在依赖关系注入完成之后需要执行的方法上, 以执行任何初始化. 此方法必须在将类放入服务之前调用, 支持依赖关系注入的所有类都必须支持此注释. 即使类中没有请求注入任何资源, 用PostConstruct注释的方法也必须被调用. 只有一个方法可以用此进行注释. 应用PostConstruct注释的方法必须遵守以下所有标准:

    • 该方法不得有任何参数, 除非是在EJB拦截器的情况下, 根据EJB规范的定义, 在这种情况下它将带有一个invocationContext对象
    • 该方法的返回类型必须为void
    • 该方法不得抛出已检查异常
    • 应用PostConstruct的方法可以是Public, protected, package private或private
    • 除了应用程序之外, 该方法不能是static, 该方法可以是final
    • 如果该方法抛出未检查异常, 那么不得将次类放入服务中, 除非是能够处理异常并可从中恢复的EJB .
  • ScopeBean.java

    1
    2
    3
    4
    5
    6
    7
    8
    package cn.lacknb.util;

    public class ScopeBean {

    public ScopeBean() {
    System.out.println("ScopeBean初始化....");
    }
    }
  • 测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      
    /*
    * 测试一个bean实例使用4次
    * */

    @Autowired
    ApplicationContext context;

    @Test
    public void test06(){
    for (int i = 0; i < 7; i++) {
    System.out.println(context.getBean("beanA"));
    }
    }
  • 输出结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ScopeBean初始化....
    cn.lacknb.util.ScopeBean@3af4e0bf
    cn.lacknb.util.ScopeBean@3af4e0bf
    cn.lacknb.util.ScopeBean@3af4e0bf
    cn.lacknb.util.ScopeBean@3af4e0bf
    ScopeBean初始化....
    cn.lacknb.util.ScopeBean@245a26e1
    cn.lacknb.util.ScopeBean@245a26e1
    cn.lacknb.util.ScopeBean@245a26e1

Spring MVC常用注解

@ Controller, @RequestMapping, @GetMapping等.

@RestController

@RestController注解的目的是为了让我们更加简便地使用@Controller和@ResponseBody这两个注解, 查看@RestController源码就可以知道, 这个注解本身就使用了@Controller与@ResponseBody来修饰. ResponseBody注解可以修饰控制方法, 方法的返回值将会被写到HTTP响应体中, 所返回的内容, 将不会放到模型中, 也不会被解释为视图的名称.

@RequestAttribute, @SessionAttribute, @SessionAttributes等注解也较为常用.

@MatrixVariable注解

前面的@PathVariable注解主要是用来获取单一的URI参数, 如果通过URI来传输一些复杂的参数, 则可以考虑使用@MatrixVariable注解. 使用时需要遵守一定的规范, 参数的名称与值使用key-value形式, 多个参数之间使用(;)号隔开, 如果一个参数拥有多个值, 则值与值之间用逗号隔开. 这种URI的参数表现形式, 成为矩阵变量.

1
/car/{id};color=red;year=2019

该URI传入了id参数, 并且传入了color与year参数, 值分别为red与2019. 对于上面的URI, 如何获取呢? 例子:

  1. 首先开启矩阵变量的支持

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package cn.lacknb.controller;

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
    import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

    @Configuration
    public class WebConfig extends WebMvcConfigurationSupport {
    @Override
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
    final RequestMappingHandlerMapping requestMappingHandlerMapping = super.requestMappingHandlerMapping();

    /*
    * 将 RequestMappingHandlerMapping的RemoveSemicolonContent的属性设置为false, 即可开启对矩阵变量的支持
    * */

    requestMappingHandlerMapping.setRemoveSemicolonContent(false);
    return requestMappingHandlerMapping;
    }
    }
  2. 控制器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RequestMapping("/car/{id}")
    @ResponseBody
    public String test03(@PathVariable Integer id, @MatrixVariable String color, @MatrixVariable String year){

    System.out.println(id);
    System.out.println(color);
    System.out.println(year);
    return "test";
    }
  3. 测试

    url为: http://localhost:8080/car/11;color=red;year=2019

    控制台输出id color和year的值.

文件上传

fileUpload.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="http://localhost:8080/file/upload" method="post" enctype="multipart/form-data">
上传文件: <input type="file" name="file" />
<input type="submit" value="上传">
</form>
</body>
</html>

application.yml

1
2
3
4
5
6
7
8
9
10
spring:
thymeleaf:
cache: false
mode: HTML
servlet:
multipart:
# 上传文件总的最大值
max-file-size: 10MB
# 单个文件的最大值
max-request-size: 10MB

控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RequestMapping("/file/upload")
public String test04(@RequestParam("file") MultipartFile file, Model model) throws IOException {
String fileName = UUID.randomUUID() + file.getOriginalFilename();
String path = "/media/nianshao/nie475/java项目/files";
File filePath = new File(path);
if (!file.isEmpty()) {
if (!filePath.exists()) {
filePath.mkdirs();
}
File targetFile = new File(path, fileName);
file.transferTo(targetFile);
model.addAttribute("aaa", "上传成功");
}else{
model.addAttribute("aaa", "文件为空, 重新上传");
}
return "test01";
}

多文件上传

  1. 创建实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package cn.lacknb.beans;

    import org.springframework.web.multipart.MultipartFile;

    import java.util.List;

    public class MyFile {

    private List<MultipartFile> files;

    public List<MultipartFile> getFiles() {
    return files;
    }

    public void setFiles(List<MultipartFile> files) {
    this.files = files;
    }
    }
  2. upload.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <form action="http://localhost:8080/files/many" method="post" enctype="multipart/form-data">
    上传文件: <input type="file" name="files" /> <br>
    上传文件: <input type="file" name="files" /> <br>
    上传文件: <input type="file" name="files" /> <br>
    上传文件: <input type="file" name="files" /> <br>
    上传文件: <input type="file" name="files" /> <br>
    上传文件: <input type="file" name="files" /> <br>
    <input type="submit" value="上传">
    </form>
    </body>
    </html>
  3. 控制器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    /*
    * 多文件上传
    * */

    @RequestMapping("/files/many")
    public String test05(@ModelAttribute MyFile files, Model model) throws IOException {
    String path = "/media/nianshao/nie475/java项目/files";
    File filePath = new File(path);
    if (!filePath.exists()){
    filePath.mkdirs();
    }
    List<MultipartFile> files11 = files.getFiles();
    int count = 0;
    for (int i = 0; i < files11.size(); i++) {
    if (!files11.get(i).isEmpty()) {
    String fileName = UUID.randomUUID() + files11.get(i).getOriginalFilename();
    File targetFile = new File(filePath, fileName);
    files11.get(i).transferTo(targetFile);
    count++;
    }
    }
    model.addAttribute("aaa", "成功上传" + count + "个文件");
    if (count == 0){
    model.addAttribute("aaa", "上传的文件为空");
    }
    return "test01";
    }

SpringBoot的条件注解

   SpringBoot提供了自动配置, 一些内置的或者常用的组件Spring已经帮我们配置好了, 我们在使用这些组件的时候, 加入相关依赖就可以了, 并不需要额外进行配置, 从而减少了搭建项目的工作量. 自动配置是否执行, 取决于条件注解.

类条件注解

   当类路径中存在或不存在某个类时, 再决定是否执行自动配置. 存在该类则使用@ConditionalOnClass, 不存在则使用@ConditionalOnMissingClass, 它们都可以修饰类或者方法. 这两个注解通过配置value属性来指定一个类, 这个类有可能并不存在于实际的运行环境中, 也可以使用name属性来指定权限定类名. 假设项目中存在MySQL的驱动类, 则会启用某个配置类, 此时则可以使用@ConditionalOnClass.

  1. MyBean.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package cn.lacknb.bean;

    public class MyBean {

    private String name;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }
    }
  2. OnClassConfig.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package cn.lacknb.config;

    import cn.lacknb.bean.MyBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    @ConditionalOnClass(name = "com.mysql.jdbc.Driver")
    @Configuration
    public class OnClassConfig {

    @Bean
    public MyBean myBean(){
    return new MyBean();
    }

    }

    当项目有mysql的依赖时, 就会创建 MyBean的实例

  3. 测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package cn.lacknb.contoller;

    import cn.lacknb.bean.MyBean;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.Map;

    @RestController
    public class OnClassController {

    @Autowired
    ApplicationContext context;

    @RequestMapping("/bean")
    public String getBean(){
    Map<String, MyBean> beans = context.getBeansOfType(MyBean.class);
    System.out.println(beans.size());
    return "success";
    }

    }

    代码中的getBean方法会到容器中获取MyBean类型的bean, 并以Map形式返回, 最后输出Map的size. 在pom.xml文件中, 加入MySql依赖, 测试运行, 控制台输出为1, 将mysql的依赖注释掉, 则输出为0. 使用@ConditionalOnMissingClass则有相反的效果.

发布和调用REST服务

REST是英文 Representational State Transfer 的缩写, 一般翻译为 “表述性状态转移”. REST本身只是分布式系统中的一种架构风格, 并不是某种标准或者规范. 而是一种 轻量级的基于HTTP协议的Web Service风格.

发布REST服务

加入spring-boot-starter-web依赖,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package cn.lacknb.REST;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/*
* 发布与调用REST服务
* 一定要加@RestController注解
* 浏览器输入 http://localhost:8080/person/name 返回json数据
* 如果不使用springboot, 估计你还要为寻找依赖包而疲于奔命
* */

@SpringBootApplication
@RestController
public class RESTexample {

public static void main(String[] args) {
SpringApplication.run(RESTexample.class, args);
}


/*
* MediaType.APPLICATION_JSON_VALUE的值为 application/json 将生产数据的类型
*
* */

@GetMapping(value = "/person/{name}", produces = MediaType.APPLICATION_JSON_VALUE)
public Person person(@PathVariable String name){
Person person = new Person();
person.setName(name);
person.setAge(23);
return person;
}

static class Person{
private String name;
private Integer age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}

}

使用RestTemplate调用服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package cn.lacknb.REST;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class MyService {

@Autowired
private RestTemplateBuilder restTemplateBuilder;

public RestTemplate restTemplate(){
return restTemplateBuilder.rootUri("http://localhost:8080").build();
}

/*
* 使用RestTemplateBuilder创建template
* */

public RESTexample.Person useBuild(){
RESTexample.Person p = restTemplate().getForObject("/person/aynu", RESTexample.Person.class);
return p;
}

}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cn.lacknb.REST;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RESTexample.class)
public class MyServiceTest {

@Autowired
private MyService myService;

@Test
public void test(){
RESTexample.Person person = myService.useBuild();
System.out.println(person.getName() + "===" + person.getAge());
}

}

使用feign调用服务

Feign是github上的一个开源项目, 其目的就是为了简化web service客户端的开发. 加入依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>RELEASE</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.lacknb.feign;

import cn.lacknb.REST.RESTexample;
import feign.Param;
import feign.RequestLine;

/*
* 在接口中使用了@RequestLine和@Param注解, 这两个注解是Feign的注解. 使用注解修饰后
* getPerson方法被调用, 然后使用HTTP的get方法向 /person/name, 服务发送请求
* */

public interface PersonClient {

@RequestLine("GET /person/{name}")
RESTexample.Person getPerson(@Param("name") String name);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cn.lacknb.feign;

import cn.lacknb.REST.RESTexample;
import feign.Feign;
import feign.gson.GsonDecoder;

public class FeignMain {

public static void main(String[] args) {
// 调用接口
PersonClient personClient = Feign.builder().decoder(new GsonDecoder()).target(PersonClient.class, "http:" +
"//localhost:8080/");
RESTexample.Person p = personClient.getPerson("aynu");
System.out.println(p.getName() + "****" + p.getAge());
}

}

SpringBoot与Servlet

无论是创建servlet, listener 还是filter, Springboot启动类都需要加 注解@ServletComponentScan(“需要扫描的包”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.lacknb.Test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication(scanBasePackages = "cn.lacknb")
@ServletComponentScan("cn.lacknb.servlet")
public class Main {

public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}

创建Servlet

这里使用注解@WebServlet(“/tets”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.lacknb.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/test")
public class TestServlet extends HttpServlet {

public TestServlet() {
System.out.println("servlet初始化");
}

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("servlet中的service方法被调用");
}
}

浏览器访问 http://localhost:8080/test

创建监听器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package cn.lacknb.servlet;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class TestListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("ServletContext初始化");
}

@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("ServletContext销毁...");
}
}

创建过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.lacknb.servlet;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/filter/*")
public class TestLisnter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器初始化");
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("dofilter 方法执行");
}

@Override
public void destroy() {
System.out.println("destroy ");
}
}

使用Springboot构建可部署的war包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
</parent>
<groupId>cn.lacknb.war</groupId>
<artifactId>Springboot-war</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<build>
<finalName>test-war</finalName>
</build>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package cn.lacknb.main;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/*
* 启动类继承SpringBootServletInitializer类 这里需要实现重写父类的configure方法
* SpringBootServletInitializer类将会调用configure方法来得到一个SpringApplicationBuilder
* 实例, 根据名称所知, 这个SpringApplicationBuilder会帮我们创建上下文, 实际上它会帮我们创建
* WebApplicationContext实例, 其在创建Spring上下文的过程中, 会查找使用了@Configuration注解的
* 配置类, 然后对它们进行初始化
*
* */
@SpringBootApplication
@RestController
public class WarApp extends SpringBootServletInitializer {

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(WarApp.class);
}

public static void main(String[] args) {
SpringApplication.run(WarApp.class, args);
}

@GetMapping("/hello")
@ResponseBody
public String hello(){
return "hello, war .....package......";
}
}

启动类继承SpringBootServletInitializer类 这里需要实现重写父类的configure方法

SpringBootServletInitializer类将会调用configure方法来得到一个SpringApplicationBuilder

实例, 根据名称所知, 这个SpringApplicationBuilder会帮我们创建上下文, 实际上它会帮我们创建

WebApplicationContext实例, 其在创建Spring上下文的过程中, 会查找使用了@Configuration注解的

配置类, 然后对它们进行初始化

– 对于启动类, 我们可以这样理解: 在eclipse运行main方法时, 会使用原来的方式(执行jar包的方式)来启动web应用(含服务器), 如果将项目打包成一个war包放到web服务器上运行, 就会创建WebApplicationContext, 在创建的过程中, 会调用configure方法. 另外, 父类调用一个方法来实现部分工作, 但该方法需要由继承的子类实现, 这种模式是GoF设计模式的一种: 模板方法.

将war包放在tomcat的webapp目录下, 然后直接启动tomcat, 在浏览器地址栏输入: localhost:8080/test-war/hello

使用其他服务器

使用jetty

首先先排除依赖tomcat, 这个是springboot默认使用的.

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>

添加jetty服务器

1
2
3
4
5
<!--使用jetty服务器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

MIU3z6.png

使用Undertow

方法和上面一样

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

MIap6K.png

服务器访问日志

服务器访问日志与服务器日志不是同一个概念, 我们一般在tomcat控制台中看到的日志, 是服务器日志, 而服务器访问日志, 则会记录服务处理的消息, 默认情况下, tomcat并不会记录fangwen 日志. 创建一个maven项目, 将application.yml文件 添加如下信息.

1
2
3
4
5
6
7
8
server:
tomcat:
basedir: my-tomcat
accesslog:
pattern: 'ip: %A, response code: %s, time: %t'
enabled: true # 用于开启日志记录
directory: my-logs
buffered: false # 表示不进行缓冲, 直接将日志记录到文件中

日志显示格式为:

1
2
3
ip: 127.0.1.1, response code: 200, time: [21/Nov/2019:18:01:50 +0800]
ip: 127.0.1.1, response code: 200, time: [21/Nov/2019:18:01:50 +0800]
ip: 127.0.1.1, response code: 200, time: [21/Nov/2019:18:01:51 +0800]

banner配置 (自定义显示图案)

  1. 在根目录创建banner.txt

    将ASCII图案放在这里

  2. 除了文本外, 还可以提供图片文件用于显示, 图片格式可以为jpg, png或gif, Springboot会将图片转换为ASCII, 以文本的方式将图片显示到控制台中.

application.yml如下

1
2
3
4
5
6
spring:
banner:
charset: utf-8
image:
location: classpath:banner.jpg
invert: false

在springboot中配置banner, 可以在Application.yml中配置一下属性:

  • spring.banner.charset: 如果banner的文本文件中有utf-8以外的编码, 则需要配置该项
  • spring.banner.image.location: 指定banner图片的位置
  • spring.banner.image.width: banner图片经转换后的字符长度, 默认为76
  • spring.banner.image.height: 图片经转换后的字符高度
  • spring.banner.image.margin: 设置图片的显示边距 默认为2
  • spring.banner.image.invert: 配置为true 则将图片进行转换显示, 以适应深色的终端风格
-------------------本文结束 感谢您的阅读-------------------
坚持原创技术分享,您的支持将鼓励我继续创作!
0%