【SpringBoot】| Spring Boot 常见的底层注解剖析

红太狼 2024-03-17 19:11 135阅读 0赞

目录

一:Spring Boot 常见的底层注解

  1. 容器功能

1.1 组件添加

方法一:使用@Configuration注解+@Bean注解

方法二:使用@Configuration注解+@Import注解

方法三:使用@Configuration注解+@Conditional注解

1.2 原生xml配置文件引入

@ImportResource注解

1.3 配置绑定

方法一:@Component注解 + @ConfigurationProperties注解

方法二:@EnableConfigurationProperties注解 + @ConfigurationProperties注解

  1. 自动配置原理入门

2.1 引导加载自动配置类@SpringBootApplication

@EnableAutoConfiguration注解

2.2 修改默认配置

2.3 最佳实践

  1. 开发技巧

3.1 Lombok

3.2 dev-tools

3.3 Spring Initailizr(项目初始化向导)

图书推荐:《深入浅出Java虚拟机:JVM原理与实战》


一:Spring Boot 常见的底层注解

为了后面能够深入的掌握SpringBoot的自动配置原理,这里就专门总结一下SpringBoot的一些底层注解是怎样完成相关的功能!

1. 容器功能

User类

  1. package com.zl.bean;
  2. public class User {
  3. private String name;
  4. private int age;
  5. public User() {
  6. }
  7. public User(String name, int age) {
  8. this.name = name;
  9. this.age = age;
  10. }
  11. @Override
  12. public String toString() {
  13. return "User{" +
  14. "name='" + name + '\'' +
  15. ", age=" + age +
  16. '}';
  17. }
  18. public String getName() {
  19. return name;
  20. }
  21. public void setName(String name) {
  22. this.name = name;
  23. }
  24. public int getAge() {
  25. return age;
  26. }
  27. public void setAge(int age) {
  28. this.age = age;
  29. }
  30. }

Pet类

  1. package com.zl.bean;
  2. public class Pet {
  3. private String name;
  4. public Pet() {
  5. }
  6. public Pet(String name) {
  7. this.name = name;
  8. }
  9. public String getName() {
  10. return name;
  11. }
  12. public void setName(String name) {
  13. this.name = name;
  14. }
  15. @Override
  16. public String toString() {
  17. return "Pet{" +
  18. "name='" + name + '\'' +
  19. '}';
  20. }
  21. }

1.1 组件添加

回顾Spring:Spring如何把上面的两个类纳入Spring容器管理!

在resources下创建一个spring.xml,使用Bean标签

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="userBean" class="com.zl.bean.User">
  6. <property name="name" value="张三"/>
  7. <property name="age" value="18" />
  8. </bean>
  9. <bean id="petBean" class="com.zl.bean.Pet">
  10. <property name="name" value="Tom"/>
  11. </bean>
  12. </beans>

使用SpringBoot:使用SpringBoot中的注解来完成

方法一:使用@Configuration注解+@Bean注解

第一步:编写一个配置类MyConfig

  1. package com.zl.config;
  2. import com.zl.bean.Pet;
  3. import com.zl.bean.User;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. @Configuration // 告诉SpringBoot这是一个配置类
  7. public class MyConfig {
  8. // @Bean注解:给容器添加组件,以方法名作为组件的id,返回的值就是组件容器的实例
  9. // @Bean("u"),表示不再以方法为名,自己定义名字
  10. @Bean
  11. public User user(){
  12. return new User("张三",18);
  13. }
  14. @Bean
  15. public Pet pet(){
  16. return new Pet("Tom");
  17. }
  18. }

第二步:从容器中获取

  1. package com.zl;
  2. import com.zl.bean.User;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.context.ConfigurableApplicationContext;
  6. @SpringBootApplication
  7. public class App {
  8. public static void main( String[] args ) {
  9. // 返回的就是IOC容器
  10. ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
  11. // 从容器中获取(获取的是单列的)
  12. User user = run.getBean("user", User.class);
  13. System.out.println(user);
  14. }
  15. }

剖析:

(1)使用@Configuration注解表示配置类,配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单例的。

(2)配置类本身也是一个组件,也可以调用getBean方法获取到;从输出格式可以看出这个对象实际上是CJLIB代理对象。

(3)@Configuration注解有一个proxyBeanMethods属性:代理bean的方法,默认的值是true;表示外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例。

  1. package com.zl;
  2. import com.zl.bean.User;
  3. import com.zl.config.MyConfig;
  4. import org.springframework.boot.SpringApplication;
  5. import org.springframework.boot.autoconfigure.SpringBootApplication;
  6. import org.springframework.context.ConfigurableApplicationContext;
  7. @SpringBootApplication
  8. public class App {
  9. public static void main( String[] args ) {
  10. // 返回的就是IOC容器
  11. ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
  12. // 从容器中获取组件
  13. User user = run.getBean("user", User.class);
  14. System.out.println(user);// User{name='张三', age=18}
  15. // 配置类本身也是一个组件,可以获取到(实际上获取到的是一个代理代理对象)
  16. MyConfig bean = run.getBean(MyConfig.class);
  17. System.out.println(bean); // com.zl.config.MyConfig$$EnhancerBySpringCGLIB$$8f51ff9f@5488b5c5
  18. // 获取到配置类就可以通过外部的方式调用里面的组件方法
  19. User user1 = bean.user();
  20. User user2 = bean.user();
  21. // 外部无论调用多少次,调用的都是容器中的,而不是重新创建的
  22. // 本质上相等的原因就是@Configuration注解的proxyBeanMethods属性,结果为true
  23. // 就是代理对象创建方法,SpringBoot会检查这个组件是否在容器当中
  24. System.out.println(user1==user2); // true
  25. }
  26. }

Full模式与Lite模式

(1)Full模式:proxyBeanMethods = true,保证每个@Bean方法被调用多少次返回的组件都是单实例的;代理对象去调用。
(2)Lite模式:proxyBeanMethods = false,每个@Bean方法被调用多少次返回的组件都是新创建的。
注:有组件依赖必须使用Full模式;其他默认是否Lite模式!

例:假如现在User有一个宠物Pet,有组件依赖关系只能使用Full模式!

(1)重写User类:类中含有Pet属性,添加set和get方法,重写toString方法

  1. // 添加Pet属性
  2. private Pet pet;
  3. // 添加set和get方法
  4. public Pet getPet() {
  5. return pet;
  6. }
  7. public void setPet(Pet pet) {
  8. this.pet = pet;
  9. }
  10. // 重写toString
  11. @Override
  12. public String toString() {
  13. return "User{" +
  14. "name='" + name + '\'' +
  15. ", age=" + age +
  16. ", pet=" + pet +
  17. '}';
  18. }

(2)给User的pet属性赋值

  1. package com.zl.config;
  2. import com.zl.bean.Pet;
  3. import com.zl.bean.User;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. @Configuration // 告诉SpringBoot这是一个配置类
  7. public class MyConfig {
  8. // @Bean注解:给容器添加组件,以方法名作为组件的id,返回的值就是组件容器的实例
  9. // @Bean("u"),表示不再以方法为名,自己定义名字
  10. @Bean
  11. public User user(){
  12. User user = new User("张三", 18);
  13. // 给User中的pet属性赋值(通过下面的方法名)
  14. user.setPet(pet());
  15. return user;
  16. }
  17. @Bean
  18. public Pet pet(){
  19. return new Pet("Tom");
  20. }
  21. }

(3)验证User中的pet属性和原来的Pet是否是同一个

  1. package com.zl;
  2. import com.zl.bean.Pet;
  3. import com.zl.bean.User;
  4. import com.zl.config.MyConfig;
  5. import org.springframework.boot.SpringApplication;
  6. import org.springframework.boot.autoconfigure.SpringBootApplication;
  7. import org.springframework.context.ConfigurableApplicationContext;
  8. @SpringBootApplication
  9. public class App {
  10. public static void main( String[] args ) {
  11. // 返回的就是IOC容器
  12. ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
  13. // 获取User中的pet
  14. User user = run.getBean("user", User.class);
  15. Pet userPet = user.getPet();
  16. // 获取Pet
  17. Pet pet = run.getBean("pet", Pet.class);
  18. // 判断两者是否是同一个
  19. System.out.println(userPet == pet);
  20. }
  21. }

Full模式下(有依赖关系):是一个;Lite模式下(无依赖关系):不是同一个!

配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断,启动速度快!

配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式!

方法二:使用@Configuration注解+@Import**注解**

回顾:前面我们已经学习了@Componet、@Controller、@Service、@Repository、@Bean注解结合组件扫描@ComponentScan注解也可以完成纳入Spring容器管理!

在配置类上使用@Import注解,通过源码分析发现它的参数是一个Class类型的数据

  1. package org.springframework.context.annotation;
  2. import java.lang.annotation.Documented;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. @Target({ElementType.TYPE})
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Documented
  10. public @interface Import {
  11. Class<?>[] value();
  12. }

例如:把User类配置进去

此时已经把User纳入Spring容器管理,实际上此时已经加入两个User类,我们打印输出就可以发现!

  1. package com.zl.config;
  2. import com.zl.bean.Pet;
  3. import com.zl.bean.User;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.context.annotation.Import;
  7. @Configuration(proxyBeanMethods = true) // 告诉SpringBoot这是一个配置类
  8. // 使用Import注解
  9. @Import({User.class})
  10. public class MyConfig {
  11. // @Bean注解:给容器添加组件,以方法名作为组件的id,返回的值就是组件容器的实例
  12. // @Bean("u"),表示不再以方法为名,自己定义名字
  13. @Bean
  14. public User user(){
  15. User user = new User("张三", 18);
  16. // 给User中的pet属性赋值
  17. user.setPet(pet());
  18. return user;
  19. }
  20. @Bean
  21. public Pet pet(){
  22. return new Pet("Tom");
  23. }
  24. }

进行打印

实际上@Bean注解和@Import注解的作用相同:

第一个User类:@Bean注解,事实上key是方法的方法名——》user;

第二个User类:@Import注解,事实上key是本类的完整类名——》com.zl.bean.User;

  1. package com.zl;
  2. import com.zl.bean.Pet;
  3. import com.zl.bean.User;
  4. import com.zl.config.MyConfig;
  5. import org.springframework.boot.SpringApplication;
  6. import org.springframework.boot.autoconfigure.SpringBootApplication;
  7. import org.springframework.context.ConfigurableApplicationContext;
  8. @SpringBootApplication
  9. public class App {
  10. public static void main( String[] args ) {
  11. // 返回的就是IOC容器
  12. ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
  13. // 获取User组件Bean的名字
  14. String[] beanNamesForType = run.getBeanNamesForType(User.class);
  15. for (String s : beanNamesForType) {
  16. System.out.println(s); // com.zl.bean.User(完整类名) 和 user(方法名)
  17. }
  18. }
  19. }
#
方法三:使用@Configuration注解+@Conditional注解

按照条件装配:满足Conditional指定的条件,则进行组件注入!

Ctrl+h打开@Conditional注解的继承树:

828867d571264fd6b61977ff80615962.png

案例:现在要User中有tom组件,才让User注入

此时配置类中并没有注入tom

  1. package com.zl.config;
  2. import com.zl.bean.Pet;
  3. import com.zl.bean.User;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.context.annotation.Import;
  7. @Configuration(proxyBeanMethods = true) // 告诉SpringBoot这是一个配置类
  8. public class MyConfig {
  9. @Bean
  10. public User user(){
  11. User user = new User("张三", 18);
  12. // 给User中的pet属性赋值
  13. user.setPet(pet());
  14. return user;
  15. }
  16. // @Bean("tom")没有注入tom
  17. public Pet pet(){
  18. return new Pet("Tom");
  19. }
  20. }

容器中含有User但是没有Pet

  1. package com.zl;
  2. import com.zl.bean.Pet;
  3. import com.zl.bean.User;
  4. import com.zl.config.MyConfig;
  5. import org.springframework.boot.SpringApplication;
  6. import org.springframework.boot.autoconfigure.SpringBootApplication;
  7. import org.springframework.context.ConfigurableApplicationContext;
  8. import org.springframework.context.annotation.Conditional;
  9. @SpringBootApplication
  10. public class App {
  11. public static void main( String[] args ) {
  12. // 返回的就是IOC容器
  13. ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
  14. System.out.println(run.containsBean("user")); // true
  15. System.out.println(run.containsBean("tom")); // false
  16. }
  17. }

需求:因为User是包含Pet的,所以如果Pet没有,User也无法注入,此时就可以使用

注:当然这个注解也可以标注在方法上!

a6b7bbf1ce1649b987d6fc16dc81caf8.png

此时没有注入tom,所以两个结果都是false!

1.2 原生xml配置文件引入

@ImportResource注解

这个注解是用来引入外部的xml文件的,例如:导入Spring的配置文件!

spring.xml

注:此时只是一个xml,SpringBoot是无法识别的,没有注入成功

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="userBean" class="com.zl.bean.User">
  6. <property name="name" value="张三"/>
  7. <property name="age" value="18" />
  8. </bean>
  9. <bean id="petBean" class="com.zl.bean.Pet">
  10. <property name="name" value="Tom"/>
  11. </bean>
  12. </beans>

在配置类上使用@ImportResource注解进行导入

这个配置文件是在类文件下,所以格式是:classpath:xxx.xml!

816385ced8604a27abffc0778c2abea1.png

从容器中取出这两个Bean

  1. package com.zl;
  2. import com.zl.bean.Pet;
  3. import com.zl.bean.User;
  4. import com.zl.config.MyConfig;
  5. import org.springframework.boot.SpringApplication;
  6. import org.springframework.boot.autoconfigure.SpringBootApplication;
  7. import org.springframework.context.ConfigurableApplicationContext;
  8. import org.springframework.context.annotation.Conditional;
  9. @SpringBootApplication
  10. public class App {
  11. public static void main( String[] args ) {
  12. // 返回的就是IOC容器
  13. ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
  14. // 从容器中取出引入的外部xml的Bean
  15. User userBean = run.getBean("userBean", User.class);
  16. System.out.println(userBean);
  17. Pet petBean = run.getBean("petBean", Pet.class);
  18. System.out.println(petBean);
  19. }
  20. }

1.3 配置绑定

如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用?

方法一:@Component注解 + @ConfigurationProperties注解

注:@Component注解和@ConfigurationProperties注解都是写在当前要注入的类当中的!

@Component注解是把JavaBean纳入Spring容器管理,@ConfigurationPropertues注解是让JavaBean的属性与配置文件applicatio.properties中的属性建立关系,进行赋值!

application.properties属性配置

  1. // 端口号
  2. server.port=9999
  3. // 属性
  4. mycar.brand=比亚迪
  5. mycar.price=24

在Car类中引入属性

(1)首先在类中引入@Component注解,只有纳入容器中管理的组件,才可以使用SpringBoot提供的强大功能!

(2)@ConfigurationProperties注解的pefix属性(也就是value属性别名)指定application.properties属性配置中的前缀mycar即可。

  1. package com.zl.bean;
  2. import org.springframework.boot.context.properties.ConfigurationProperties;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. @ConfigurationProperties(prefix = "mycar")
  6. public class Car {
  7. private String brand;
  8. private Integer price;
  9. @Override
  10. public String toString() {
  11. return "Car{" +
  12. "brand='" + brand + '\'' +
  13. ", price=" + price +
  14. '}';
  15. }
  16. public String getBrand() {
  17. return brand;
  18. }
  19. public void setBrand(String brand) {
  20. this.brand = brand;
  21. }
  22. public Integer getPrice() {
  23. return price;
  24. }
  25. public void setPrice(Integer price) {
  26. this.price = price;
  27. }
  28. }

编写Controller进行访问,看属性是否已经赋值上

  1. package com.zl.controler;
  2. import com.zl.bean.Car;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.annotation.ComponentScan;
  5. import org.springframework.stereotype.Controller;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.ResponseBody;
  8. import org.springframework.web.bind.annotation.RestController;
  9. // @Controller
  10. @RestController
  11. public class AppController {
  12. // @ResponseBody
  13. // 前面已经自动纳入Spring容器管理了,这里自动注入
  14. @Autowired
  15. Car car;
  16. @RequestMapping("/car")
  17. public Car car(){
  18. return car;
  19. }
  20. }

进行访问

1473c3825f0d469186995e964fe62dab.png

方法二:@EnableConfigurationProperties注解 + @ConfigurationProperties注解

注:@EnableConfigurationProperties注解是写在配置类当中的,@ConfigurationProperties注解是写在当前要注入的类当中的!

@EnableConfigurationProperties注解有两个作用:

①开启某个类的配置绑定功能;

②把某个类这个组件自动注入到容器中去;(相当于@Component注解的功能)

要注入的Car类

  1. package com.zl.bean;
  2. import org.springframework.boot.context.properties.ConfigurationProperties;
  3. import org.springframework.stereotype.Component;
  4. // @Component
  5. @ConfigurationPropertie s(prefix = "mycar")
  6. public class Car {
  7. private String brand;
  8. private Integer price;
  9. @Override
  10. public String toString() {
  11. return "Car{" +
  12. "brand='" + brand + '\'' +
  13. ", price=" + price +
  14. '}';
  15. }
  16. public String getBrand() {
  17. return brand;
  18. }
  19. public void setBrand(String brand) {
  20. this.brand = brand;
  21. }
  22. public Integer getPrice() {
  23. return price;
  24. }
  25. public void setPrice(Integer price) {
  26. this.price = price;
  27. }
  28. }

配置类上的@EnableConfigurationProperties注解

  1. package com.zl.config;
  2. import com.zl.bean.Car;
  3. import com.zl.bean.Pet;
  4. import com.zl.bean.User;
  5. import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
  6. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import org.springframework.context.annotation.Import;
  10. import org.springframework.context.annotation.ImportResource;
  11. @Configuration(proxyBeanMethods = true)
  12. // 参数是一个Class类型
  13. @EnableConfigurationProperties(Car.class)
  14. public class MyConfig {
  15. @Bean
  16. public User user(){
  17. User user = new User("张三", 18);
  18. // 给User中的pet属性赋值
  19. user.setPet(pet());
  20. return user;
  21. }
  22. @Bean("tom")
  23. public Pet pet(){
  24. return new Pet("Tom");
  25. }
  26. }

进行访问

1473c3825f0d469186995e964fe62dab.png

#

2. 自动配置原理入门

2.1 引导加载自动配置类@SpringBootApplication

在启动类中有一个核心注解@SpringBootApplication,这个注解是一个复合注解!

①@SpringBootConfiguration注解本质上就是@Configuration注解,表明这是一个配置类;

②@ComponentScan注解就是Spring中学习的组件扫描;

③@EnableAutoConfiguration注解是最核心的注解,下面详细进行分析。

  1. @SpringBootConfiguration
  2. @EnableAutoConfiguration
  3. @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  4. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  5. public @interface SpringBootApplication{
  6. }
@EnableAutoConfiguration注解

@EnableAutoConfiguration注解也是一个复合注解,由@AutoConfigurationPackage和@Import注解组成!

:@AutoConfigurationPackage是导入当前配置类的包及其子包下的所有类到容器;@Import是导入组件到容器!

  1. @AutoConfigurationPackage
  2. @Import(AutoConfigurationImportSelector.class)
  3. public @interface EnableAutoConfiguration {}

(1) @AutoConfigurationPackage注解

翻译为自动配置包,本质上是一个@Import注解:给容器导入一个组件,这个组件是Register

  1. package org.springframework.boot.autoconfigure;
  2. import java.lang.annotation.Documented;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Inherited;
  5. import java.lang.annotation.Retention;
  6. import java.lang.annotation.RetentionPolicy;
  7. import java.lang.annotation.Target;
  8. import org.springframework.context.annotation.Import;
  9. @Target({ElementType.TYPE})
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @Documented
  12. @Inherited
  13. @Import({AutoConfigurationPackages.Registrar.class})
  14. public @interface AutoConfigurationPackage {
  15. String[] basePackages() default {};
  16. Class<?>[] basePackageClasses() default {};
  17. }

通过源码发现这个Register中有两个方法,通过方法区批量导入组件(一个个导太麻烦了)

  1. static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  2. Registrar() {
  3. }
  4. // 批量注册
  5. //
  6. public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  7. AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
  8. }
  9. public Set<Object> determineImports(AnnotationMetadata metadata) {
  10. return Collections.singleton(new PackageImports(metadata));
  11. }
  12. }

①AnnotationMetadata metadata是注解的原信息,表示注解是标注在哪里;通过打断点就可以得到是在:com.zl.App类上

7fc76e1f8f9148ac93ae1d2a24ef7677.png

②new PackageImports后面表示通过注解的原信息拿到包名去导入整个包,在这里的包名就是com.zl。通过(new PackageImports(metadata)).getPackageNames()这段代码计算出来。

83dffee5168e4a928431c1e980aaf062.png

总结@AutoConfigurationPackage注解:就是把一个包下的所有内容批量注入到容器;这也就解释了在启动类的包或者子包下的类都会被自动注入进去!

(2)@Import注解

@Import注解前面已经学习过,这是导入某个组件,这里就是把AutoConfigurationImportSelector这个类纳入容器的管理;所以这里需要分析这个类的作用!

通过分析AutoConfigurationImportSelector这个类的源码发现:

是利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件

  1. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  2. if (!this.isEnabled(annotationMetadata)) {
  3. return NO_IMPORTS;
  4. } else {
  5. AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
  6. return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  7. }
  8. }

在getAutoConfigurationEntry实现上打断点,进行调试

①最终返回的是封装后的configurations,当调试走到configurations时发现里面有127个组件,这些都是需要导入容器当中的;实际上就是调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类!

②再次查看getCandidateConfigurations方法(获取候选配置)的源码发现是通过这个方法得到所有的组件。更详细点是从META-INF/spring.factories位置来加载一个文件,默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件!

79334bb8ccfa49f088b2a1a0911a66d5.png

总结@Import注解:SpringBoot一启动,就给容器加载这127个配置类(这127个配置类实际上是在配置文件写死的);虽然127个场景的所有自动配置启动的时候默认全部加载,但是具体生效还是没生效,需要按需开启

注:这里的按需开启,实际上使用的就是使用@Conditional注解按照条件装配:满足Conditional指定的条件,则进行组件注入!

2.2 修改默认配置

SpringBoot默认会在底层配好所有的组件;但是如果用户自己配置了以用户的优先!

实际上是底层是通过@ConditionalOnMissingBean这个注解来实现的:只有你没有配置某个Bean,SpringBoot才会帮你配置。

  1. // 底层大量的这样代码
  2. @Bean
  3. @ConditionalOnMissingBean
  4. public CharacterEncodingFilter characterEncodingFilter() {
  5. }

总结:

(1)SpringBoot先加载所有的自动配置类 ;绑定xxxxxAutoConfiguration到容器当中。

(2)每个自动配置类按照条件进行生效;默认都会绑定配置文件指定的值,xxxxProperties里面拿,xxxProperties和配置文件进行了绑定。

(3)生效的配置类就会给容器中装配很多组件,只要容器中有这些组件,相当于这些功能就有了。

(4)只要用户有自己配置的组件,就以用户的优先。

以HttpEncodingAutoConfiguration字符编码为例:

对于经常需要改变的值,例如字符编码从UTF-8改成GBK:会利用@EnableConfigurationProperties(ServerProperties.class)注解把ServerProperties类绑定到容器,然后对于properties的值就是从ServerProperties类中取出来的,后面在进行修改

52ea9ea8e5c2476a8acebe72e5d18afb.png

ServerProperties类与配置文件application绑定

7cfa50cd53444c1ca73e747baca64255.png

所以对于定制化配置实际上有两种方法:

(1)用户直接自己@Bean替换底层的组件;

  1. // 在配置类中去自己写,替换
  2. @Bean
  3. public CharacterEncodingFilter characterEncodingFilter() {
  4. return null;
  5. }

(2)用户去看这个组件是获取的配置文件什么值就去修改。

  1. server.port=9999
  2. mycar.brand=比亚迪
  3. mycar.price=24
  4. // 通过属性配置文件application.properties进行更改
  5. // server.servlet.encoding就是上述注解中prefix指定的前缀
  6. server.servlet.encoding.charset=GBK

自动配置的核心原理:从xxxAutoConfiguration中导进来很多的组件——-》组件从xxxProperties中获取值——》xxxProperties又从application.properties配置文件中获取!

小试牛刀:改缓存里面的配置

第一步:找到缓存的类xxxAutoConfiguration(CacheAutoConfiguration),然后找到xxxProperties(CacheProperties)

45ee04aadaa94607a3e3663ec9f27766.png

第二步:点进去CacheProperties类,与配置文件的spring.cache前缀进行绑定

169128ed81b2497c898c40a1396f6189.png

第三步:在配置文件application.properties中使用spring.cache进行修改缓存的信息

fd812ecdbe6a489f9a8f83c8317a5c5b.png

2.3 最佳实践

第一步:引入场景依赖

通过官方文档可以查看:Developing with Spring Boot

fe733aeb58a74b5ab69f167ae6a936fe.png

第二步:查看自动配置了哪些(选做),实际上是底层的原理,看配置是否生效

第一种方法:自己分析,引入场景对应的自动配置一般都生效了

导入spring-boot-starter-web依赖后,关于Web的场景都会被导入!

①找到spring-boot-autoconfigure的jar包

9183755b94bd4f77ae0d025dfdc0dc5b.png

②找到对应的web包小的类进行分析

3b9a05baa50b4bdb9304c0a6142cfd19.png

第二种方法:配置文件application.properties中写debug=true开启自动配置报告

  1. // 默认值是false
  2. debug=true

启动:Negative(不生效),Positive(生效)

476e871769f54290bd98b1d60b2a4f9a.png

第三步:是否需要修改

例如:连接数据库的信息

方法一:参照文档修改配置项:Common Application Properties

9da8b9138ac64137a31bdbc337043220.png

方法二: 自己分析,xxxxProperties绑定了配置文件的哪些前缀

643fd48b97104eecb34247a503991a4d.png

再例如:自己定义或者替换组件

使用@Bean注解和@Component注解进行替换——用户配置生效优先原则!

3. 开发技巧

3.1 Lombok

Lombok插件是可以简化JavaBean开发的,例如:setter和getter方法、构造方法、toString方法等;在程序编译时自动生成这些!

第一步:引入Lombok的依赖

  1. <dependency>
  2. <groupId>org.projectlombok</groupId>
  3. <artifactId>lombok</artifactId>
  4. </dependency>

第二步:在软件市场进行安装

注:对于高版本的IDEA已经集成了这个插件,就不需要安装了!

a2b86d5a0f2649579ea049d9ecc6b698.png

此时的JavaBean形式

  1. package com.zl.bean;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import lombok.ToString;
  6. import org.springframework.boot.context.properties.ConfigurationProperties;
  7. import org.springframework.stereotype.Component;
  8. @Component
  9. @ConfigurationProperties(prefix = "myfriend")
  10. // 生成setter and getter
  11. @Data
  12. // 生成toString
  13. @ToString
  14. // 生成有参和无参构造
  15. @AllArgsConstructor
  16. @NoArgsConstructor
  17. // 生成equals和hashcode
  18. @EqualsAndHashCode
  19. public class Person {
  20. private String name;
  21. private int age;
  22. }

此时进行访问

注:对于Lombok有一个@Slf4j注解,表示引入日志!

  1. package com.zl.controler;
  2. import com.zl.bean.Car;
  3. import com.zl.bean.Person;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. @RestController
  9. @Slf4j // 引入日志信息
  10. public class AppController {
  11. @Autowired
  12. Person person;
  13. @RequestMapping("/person")
  14. public Person person(){
  15. log.info("请求进来了....................");
  16. return person;
  17. }
  18. }

发出请求访问时,控制器会打印日志信息

6ae120ccd51d42ffafa2aadcc547e8da.png

3.2 dev-tools

打开文档,找到Developer Tools,按照步骤完成:Developing with Spring Boot

第一步:引入依赖

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

第二步:以后项目或者页面更改,按Ctrl+F9就可以生效

3.3 Spring Initailizr(项目初始化向导)

https://start.spring.io,使用国外的地址,必须联网

①新建项目

2bddd03e0d7d4204b4d96cad630ae26b.png

②设置基本信息

f3da0d9eff644f049509f939f0e2184f.png

③选择依赖的列表

f43e5210809d4a3890c354b4ed81b242.png

④项目的结构

dbf68828b31e4b54a3f844522232e53a.png

⑤生成的pom.xml配置

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <!--SpringBoot项目的父项目-->
  6. <parent>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-parent</artifactId>
  9. <version>3.0.3</version>
  10. <relativePath/>
  11. </parent>
  12. <!--当前项目的gav坐标-->
  13. <groupId>com.zl</groupId>
  14. <artifactId>study-springboot-002</artifactId>
  15. <version>0.0.1-SNAPSHOT</version>
  16. <name>study-springboot-002</name>
  17. <description>study-springboot-002</description>
  18. <!--JDK的版本-->
  19. <properties>
  20. <java.version>1.8</java.version>
  21. </properties>
  22. <dependencies>
  23. <!--web依赖,版本号就是父的版本号-->
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-web</artifactId>
  27. </dependency>
  28. <!--单元测试-->
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-starter-test</artifactId>
  32. <scope>test</scope>
  33. </dependency>
  34. </dependencies>
  35. <build>
  36. <plugins>
  37. <!--maven的插件,打包用的-->
  38. <plugin>
  39. <groupId>org.springframework.boot</groupId>
  40. <artifactId>spring-boot-maven-plugin</artifactId>
  41. </plugin>
  42. </plugins>
  43. </build>
  44. </project>

图书推荐:深入浅出Java**虚拟机:JVM原理与实战》**

参与方式:

本次送书 2 本!
活动时间:截止到 2023-07-20 00:00:00。

抽奖方式:利用程序进行抽奖。

参与方式:关注博主(只限粉丝福利哦)、点赞、收藏,评论区随机抽取,最多三条评论!

Java虚拟机核心技术一本通:通过实战案例+执行效果图+核心代码,剖析探索JVM核心底层原理,强化推动JVM优化落地,手把手教你吃透Java虚拟机深层原理!

推荐理由

系统:全书内容层层递进,深入浅出,手把手教你吃透JVM虚拟机核心技术

深入:剖析探索JVM核心底层原理,强化推动JVM优化落地

实战:原理与实践相结合,懂理论,能落地,实战化案例精准定位技术细节

资源:附赠全书案例源代码,知其然更知其所以然,快速上手不用愁

内容简介

本书主要以 Java 虚拟机的基本特性及运行原理为中心,深入浅出地分析 JVM 的组成结构和底层实现,介绍了很多性能调优的方案和工具的使用方法。最后还扩展介绍了 JMM 内存模型的实现原理和 Java 编译器的优化机制,让读者不仅可以学习 JVM 的核心技术知识,还能夯实 JVM 调优及代码优化的技术功底。

本书适合已具有一定 Java 编程基础的开发人员、项目经理、架构师及性能调优工程师参考阅读,同时,本书还可以作为广大职业院校、计算机培训班相关专业的教学参考用书。

7104ca0a7a1746fba44d448ff34ed924.png

京东购买链接:《深入浅出Java虚拟机:JVM原理与实战》(李博)【摘要 书评 试读】- 京东图书

发表评论

表情:
评论列表 (有 0 条评论,135人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Spring Boot底层原理

    标注了@SpringBootApplication注解的是程序主入口类和主配置类,它是一个组合注解,包含以下几个主要的注解。因为这个时候,我们在单元测试中,并没有把spr...