1. 自动配置工作流程

1.1 bean的加载方式

  1. xml+<bean>(配置文件)
  2. xml:context+注解(@Component+4个@Bean)(注解扫描)
  3. 配置类+扫描+注解(@Component+4个@Bean)(配置类)
    • @Bean定义FactoryBean接口(可以在变成bean之前做点事)
    • @ImportResource(导入配置)
    • @Configuration注解的proxyBeanMethods属性(代理对象,默认多个)
  4. @Import导入bean的类(在配置类上注入class)
    • @Import导入配置类(在配置类上注入class)
  5. AnnotationConfigApplicationContext调用register方法(容器创建之后注册bean)
  6. @Import导入ImportSelector接口(选择 bean 的方式)
  7. @Import导入ImportBeanDefinitionRegistrar接口(控制 bean 的相关属性)
  8. @Import导入BeanDefinitionRegistryPostProcessor接口(bean的最终裁决)

最初级的bean的加载方式其实可以直击spring管控bean的核心思想,就是提供类名,然后spring就可以管理了

所以第一种方式就是给出bean的类名,至于内部嘛就是反射机制加载成class

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--xml方式声明自己开发的bean-->
<bean id="cat" class="Cat"/>
<bean class="Dog"/>

<!--xml方式声明第三方开发的bean-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
<bean class="com.alibaba.druid.pool.DruidDataSource"/>
</beans>
1
2
3
4
5
6
7
8
9
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationCOntext1.xml");
// Object cat = ctx.getBean("cat");
// System.out.println(cat);
// Dog dog = ctx.getBean(Dog.class);
// System.out.println(dog);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

由于方式一种需要将spring管控的bean全部写在xml文件中,对于程序员来说非常不友好

所以就有了第二种方式。哪一个类要受到spring管控加载成bean,就在这个类的上面加一个注解,还可以顺带起一个bean的名字(id)。

这里可以使用的注解有@Component以及三个衍生注解@Service、@Controller、@Repository。

1
2
3
@Component("tom")
public class Cat {
}

由于我们无法在第三方提供的技术源代码中去添加上述4个注解

因此当你需要加载第三方开发的bean的时候可以使用下列方式定义注解式的bean

@Bean定义在一个方法上方,当前方法的返回值就可以交给spring管控

记得这个方法所在的类一定要定义在@Component修饰的类中

1
2
3
4
5
6
7
8
@Component
public class DbConfig {
@Bean
public DruidDataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!--指定扫描加载bean的位置-->
<context:component-scan base-package="cn.jyw.bean,cn.jyw.config"/>
</beans>
1
2
3
4
5
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationCOntext2.xml");
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

使用java类替换掉xml的配置

定义一个类并使用@ComponentScan替代原始xml配置中的包扫描这个动作,其实功能基本相同

1
2
3
4
5
6
7
@ComponentScan({"cn.jyw.bean","cn.jyw.config"})
public class SpringConfig3 {
@Bean
public DogFactoryBean dog(){
return new DogFactoryBean();
}
}
1
2
3
4
5
6
   //注解类声明配置而不是使用配置文件    
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

使用FactroyBean接口

造出来的bean并不是DogFactoryBean,而是Dog

可以在对象初始化前做一些事情,下例中的注释位置就是让你自己去扩展要做的其他事情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DogFactoryBean implements FactoryBean<Dog> {
@Override
public Dog getObject() throws Exception {
Dog d = new Dog();
//.........
return d;
}
@Override
public Class<?> getObjectType() {
return Dog.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
1
2
3
4
5
6
7
@ComponentScan({"cn.jyw.bean","cn.jyw.config"})
public class SpringConfig3 {
@Bean
public DogFactoryBean dog(){
return new DogFactoryBean();
}
}

注解格式导入XML格式配置的bean

@ImportResource,在配置类上直接写上要被融合的xml配置文件名

就可以融合配置文件

1
2
3
4
@Configuration
@ImportResource("applicationContext1.xml")
public class SpringConfig32 {
}
1
2
3
4
5
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig32.class);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

proxyBeanMethods属性

​ 前面的例子中用到了@Configuration这个注解,当我们使用AnnotationConfigApplicationContext加载配置类的时候,配置类可以不添加这个注解。但是这个注解有一个更加强大的功能,它可以保障配置类中使用方法创建的bean的唯一性。为@Configuration注解设置proxyBeanMethods属性值为true即可,由于此属性默认值为true,所以很少看见明确书写的,除非想放弃此功能。

1
2
3
4
5
6
7
@Configuration(proxyBeanMethods = true)
public class SpringConfig33 {
@Bean
public Cat cat(){
return new Cat();
}
}

​ 下面通过容器再调用上面的cat方法时,得到的就是同一个对象了。注意,必须使用spring容器对象调用此方法才有保持bean唯一性的特性。

1
2
3
4
5
6
7
8
9
10
11
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig33.class);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
System.out.println("-------------------------");
SpringConfig33 springConfig33 = ctx.getBean("springConfig33", SpringConfig33.class);
System.out.println(springConfig33.cat());
System.out.println(springConfig33.cat());
System.out.println(springConfig33.cat());
}

使用@Import注解一种精准制导的加载方式

直接指定类

1
2
3
@Import({Dog.class,DbConfig.class})
public class SpringConfig4 {
}
1
2
3
4
5
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

前面介绍的加载bean的方式都是在容器启动阶段完成bean的加载,下面这种方式就比较特殊了,可以在容器初始化完成后手动加载bean。通过这种方式可以实现编程式控制bean的加载。

1
2
3
4
5
6
7
8
      //此时不能用父类来接收
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//上下文容器对象已经初始化完毕后,手工加载bean
ctx.register(Mouse.class);
//后加载的会覆盖前面加载的
ctx.registerBean("tom", Cat.class,0);
ctx.registerBean("tom", Cat.class,1);
ctx.registerBean("tom", Cat.class,2);

容器初始化过程中进行控制

一种控制bean加载的方式,或者说是选择bean的方式

1
2
3
4
5
6
7
8
9
10
11
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
//各种条件的判定,判定完毕后,决定是否装载指定的bean
boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Configuration");
if(flag){
return new String[]{"cn.jyw.bean.Dog"};
}
return new String[]{"cn.jyw.bean.Cat"};
}
}
1
2
3
4
5
@Configuration
//@ComponentScan(basePackages = "cn.jyw")
@Import(MyImportSelector.class)
public class SpringConfig6 {
}
1
2
3
4
5
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig6.class);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

spring中定义了一个叫做BeanDefinition的东西,它才是控制bean初始化加载的核心

BeanDefinition接口中给出了若干种方法,可以控制bean的相关属性

说个最简单的,创建的对象是单例还是非单例,在BeanDefinition中定义了scope属性就可以控制这个

1
2
3
4
5
6
7
8
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition =
BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();
registry.registerBeanDefinition("bookService",beanDefinition);
}
}

BeanDefinition意思是bean定义,Registry注册的意思,Post后置,Processor处理器,全称bean定义后处理器

对容器中的bean进行最终裁定

1
2
3
4
5
6
7
8
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanDefinition beanDefinition =
BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
registry.registerBeanDefinition("bookService",beanDefinition);
}
}

后面加载的会覆盖前面加载的bean, 但是继承BeanDefinitionRegistryPostProcessor的类有最终裁定权

1
2
3
@Import({BookServiceImpl1.class, MyPostProcessor.class, MyRegistrar2.class, MyRegistrar.class})
public class SpringConfig8 {
}
1
2
3
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig8.class);
BookSerivce bookService = ctx.getBean("bookService", BookSerivce.class);
bookService.check();

总结

  1. bean的定义由前期xml配置逐步演化成注解配置,本质是一样的,都是通过反射机制加载类名后创建对象,对象就是spring管控的bean
  2. @Import注解可以指定加载某一个类作为spring管控的bean,如果被加载的类中还具有@Bean相关的定义,会被一同加载
  3. spring开放出了若干种可编程控制的bean的初始化方式,通过分支语句由固定的加载bean转成了可以选择bean是否加载或者选择加载哪一种bean

1.2 bean的加载控制

企业级开发中不可能在spring容器中进行bean的饱和式加载的

应该是用什么技术就加载什么bean

所以在spring容器中,通过判定是否加载了某个类来控制某些bean的加载是一种常见操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
try {
Class<?> clazz = Class.forName("com.itheima.bean.Mouse");
if(clazz != null) {//如果对应于的类存在就加载bean
return new String[]{"com.itheima.bean.Cat"};
}
} catch (ClassNotFoundException e) {
// e.printStackTrace();
return new String[0];
}
return null;
}
}

注解方式

比如可以直接写(.class) 也可以写(name=”全路径名”)

注解 作用
@ConditionalOnClass 有某个class才加载bean
@ConditionalOnMissingClass 没有哪个class才加载bean
@ConditionalOnBean 有这个bean才加载
@ConditionalOnWebApplication 是Web环境才加载
@ConditionalOnNotWebApplication 不是Web环境才加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//写2个就是2个条件都成立,写多个就是多个条件都成立
@Bean
@ConditionalOnClass(name = "cn.jyw.bean.Wolf")
@ConditionalOnMissingClass("cn.jyw.bean.Mouse")
public Cat tom(){
return new Cat();
}
//判定是否加载了指定名称的bean
@Bean
@ConditionalOnBean(name="jerry")
public Cat tom(){
return new Cat();
}
//判定当前是否加载了mysql的驱动类,如果加载了,就创建一个Druid的数据源对象
@Bean
@ConditionalOnClass(name="com.mysql.jdbc.Driver")
public DruidDataSource dataSource(){
return new DruidDataSource();
}

1.3 bean的依赖属性配置管理

bean在运行的时候,实现对应的业务逻辑时有可能需要开发者提供一些设置值,有就是属性了

如果使用构造方法将参数固定,灵活性不足

需要灵活的配置

先定义配置文件

1
2
3
4
5
6
7
cartoon:
cat:
name: "图多盖洛"
age: 5
mouse:
name: "泰菲"
age: 1

定义一个封装属性的专用类,加载配置属性,读取对应前缀相关的属性值

1
2
3
4
5
6
@ConfigurationProperties(prefix = "cartoon")
@Data//需要get和set
public class CartoonProperties {
private Cat cat;
private Mouse mouse;
}

在使用的位置注入对应的配置即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@EnableConfigurationProperties(CartoonProperties.class)//在使用本类时指定某类加载配置文件
@Data
public class CartoonCatAndMouse{
private Cat cat;
private Mouse mouse;

private CartoonProperties cartoonProperties;
//如果配置文件指定了就使用配置文件,没有就使用设定的值
public CartoonCatAndMouse(CartoonProperties cartoonProperties){
this.cartoonProperties = cartoonProperties;
cat = new Cat();
cat.setName(cartoonProperties.getCat()!=null && StringUtils.hasText(cartoonProperties.getCat().getName()) ? cartoonProperties.getCat().getName() : "tom");
cat.setAge(cartoonProperties.getCat()!=null && cartoonProperties.getCat().getAge()!=null ? cartoonProperties.getCat().getAge() : 3);
mouse = new Mouse();
mouse.setName(cartoonProperties.getMouse()!=null && StringUtils.hasText(cartoonProperties.getMouse().getName()) ? cartoonProperties.getMouse().getName() : "jerry");
mouse.setAge(cartoonProperties.getMouse()!=null && cartoonProperties.getMouse().getAge()!=null ? cartoonProperties.getMouse().getAge() : 4);
}

public void play(){
System.out.println(cat.getAge()+"岁的"+cat.getName()+"和"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了");
}

建议在业务类上使用@EnableConfigurationProperties声明bean,这样在不使用这个类的时候,也不会无故加载专用的属性配置类CartoonProperties,减少spring管控的资源数量。

执行

1
2
3
4
5
6
7
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(App.class);
CartoonCatAndMouse bean = ctx.getBean(CartoonCatAndMouse.class);
bean.play();
}
}

总结

  1. bean的运行如果需要外部设置值,建议将设置值封装成专用的属性类* * * * Properties
  2. 设置属性类加载指定前缀的配置信息
  3. 在需要使用属性类的位置通过注解@EnableConfigurationProperties加载bean,而不要直接在属性配置类上定义bean,减少资源加载的数量,因需加载而不要饱和式加载。

1.4 自动配置原理

整体过程分为2个阶段

阶段一:准备阶段

  1. 收集Spring开发者的编程习惯,整理开发过程使用的常用技术列表——>(技术集A)

  2. 收集常用技术(技术集A)的使用参数,整理开发过程中每个技术的常用设置列表——>(设置集B)

阶段二:加载阶段

  1. 初始化SpringBoot基础环境,加载用户自定义的bean和导入的其他坐标,形成初始化环境
  2. 技术集A包含的所有技术都定义出来,在Spring/SpringBoot启动时默认全部加载
  3. 技术集A中具有使用条件的技术约定出来,设置成按条件加载,由开发者决定是否使用该技术(与初始化环境比对 )
  4. 设置集B作为默认配置加载(约定大于配置),减少开发者配置工作量
1
2
3
4
//指定配置文件地点
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
}

总结

  1. 先开发若干种技术的标准实现
  2. SpringBoot启动时加载所有的技术实现对应的自动配置类
  3. 检测每个配置类的加载条件是否满足并进行对应的初始化
  4. 切记是先加载所有的外部资源,然后根据外部资源进行条件比对

1.5 变更自动配置

自定义自动配置(META-INF/spring.factories) 自动bean化

1
2
3
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.jyw.bean.CartoonCatAndMouse

控制SpringBoot内置自动配置类加载

  1. 配置文件控制
1
2
3
4
5
spring:
autoconfigure:
exclude: #排除配置类
- org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
- org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
  1. 注解控制
1
@EnableAutoConfiguration(excludeName = "",exclude = {})

变更自动配置:去除tomcat自动配置(条件激活),添加jetty自动配置(条件激活)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--web起步依赖环境中,排除Tomcat起步依赖,匹配自动配置条件-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加Jetty起步依赖,匹配自动配置条件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>

总结

  1. 通过配置文件exclude属性排除自动配置
  2. 通过注解@EnableAutoConfiguration属性排除自动配置项
  3. 启用自动配置只需要满足自动配置条件即可
  4. 可以根据需求开发自定义自动配置项

2. 自定义starter开发

2.1 基本实现

业务功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class IpCountService {

private Map<String,Integer> ipCountMap = new HashMap<>();
@Autowired//当前的request对象的注入工作由使用当前starter的工程提供自动装配
private HttpServletRequest httpServletRequest;

public void count(){
//每次调用当前操作,就记录当前访问的IP,然后累加访问次数
//1.获取当前操作的IP地址
String ip = httpServletRequest.getRemoteAddr();
System.out.println("-----------------------------"+ip);
//2.根据IP地址从Map取值,并递增
ipCountMap.merge(ip, 1, Integer::sum);
}
}

自动配置类

1
2
3
4
5
6
public class IpAutoConfiguration {
@Bean
public IpCountService ipCountService(){
return new IpCountService();
}
}

配置

在resources/META-INF/spring.factories中

1
2
3
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.jyw.autoconfig.IpAutoConfiguration

使用

clean和install之后导入pom依赖即可

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class MyController {

@Autowired
private IpCountService ipCountService;

@GetMapping("/test/{id}")
public User test(@PathVariable Integer id) {
ipCountService.count();
}
}

2.2 开启定时任务功能

在自动配置上加上springBoot的内置定时任务

1
2
3
4
5
6
7
@EnableScheduling//开启定时任务
public class IpAutoConfiguration {
@Bean
public IpCountService ipCountService(){
return new IpCountService();
}
}

在Service中开始任务

1
2
3
4
5
6
7
8
9
10
11
@Scheduled(cron = "0/5 * * * * ?")
public void print(){
System.out.println(" IP访问监控");
System.out.println("+-----ip-address-----+--num--+");
for (Map.Entry<String, Integer> entry : ipCountMap.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.printf("|%18s |%5d |%n",key,value);
}
System.out.println("+--------------------+-------+");
}

2.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
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
//定义成bean
//因为使用EnableConfigurationProperties会导致注入的bean为全类名冲突
//定时器配置时会用到配置属性
@Component("ipProperties")
@ConfigurationProperties(prefix = "tools.ip")//记载配置文件
public class IpProperties {
/**
* 日志显示周期
*/
private Long cycle = 5L;

/**
* 是否周期内重置数据
*/
private Boolean cycleReset = false;

/**
* 日志输出模式 detail:详细模式 simple:极简模式
*/
private String model = LogModel.DETAIL.value;

public enum LogModel{
DETAIL("detail"),
SIMPLE("simple");
private String value;
LogModel(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}

public Long getCycle() {
return cycle;
}

public void setCycle(Long cycle) {
this.cycle = cycle;
}

public Boolean getCycleReset() {
return cycleReset;
}

public void setCycleReset(Boolean cycleReset) {
this.cycleReset = cycleReset;
}

public String getModel() {
return model;
}

public void setModel(String model) {
this.model = model;
}
}

自动配置类

1
2
3
4
5
6
7
8
9
10
11
@EnableScheduling//开启定时器
//如果不设定定时器可以用下面注解自动配置文件
//@EnableConfigurationProperties(IpProperties.class)
@Import(IpProperties.class)
public class IpAutoConfiguration {

@Bean
public IpCountService ipCountService(){
return new IpCountService();
}
}

服务类

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
    @Autowired
private IpProperties ipProperties;
// @Scheduled(cron = "0/5 * * * * ?")
//有就用配置,没有就设定5,但会有bug
// @Scheduled(cron = "0/${tools.ip.cycle:5} * * * * ?")
@Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?")
public void print(){

if(ipProperties.getModel().equals(IpProperties.LogModel.DETAIL.getValue())){
System.out.println(" IP访问监控");
System.out.println("+-----ip-address-----+--num--+");
for (Map.Entry<String, Integer> entry : ipCountMap.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(String.format("|%18s |%5d |",key,value));
}
System.out.println("+--------------------+-------+");
}else if(ipProperties.getModel().equals(IpProperties.LogModel.SIMPLE.getValue())){
System.out.println(" IP访问监控");
System.out.println("+-----ip-address-----+");
for (String key: ipCountMap.keySet()) {
System.out.println(String.format("|%18s |",key));
}
System.out.println("+--------------------+");
}

if(ipProperties.getCycleReset()){
ipCountMap.clear();
}
}

配置

定义在要使用的项目中

1
2
3
4
5
6
tools:
ip:
cycle: 10
cycleReset: false
model: "detail"

2.4 配置拦截器

定义拦截器

在启用start时能对每次访问都执行

1
2
3
4
5
6
7
8
9
10
11
public class IpCountInterceptor implements HandlerInterceptor {

@Autowired
private IpCountService ipCountService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ipCountService.count();
return true;
}
}

开启拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration//单例化
public class SpringMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加拦截器,设置拦截路径
registry.addInterceptor(ipCountInterceptor()).addPathPatterns("/**");
}

@Bean //拦截器实例化
public IpCountInterceptor ipCountInterceptor(){
return new IpCountInterceptor();
}

}

2.5 辅助功能开发(yml输入提示)

导入配置处理器坐标

这个坐标的作用是根据注释生成文件,但部分数据调整仍需手动

获得spring-configuration-metadata.json文件后放入yml同级即可

上线需要删掉此坐标

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

进行自定义提示功能开发

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
{
"groups": [
{
"name": "tools.ip",
"type": "cn.itcast.properties.IpProperties",
"sourceType": "cn.itcast.properties.IpProperties"
}
],
"properties": [
{
"name": "tools.ip.cycle",
"type": "java.lang.Long",
"description": "日志显示周期",
"sourceType": "cn.itcast.properties.IpProperties",
"defaultValue": 5
},
{
"name": "tools.ip.cycle-reset",
"type": "java.lang.Boolean",
"description": "是否周期内重置数据",
"sourceType": "cn.itcast.properties.IpProperties",
"defaultValue": false
},
{
"name": "tools.ip.model",
"type": "java.lang.String",
"description": "日志输出模式 detail:详细模式 simple:极简模式",
"sourceType": "cn.itcast.properties.IpProperties"
}
],
"hints": [
{
"name": "tools.ip.model",
"values": [
{
"value": "detail",
"description": "详细模式."
},
{
"value": "simple",
"description": "极简模式."
}
]
}
]
}

3. springboot程序启动流程

SpringBoot启动流程

  1. 初始化各种属性,加载成对象
    • 读取环境属性(Environment)
    • 系统配置(spring.factories)
    • 参数(Arguments、application.properties)
  2. 创建Spring容器对象ApplicationContext,加载各种配置
  3. 在容器创建前,通过监听器机制,应对不同阶段加载数据、更新数据的需求
  4. 容器初始化过程中追加各种功能,例如统计时间、输出日志等

监听器类型

  1. 在应用运行但未进行任何处理时,将发送 ApplicationStartingEvent。
  2. 当Environment被使用,且上下文创建之前,将发送 ApplicationEnvironmentPreparedEvent。
  3. 在开始刷新之前,bean定义被加载之后发送 ApplicationPreparedEvent。
  4. 在上下文刷新之后且所有的应用和命令行运行器被调用之前发送 ApplicationStartedEvent。
  5. 在应用程序和命令行运行器被调用之后,将发出 ApplicationReadyEvent,用于通知应用已经准备处理请求。
  6. 启动时发生异常,将发送 ApplicationFailedEvent。

源码分析

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
Springboot30StartupApplication【10】->SpringApplication.run(Springboot30StartupApplication.class, args);
SpringApplication【1332】->return run(new Class<?>[] { primarySource }, args);
SpringApplication【1343】->return new SpringApplication(primarySources).run(args);
SpringApplication【1343】->SpringApplication(primarySources)
# 加载各种配置信息,初始化各种配置对象
SpringApplication【266】->this(null, primarySources);
SpringApplication【280】->public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)
SpringApplication【281】->this.resourceLoader = resourceLoader;
# 初始化资源加载器
SpringApplication【283】->this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
# 初始化配置类的类名信息(格式转换)
SpringApplication【284】->this.webApplicationType = WebApplicationType.deduceFromClasspath();
# 确认当前容器加载的类型
SpringApplication【285】->this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
# 获取系统配置引导信息
SpringApplication【286】->setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
# 获取ApplicationContextInitializer.class对应的实例
SpringApplication【287】->setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
# 初始化监听器,对初始化过程及运行过程进行干预
SpringApplication【288】->this.mainApplicationClass = deduceMainApplicationClass();
# 初始化了引导类类名信息,备用
SpringApplication【1343】->new SpringApplication(primarySources).run(args)
# 初始化容器,得到ApplicationContext对象
SpringApplication【323】->StopWatch stopWatch = new StopWatch();
# 设置计时器
SpringApplication【324】->stopWatch.start();
# 计时开始
SpringApplication【325】->DefaultBootstrapContext bootstrapContext = createBootstrapContext();
# 系统引导信息对应的上下文对象
SpringApplication【327】->configureHeadlessProperty();
# 模拟输入输出信号,避免出现因缺少外设导致的信号传输失败,进而引发错误(模拟显示器,键盘,鼠标...)
java.awt.headless=true
SpringApplication【328】->SpringApplicationRunListeners listeners = getRunListeners(args);
# 获取当前注册的所有监听器
SpringApplication【329】->listeners.starting(bootstrapContext, this.mainApplicationClass);
# 监听器执行了对应的操作步骤
SpringApplication【331】->ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
# 获取参数
SpringApplication【333】->ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
# 将前期读取的数据加载成了一个环境对象,用来描述信息
SpringApplication【333】->configureIgnoreBeanInfo(environment);
# 做了一个配置,备用
SpringApplication【334】->Banner printedBanner = printBanner(environment);
# 初始化logo
SpringApplication【335】->context = createApplicationContext();
# 创建容器对象,根据前期配置的容器类型进行判定并创建
SpringApplication【363】->context.setApplicationStartup(this.applicationStartup);
# 设置启动模式
SpringApplication【337】->prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
# 对容器进行设置,参数来源于前期的设定
SpringApplication【338】->refreshContext(context);
# 刷新容器环境
SpringApplication【339】->afterRefresh(context, applicationArguments);
# 刷新完毕后做后处理
SpringApplication【340】->stopWatch.stop();
# 计时结束
SpringApplication【341】->if (this.logStartupInfo) {
# 判定是否记录启动时间的日志
SpringApplication【342】-> new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
# 创建日志对应的对象,输出日志信息,包含启动时间
SpringApplication【344】->listeners.started(context);
# 监听器执行了对应的操作步骤
SpringApplication【345】->callRunners(context, applicationArguments);
#
SpringApplication【353】->listeners.running(context);
# 监听器执行了对应的操作步骤