Hefery 的个人网站

Hefery's Personal Website

Contact:hefery@126.com
  menu
73 文章
0 浏览
1 当前访客
ღゝ◡╹)ノ❤️

SpringBoot基础—必知必会

SpringBoot相关概念

SpringBoot简介

SpringBoot:Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手

SpringBoot优点

  • 独立运行:内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内
  • 简化配置:启动器自动依赖其他组件,简少了maven的配置
  • 自动配置:能根据当前类路径下的类、jar包来自动配置bean,如添加一个spring-boot-starter-web启动器就能拥有web的功能,无需其他配置
  • 无代码生成和XML配置:Spring Boot配置过程中无代码生成,也无需XML配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是Spring4.x的核心功能之一
  • 应用监控:提供一系列端点可以监控服务及应用,做健康检测

SpringBoot依赖

  • 项目元数据:项目信息,包括groupld、artifactld、version,name,description
  • parent父工程:继承spring-boot-starter-parent管理核心依赖
  • dependencies依赖:
    • spring-boot-starter:启动器,即 SpringBoot 启动场景,如 web、test
    • spring-boot-starter-web:实现 HTTP 接口,使用 SpringMVC 构建 RESTful 应用,使用 Tomcat 作为默认嵌入式容器
    • spring-boot-starter-test:单元测试
    • spring-boot-starter-data-jpa:引入JPA
  • build构建配置:默认使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把SpringBoot 应用打包成 jar 来直接运行

开启 SpringBoot 特性的方式

  • 继承spring-boot-starter-parent项目
  • 导入spring-boot-dependencies依赖

SpringBoot启动器

SpringBoot Starters:启动器,包含了一系列可以集成到应用里面的依赖包,可以一站式集成 Spring 及其他技术,而不需要到处找示例代码和依赖包

分类:

  • 应用类启动器:
    • spring-boot-starter:包含自动配置、日志、YAML的支持
    • spring-boot-starter-web:使用Spring MVC构建 Web 工程,包含RESTful,默认使用Tomcat容器
  • 生产类启动器:
    • spring-boot-starter-actuator :提供生产环境特性,能监控管理应用
  • 技术类启动器
    • spring-boot-starter-json :提供对JSON的读写支持
    • spring-boot-starter-logging:默认的日志启动器,默认使用Logback
如何在SpringBoot启动时运行特定代码?

实现接口 ApplicationRunner 或 CommandLineRunner,这两个接口实现方式一样,都只提供一个 run 方法

关于启动顺序:如果启动的时候有多个 ApplicationRunner 和 CommandLineRunner,想控制它们的启动顺序,实现org.springframework.core.Ordered接口或使用org.springframework.core.annotation.Order注解

SpringBoot自定义Starters

使用 Spring + SpringMVC 框架进行开发的时候,如果需要引入 Mybatis 框架,那么需要在 xml 中定义需要的 bean 对象,这个过程很明显是很麻烦的,如果需要引入额外的其他组件,那么也需要进行复杂的配置,因此在 SpringBoot 中引入了 Starter

starter就是一个jar包,写一个 @Configuration 的配置类,将这些 bean 定义在其中,然后在 starter 包的 META-INF/spring.factories 中写入配置类,那么 SpringBoot 程序在启动的时候就会按照约定来加载该配置类

开发人员只需要将相应的 starter 包依赖进应用中,进行相关的属性配置,就可以进行代码开发,而不需要单独进行 bean 对象的配置

步骤:

  • 配置@Configuration的配置类
  • 编写spring.factories文件,添加到要启动的应用程序

SpringBoot启动类

SpringApplication.run(HelloWorldApplication.class, args);

  • SpringApplication的加载实例化:
    1. 判断项目是普通项目还是Web项目
      public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
         this.resourceLoader = resourceLoader;
         Assert.notNull(primarySources, "PrimarySources must not be null");
         this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
         this.webApplicationType = WebApplicationType.deduceFromClasspath();
         this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
         setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
         setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
         this.mainApplicationClass = deduceMainApplicationClass();
      }
      
    2. 查找并加载所有可用初始化器,设置到 initializers 属性中
    3. 查找所有的应用程序监听器,设置到 listeners 属性中
    4. 推断并设置 main 方法的定义类,找到运行的主类
  • run方法的执行:
    1. headless 系统属性设置:configureHeadlessProperty();
    2. 初始化监听器:SpringApplicationRunListeners listeners = getRunListeners(args);
      1. 根据传入的类、名,得到所需工厂集合的实例
      2. 通过类加载器获取 spring.factories 文件
      3. 获取文件中工厂类的全路径
      4. 通过工厂类反射,得到工厂的 class 对象,构造方法
      5. 生成工厂类实例,并返回
    3. 启动已准备好的监听器:listeners.starting(bootstrapContext, this.mainApplicationClass);
    4. 循环配置参数:ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      1. 创建配置环境 web/standard
      2. 加载属性资源
      3. 加入预监听集
    5. 打印 banner 图案:Banner printedBanner = printBanner(environment);
    6. 加载上下文区域:context = createApplicationContext();
      1. 根据类型创建 web/standard 上下文
    7. 准备上下文异常报告器:context.setApplicationStartup(this.applicationStartup);
    8. 上下文前置处理 prepareContext,配置监听:prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      • environment环境设置
      • initialize 初始化设置,可扩展
      • 资源获取并 load
    9. 上下文刷新 refreshContext:refreshContext(context);
      • Bean 工厂加载
      • 通过工厂生产 Bean
      • 刷新生命周期
    10. 上下文后置结束处理 afterRefresh(context, applicationArguments);,计时器结束监听结束stopWatch.stop();
    11. 发布应用上下文启动完成:listeners.started(context);
    12. 执行 Runner 运行器:callRunners(context, applicationArguments);
    13. 发布应用上下文就绪并返回:listeners.running(context);
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    // 1.headless 系统属性设置
    configureHeadlessProperty();
    // 2. 初始化监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 3.启动已准备好的监听器
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        // 4.循环配置参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 5.打印 banner 图案
        Banner printedBanner = printBanner(environment);
        // 6.加载上下文区域
        context = createApplicationContext();
        // 7.准备上下文异常报告器
        context.setApplicationStartup(this.applicationStartup);
        // 8.上下文前置处理 prepareContext,配置监听
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 9.上下文刷新 refreshContext
        refreshContext(context);
        // 10.上下文后置结束处理
        afterRefresh(context, applicationArguments);
        // 计时器结束监听结束
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // 11.发布应用上下文启动完成
        listeners.started(context);
        // 12.执行 Runner 运行器
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 13.发布应用上下文就绪并返回
        listeners.running(context);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

SpringBoot热部署

SpringBoot配置

核心配置文件

  • application.properties:语法:key=value
  • application.yml:语法:key: value。主要用于 SpringBoot 项目的自动化配置。
  • bootstrap.yml:使用SpringCloud Config配置中心;固定的不能被覆盖的属性;加密/解密的场景

boostrap 由父 ApplicationContext 加载,比 applicaton 优先加载,且里面的属性不能被覆盖

.yml 格式不支持 @PropertySource 注解导入配置

配置文件的作用:修改 SpringBoot 自动配置的默认值

application.yml

person:
  # String
  last-name: Hefery${random.uuid}
  # Integer
  age: ${random.int}
  # Boolean
  boss: true
  # Date
  birthday: 2021/08/07
  email: hefery@126.com
  #  Map
  map: {k1: v1, k2: v2}
  # List
  list:
    - lisi
    - wanwu
    - zhangsan
  # JavaBean
  dog:
    name: ${person.last-name}的小狗
    age: 3

详解SpringBoot解析yml全流程

SpringBoot读取配置方式
  • @Value注解:
    @Component
    public class InfoConfig1 {
        @Value("${info.address}")
        private String address;
    
        @Value("${info.company}")
        private String company;
    }
    
  • @ConfigurationProperties注解
    @Component
    @ConfigurationProperties(prefix = "info")
    public class InfoConfig2 {
        private String address;
        private String company;
        private String degree;
    }
    
  • 读取指定文件:@PropertySource+@Value注解
    @Component
    @PropertySource(value = { "config/db-config.properties" })
    public class DBConfig1 {
        @Value("${db.username}")
        private String username;
    
        @Value("${db.password}")
        private String password;
    }
    
SpringBoot配置加载顺序

1、开发者工具 Devtools 全局配置参数;
2、单元测试上的 @TestPropertySource 注解指定的参数;
3、单元测试上的 @SpringBootTest 注解指定的参数;
4、命令行指定的参数,如 java -jar springboot.jar --name="Java技术栈"
5、命令行中的 SPRING_APPLICATION_JSONJSON 指定参数, 如 java -Dspring.application.json='{"name":"Java技术栈"}' -jar springboot.jar
6、ServletConfig 初始化参数;
7、ServletContext 初始化参数;
8、JNDI参数(如 java:comp/env/spring.application.json);
9、Java系统参数(来源:System.getProperties());
10、操作系统环境变量参数;
11、RandomValuePropertySource 随机数,仅匹配:ramdom.*
12、JAR包外面的配置文件参数(application-{profile}.properties(YAML)
13、JAR包里面的配置文件参数(application-{profile}.properties(YAML)
14、JAR包外面的配置文件参数(application.properties(YAML)
15、JAR包里面的配置文件参数(application.properties(YAML)
16、@Configuration配置文件上 @PropertySource 注解加载的参数;
17、默认参数(通过 SpringApplication.setDefaultProperties 指定);

SpringBoot不同环境配置

applcation.properties

  • 本地开发:application-dev.properties
  • 测试环境:application-sit.properties
  • 用户测试:application-uat.properties
  • 生产环境:application-prod.properties

不同环境配置方式

  • 在 applcation.properties 文件中指定当前的环境 spring.profiles.active=sit
  • 在 bootstrap.yml 文件指定spring.profiles.active=sit
    spring:
      profiles: 
        active: prod
    ---
    spring: 
      profiles: dev  
    server: 
      port: 8080  
    
    ---
    spring: 
      profiles: test  
    server: 
      port: 8081
    
  • 运行 jar 包指定Profile:java -jar xx.jar --spring.profiles.active=prod

自动配置原理

启动类的@SpringBootApplication是一个复合注解,在@SpringBootApplication中有一个注解@EnableAutoConfiguration开启自动配置,而这个注解也是一个派生注解,其中的关键功能由@Import提供,其导入的AutoConfigurationImportSelectorselectImports()方法通过SpringFactoriesLoader.loadFactoryNames()扫描所有具有META-INF/spring.factoriesjar包。spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件。spring.factories文件也是一组一组的 key=value的形式,其中一个keyEnableAutoConfiguration类的全类名,而它的value是一个 xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔。这个@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了 SpringBoot 的启动类上。在SpringApplication.run(...)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到 Spring 容器中

xxxxAutoConfigurartion:自动配置类;给容器中添加组件
xxxxProperties:封装配置文件中相关属性

精髓:SpringBoot启动会加载大量的自动配置类,看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中,再来看这个自动配置类中到底配置了哪些组件,只要我们要用的组件存在在其中,我们就不需要再手动配置了。给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可

自定义banner

在 resources/banner.txt 设置 banner 样式,启动类会识别并应用

               _
      ,´ `.
______|___|______________________________________________
      |  /                       _..-´|
      | /                  _..-´´_..-´|
______|/__________________|_..-´´_____|__________|\______
     ,|                   |           |          | \
    / |                   |           |          | ´
___/__|___________________|___________|__________|_______
  / ,´| `.                |      ,d88b|          |
 | .  |   \            __ |      88888|       __ |
_|_|__|____|_________,d88b|______`Y88P'_____,d88b|_______
 |  ` |    |         88888|                 88888|
 `.   |   /          `Y88P'                 `Y88P'
___`._|_.´_______________________________________________
      |
    , |                                  Hefery
    '.´

SpringBoot注解

  • @SpringBootApplication:启动类注解
    • @SpringBootConfiguration:SpringBoot配置
      • @Configuration:Spring配置类
    • @EnableAutoConfiguration:自动配置
      • @AutoConfigurationPackage:自动配置包
        • @Import(AutoConfigurationPackages.Registrar.class):自动注册(metadata、registry)
      • @Import(AutoConfigurationImportSelector.class):自动导入选择
        • selectImports():加载选择的启动场景
        • getAutoConfigurationEntry():
          List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        • getCandidateConfigurations():获取候选的配置,将类路径下META-INF/spring.factories里面配置的所有 EnableAutoConfiguration 的值加入到了容器中;SpringFactoriesLoader.loadFactoryNames()循环读取META-INF/spring.factories配置,并写入到Properties properties = PropertiesLoaderUtils.loadProperties(resource);
    • @ComponentScan:扫描当前启动类同级的包

image.png

SpringBoot日志

引入依赖

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

resources/logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
    scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
    scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
    debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定义日志的根目录 -->
    <property name="LOG_HOME" value="/app/log" />
    <!-- 定义日志文件名称 -->
    <property name="appName" value="hefery-springboot"></property>
    <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!--
            日志输出格式:
                %d表示日期时间,
                %thread表示线程名,
                %-5level:级别从左显示5个字符宽度
                %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
                %msg:日志消息,
                %n是换行符
        -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <springProfile name="dev">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} --> [%thread] --> %-5level --> %logger{50} - %msg%n</pattern>
            </springProfile>
            <springProfile name="prod">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ==> [%thread] ==> %-5level ==> %logger{50} - %msg%n</pattern>
            </springProfile>
        </layout>
    </appender>

    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->  
    <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名称 -->
        <file>${LOG_HOME}/${appName}.log</file>
        <!--
        当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
        TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--
            滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动 
            %i:当文件大小超过maxFileSize时,按照i进行文件滚动
            -->
            <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <!-- 
            可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
            且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
            那些为了归档而创建的目录也会被删除。
            -->
            <MaxHistory>365</MaxHistory>
            <!-- 
            当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
            -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日志输出格式: -->   
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} --> [ %thread ] - [ %-5level ] [ %logger{50} : %line ] -->  %msg%n</pattern>
        </layout>
    </appender>

    <!-- 
		logger主要用于存放日志对象,也可以定义日志类型、级别
		name:表示匹配的logger类型前缀,也就是包的前半部分
		level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR
		additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,
		false:表示只用当前logger的appender-ref,true:
		表示当前logger的appender-ref和rootLogger的appender-ref都有效
    -->
    <!-- hibernate logger -->
    <logger name="com.hefery" level="debug" />
    <!-- Spring framework logger -->
    <logger name="org.springframework" level="debug" additivity="false"></logger>



    <!-- 
        root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
        要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。
    -->
    <root level="info">
        <appender-ref ref="stdout" />
        <appender-ref ref="appLogAppender" />
    </root>
</configuration> 

SpringBoot与Web开发

导入静态资源

静态资源存放路径

  • /**
  • classpath:/META-INF/resources/
  • classpath:/resources/
  • classpath:/static/
  • classpath:/public/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
  
        @SuppressWarnings("deprecation")
	@Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class,
	   org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
            @Override
		public void addResourceHandlers(ResourceHandlerRegistry registry) {
			// 如果在配置文件中指定了静态资源路径,默认的配置就失效 spring.mvc.ststic-path-pattern
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
			// private String staticPathPattern = "/**";
			// private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
				"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
			addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
				registration.addResourceLocations(this.resourceProperties.getStaticLocations());
				if (this.servletContext != null) {
					ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
					registration.addResourceLocations(resource);
				}
			});
		}
        }
}

拓展MVC功能

编写一个配置类(@Configuration),继承 WebMvcConfigurerAdapter ;不能标注@EnableWebMvc


i18n国际化

国际化使用

/resources/i18n/XX.properties

源码解析

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

        @Override
        @Bean
        @ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
        @SuppressWarnings("deprecation")
        public LocaleResolver localeResolver() {
            if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
                return new FixedLocaleResolver(this.webProperties.getLocale());
            }
            if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
                return new FixedLocaleResolver(this.mvcProperties.getLocale());
            }
            AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
            Locale locale = (this.webProperties.getLocale() != null) ? this.webProperties.getLocale()
                    : this.mvcProperties.getLocale();
            localeResolver.setDefaultLocale(locale);
            return localeResolver;
        }

}

国际化定制

@Configuration
public class I18nConfig implements WebMvcConfigurer {

    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver slr = new SessionLocaleResolver();
        // 默认语言
        slr.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return slr;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        // 参数名
        lci.setParamName("lang");
        return lci;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }

}

Spring Boot 如何使用拦截器、过滤器、监听器

SpringBoot集成

MySQL

引入依赖

<!-- MySQL 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.12</version>
    <scope>runtime</scope>
</dependency>

引入配置

spring:
  datasource:
    # 数据源
    url: jdbc:mysql://120.25.152.210:3306/e_commerce?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root
    password: hefery
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver

Druid

引入依赖

<!-- 阿里数据库连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>${druid.version}</version>
</dependency>

引入配置

spring:
  datasource:
    druid:
      # 初始化时建立物理连接的个数
      initial-size: 8
      # 最小连接池数量
      min-idle: 5
      # 最大连接池数量
      max-active: 20
      # 获取连接时最大等待时间,单位毫秒
      max-wait: 6000
      # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效
      test-while-idle: true
      # 既作为检测的间隔时间又作为testWhileIdel执行的依据
      time-between-eviction-runs-millis: 6000
      # 销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
      min-evictable-idle-time-millis: 3000
      # 当数据库抛出不可恢复的异常时,抛弃该连接
      exception-sorter: true
      # 是否缓存preparedStatement,mysql5.5+建议开启
      pool-prepared-statements: true
      # 合并多个DruidDataSource的监控数据
      use-global-data-source-stat: true
      # 设置访问druid监控页的账号和密码,默认没有
      stat-view-servlet:
        login-username: hefery
        login-password: hefery
      # 配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
      filters: stat,wall,log4j
      maxPoolPreparedStatementPerConnectionSize: 20
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

Druid监控面板:http://ip:port/druid/[webapp.html]

定制化监控面板:http://ip:port/monitor/druid/[webapp.html]

Druid定制

@Configuration
@Slf4j
public class DruidConfig {

	@Autowired
	private ConfigServerDruid configServerDruid;

	@Bean
	public DataSource masterDataSource(DruidProperties druidProperties) {
		log.info("[信息]:数据库加载.");
		DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
		return druidProperties.dataSource(dataSource, configServerDruid, false);
	}

	@Bean
	@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
	public DataSource slaveDataSource(DruidProperties druidProperties) {
		log.info("[信息]:数据库加载,开启主从模式.");
		DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
		return druidProperties.dataSource(dataSource, configServerDruid, true);
	}

	@Bean(name = "dynamicDataSource")
	@Primary
	public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource) {
		Map<Object, Object> targetDataSources = new HashMap<>();
		targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
		targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
		return new DynamicDataSource(masterDataSource, targetDataSources);
	}

	/**
	 * 去除监控页面底部的广告
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Bean
	@ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
	public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) {
		// 获取web监控页面的参数
		DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
		// 提取common.js的配置路径
		String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
		String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
		final String filePath = "support/http/resources/js/common.js";
		// 创建filter进行过滤
		Filter filter = new Filter() {
			@Override
			public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
			}

			@Override
			public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
					throws IOException, ServletException {
				chain.doFilter(request, response);
				// 重置缓冲区,响应头不会被重置
				response.resetBuffer();
				// 获取common.js
				String text = Utils.readFromResource(filePath);
				// 正则替换banner, 除去底部的广告信息
				text = text.replaceAll("<a.*?banner\"></a><br/>", "");
				text = text.replaceAll("powered.*?shrek.wang</a>", "");
				response.getWriter().write(text);
			}

			@Override
			public void destroy() {
			}
		};
		FilterRegistrationBean registrationBean = new FilterRegistrationBean();
		registrationBean.setFilter(filter);
		registrationBean.addUrlPatterns(commonJsPattern);
		return registrationBean;
	}

	/**
	 * 注册一个StatFilter
	 * @return
	 */
	@Bean
	@Primary
	public StatFilter statFilter() {
		StatFilter statFilter = new StatFilter();
		statFilter.setMergeSql(configServerDruid.isMergeSql());
		statFilter.setLogSlowSql(configServerDruid.isLogSlowSql());
		statFilter.setSlowSqlMillis(configServerDruid.getLowSqlMillis());
		return statFilter;
	}

	/**
	 * 注册一个StatViewServlet
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Bean
	public ServletRegistrationBean druidStatViewServlet() {
		// org.springframework.boot.context.embedded.ServletRegistrationBean提供类的进行注册.
		ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),
				"/monitor/druid/*");
		// 添加初始化参数:initParams
		// 白名单:
		servletRegistrationBean.addInitParameter("allow", configServerDruid.getAllow());
		// IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not
		// permitted to view this page.
		servletRegistrationBean.addInitParameter("deny", configServerDruid.getDeny());
		// 登录查看信息的账号密码.
		servletRegistrationBean.addInitParameter("loginUsername", configServerDruid.getLoginUsername());
		servletRegistrationBean.addInitParameter("loginPassword", configServerDruid.getLoginPassword());
		// 是否能够重置数据.
		servletRegistrationBean.addInitParameter("resetEnable", configServerDruid.getResetEnable());
		return servletRegistrationBean;
	}

	/**
	 * 注册一个:filterRegistrationBean
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Bean
	public FilterRegistrationBean druidStatFilter() {
		FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
		// 添加过滤规则.
		filterRegistrationBean.addUrlPatterns("/*");
		// 添加忽略资源
		filterRegistrationBean.addInitParameter("exclusions", configServerDruid.getExclusions());
		return filterRegistrationBean;
	}

}

@RefreshScope
@RestController
@Repository
@Data
public class ConfigServerDruid {

	@Value("${spring.datasource.type}")
	private String type;

	@Value("${spring.datasource.driverClassName}")
	private String driverClassName;

	@Value("${spring.datasource.druid.master.name}")
	private String masterName;

	@Value("${spring.datasource.druid.master.password}")
	private String masterPassword;

	@Value("${spring.datasource.druid.master.url}")
	private String masterUrl;

	@Value("${spring.datasource.druid.save.name}")
	private String saveName;

	@Value("${spring.datasource.druid.save.password}")
	private String savePassword;

	@Value("${spring.datasource.druid.save.url}")
	private String saveUrl;

	@Value("${spring.datasource.druid.initialSize}")
	private int initialSize;

	@Value("${spring.datasource.druid.minIdle}")
	private int minIdle;

	@Value("${spring.datasource.druid.maxActive}")
	private int maxActive;

	@Value("${spring.datasource.druid.maxWait}")
	private int maxWait;

	@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
	private int timeBetweenEvictionRunsMillis;

	@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
	private int minEvictableIdleTimeMillis;

	@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
	private int maxEvictableIdleTimeMillis;

	@Value("${spring.datasource.druid.validationQuery}")
	private String validationQuery;

	@Value("${spring.datasource.druid.testWhileIdle}")
	private boolean testWhileIdle;

	@Value("${spring.datasource.druid.testOnBorrow}")
	private boolean testOnBorrow;

	@Value("${spring.datasource.druid.testOnReturn}")
	private boolean testOnReturn;

	@Value("${spring.datasource.druid.allow}")
	private String allow;

	@Value("${spring.datasource.druid.deny}")
	private String deny;

	@Value("${spring.datasource.druid.loginUsername}")
	private String loginUsername;

	@Value("${spring.datasource.druid.loginPassword}")
	private String loginPassword;

	@Value("${spring.datasource.druid.resetEnable}")
	private String resetEnable;

	@Value("${spring.datasource.druid.exclusions}")
	private String exclusions;

	@Value("${spring.datasource.druid.mergeSql}")
	private boolean mergeSql;

	@Value("${spring.datasource.druid.slowSqlMillis}")
	private long lowSqlMillis;

	@Value("${spring.datasource.druid.logSlowSql}")
	private boolean logSlowSql;

	@Value("${spring.datasource.druid.filters}")
	private String filters;

	@Value("${spring.datasource.druid.connectionProperties:}")
	private String connectionProperties;

	@Value("${spring.datasource.druid.poolPreparedStatements}")
	private boolean poolPreparedStatements ;

	@Value("${spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize}")
	private Integer maxPoolPreparedStatementPerConnectionSize ;

}

MyBatis

Shiro


标题:SpringBoot基础—必知必会
作者:Hefery
地址:http://hefery.icu/articles/2022/03/10/1646854198583.html