目录
SpringBoot相关知识
/  

SpringBoot相关知识

SpringBoot相关知识

Spring常用注解

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

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

@Autowired, @Primary, @Resource

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

​ 还可以使用构造器注入

InjectBean myBean;
@Autowired
public InjectApp(InjectBean myBean2){
    this.myBean = myBean2;
}

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

​ 不能修饰构造器

@Primary注解 注入

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

  • 例子

    • User.java

      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

      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");
          }
      
      }
      
      
    • 测试类

          @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, 则每次都会创建一个新的实例

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注解

加入依赖

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

AopService.java

package cn.lacknb.aop;


import org.springframework.stereotype.Component;

@Component
public class AopService {

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


}

ProxyService.java

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("业务方法调用后");
    }

}

    @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

    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");
        }
    
    }
    
    
  • 创建测试类

        @Autowired
        @Qualifier("quaBeanB")
        private User user;
    
        /*
        * 测试 限定注解
        * */
    
        @Test
        public void test04(){
            System.out.println(user.getUsername()); // 输出quaBeanB
        }
    

自定义限定注解

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

例子:

  • 使用上面的User实体类

  • 创建自定义注解TestQualifer.java

    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

    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");
        }
    
    }
    
    
  • 创建测试类

        /*
        * 测试自定义限定注解
        * */
    
        @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

    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

    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

    package cn.lacknb.util;
    
    public class ScopeBean {
    
        public ScopeBean() {
            System.out.println("ScopeBean初始化....");
        }
    }
    
    
  • 测试类

    
        /*
        * 测试一个bean实例使用4次
        * */
    
        @Autowired
        ApplicationContext context;
    
        @Test
        public void test06(){
            for (int i = 0; i < 7; i++) {
                System.out.println(context.getBean("beanA"));
            }
        }
    
  • 输出结果

    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的参数表现形式, 成为矩阵变量.

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

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

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

    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. 控制器

        @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

<!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

spring:
  thymeleaf:
    cache: false
    mode: HTML
  servlet:
    multipart:
      # 上传文件总的最大值
      max-file-size: 10MB
      # 单个文件的最大值
      max-request-size: 10MB

控制器

    @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. 创建实体类

    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

    <!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. 控制器

     /*
        * 多文件上传
        * */
    
        @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

    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

    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. 测试类

    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依赖,

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调用服务

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;
    }

}

测试类

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客户端的开发. 加入依赖

<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>
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);
}

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("需要扫描的包")

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")

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

创建监听器

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销毁...");
    }
}

创建过滤器

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包

    <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>

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默认使用的.

<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服务器

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

MIU3z6.png

使用Undertow

方法和上面一样

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

MIap6K.png

服务器访问日志

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

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

日志显示格式为:

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如下

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 则将图片进行转换显示, 以适应深色的终端风格

标题:SpringBoot相关知识
作者:gitsilence
地址:https://blog.lacknb.cn/articles/2019/11/26/1577974160404.html