# 自动配置

# Bean 加载方式

# Bean 的加载方式(一)

# XML 声明 bean

<?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="com.baozi.bean.Cat"/>
    <!--  xml 方式声明第三方开发的 Bean  -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
</beans>

# Bean 的加载方式(二)

# XML + 注解方式声明 bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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="com.baozi.bean,com.baozi.config"/>
</beans>
  • 使用 @Component 及其衍生注解 @Controller @Service @Repository 定义 bean
@Service
public class BookServiceImpl implements BookService {
}
  • 使用 @Bean 定义第三方 bean,并将所有类定义为配置类或 Bean
//@Component
@Configuration
public class Dbconfig {
    @Bean
    public DruidDataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }
}

# Bean 的加载方式(三)

# 注解方式声明配置类

@Configuration
// @Configuration 如果不用于被扫描可以省略
@ComponentScan({"com.baozi.bean","com.baozi.config"})
public class SpringConfig {
    @Bean
    public DruidDataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }
}

# Bean 的加载方式 —— 扩展 1

  • 初始化实现 FactoryBean 接口的类,实现对 bean 加载到容器之前的批处理操作
public class DogFactoryBean implements FactoryBean<Dog> {
    @Override
    public Dog getObject() throws Exception {
        return new Dog();
    }
    @Override
    public Class<?> getObjectType() {
        return Dog.class;
    }
    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }
}
@ComponentScan({"com.baozi.bean","com.baozi.config"})
public class SpringConfig3 {
    @Bean
    public DogFactoryBean dog(){
        return new DogFactoryBean();
    }
}

# Bean 的加载方式 —— 扩展 2

@Configuration
@ComponentScan("com.baozi")
@ImportResource("applicationContext1.xml")
public class SpringConfig32 {
}

# Bean 的加载方式 —— 扩展 3

  • 使用 proxyBeanMethods=true 可以保证调用此方法得到的对象是从容器中获取的而不是重新创建的
//proxyBeanMethods 值为 true 为单例 false 为多例
@Configuration(proxyBeanMethods = false)
public class SpringConfig33 {
    @Bean
    public Cat cat(){
        return new Cat();
    }
}
public class App33 {
    public static void main(String[] args) {
        ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig33.class);
        SpringConfig33 springConfig33 = app.getBean("springConfig33", SpringConfig33.class);
        System.out.println(springConfig33.cat());
        System.out.println(springConfig33.cat());
        System.out.println(springConfig33.cat());
    }
}

# Bean 的加载方式(四)

  • 使用 @Import 注解导入要注入的 bean 对应的字节码
@Import({Dog.class})
public class SpringConfig4 {
}
  • 被导入的 bean 无需声明为 bean
public class Dog {
}

# Bean 的加载方式(五)

  • 使用上下文对象在容器初始化完毕后注入 bean
public class App5 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig4.class);
        app.register(Mouse.class);
        String[] names = app.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }
}

# Bean 的加载方式(六)

  • 导入实现 ImportSelector 接口的类,实现对导入源的编程处理
public class MyinportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Configuration");
        if (flag){
            return new String[]{"com.baozi.bean.Cat"};
        }
        return new String[]{"com.baozi.bean.Dog","com.baozi.bean.Cat"};
    }
}

# Bean 的加载方式(七)

  • 导入实现了 ImportBeanDefinitionRegistrar 接口的类,通过 BeanDefinition 的注册器注册实名 bean,实现对容器中 bean 的裁定,例如对现有的 bean 的覆盖,进而达到不修改源代码的情况下更改实现的效果
public class MyRegister implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 使用元数据做判定
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Dog.class).getBeanDefinition();
        registry.registerBeanDefinition("yellow",beanDefinition);
    }
}

# Bean 的加载方式(八)

  • 导入实现了 BeanDefinitionRegistryPostProcessor 接口的类,通过 BeanDefinition 的注册器注册实名 bean,实现对容器中 bean 的最终裁定
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 加载控制

  • bean 的加载控制根据指定特定情况对 bean 进行选择性加载以达到适用于项目的目标

  • 根据任意条件确认是否加载 bean

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        try {
            Class<?> clazz = Class.forName("com.baozi.bean.Mouse");
            if (clazz != null){
                return new String[]{"com.baozi.bean.Cat"};
            }
        } catch (ClassNotFoundException e) {
//            throw new RuntimeException(e);
            return new String[0];
        }
        return null;
    }
}
  • 使用 @Conditional 注解的派生注解设置各种组合条件控制 bean 的加载

    • 未指定匹配类
    public class SpringConfig {
        @Bean
        @ConditionalOnClass(Mouse.class)
        public Cat tom(){
            return new Cat();
        }
    }

# Bean 依赖属性配置

  • 将业务功能 bean 运行需要的资源抽取成独立的属性类,设置读取配置文件信息
@Component
@ConfigurationProperties(prefix = "cartoon")
@Data
public class CartoonProperties {
}
  • 配置文件中使用固定格式为属性类注入数据
cartoon:
    cat:
        name: tom
        age: 3
    mouse:
        name: jerry
        age: 4
  • 定义业务 bean,通常使用 @Import 导入,解耦强制加载 bean
@Component
@Data
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse {
    private Cat cat;
    private Mouse mouse;
    private CartoonProperties cartoonProperties;
    public CartoonCatAndMouse(CartoonProperties cartoonProperties){
        this.cartoonProperties = cartoonProperties;
        cat = new Cat();
        cat.setName("tom");
        cat.setAge(18);
        mouse = new Mouse();
        mouse.setName("jerry");
        mouse.setAge(20);
    }
    public void play(){
        System.out.println(cat.getAge() + "" + cat.getName() + " 和 " + mouse.getAge() + "" + mouse.getName() + "打起来了");
    }
}
  • 使用 @EnableConfigurationProperties 注解设定使用属性类时加载 bean
@Component
@Data
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse {
    private Cat cat;
    private Mouse mouse;
    private CartoonProperties cartoonProperties;
    public CartoonCatAndMouse(CartoonProperties cartoonProperties){
        this.cartoonProperties = cartoonProperties;
        cat = new Cat();
        cat.setName("tom");
        cat.setAge(18);
        mouse = new Mouse();
        mouse.setName("jerry");
        mouse.setAge(20);
    }
    public void play(){
        System.out.println(cat.getAge() + "" + cat.getName() + " 和 " + mouse.getAge() + "" + mouse.getName() + "打起来了");
    }
}

# 自动配置原理

  1. 收集 Spring 开发者的编程习惯,整理开发过程中使用的常用技术列表 ——> 技术集 A
  2. 手机常用技术(技术集 A)的使用参数,整理开发过程中每个技术常用的设置列表 ——> 设置集 B
  3. 初始化 SpringBoot 基础环境,加载用户自定义的 bean 和导入的其他坐标,形成初始化环境
  4. 技术集 A 包含的所有技术都定义出来,在 Spring/SpringBoot 启动时默认全部加载
  5. 技术集 A 中具有使用条件的技术约定出来,设置成按条件加载,由开发者决定是否使用该技术(与初始化比对)
  6. 设置集 B 作为默认配置加载,减少开发者工作量
  7. 开放设置集 B 的配置覆盖接口,由开发者根据自身需要决定是否覆盖默认配置

# 变更自动配置

  • 自定义自动配置(META-INF/spring.factories)
// Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baozi.bean.CartoonCatAndMouse
  • 控制 SpringBoot 内置自动配置类加载
spring:
	autoconfigure:
		exclue:
			- org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
			- org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
@EnableAutoConfiguration(excludeName = "",exclude={})

# 自定义 starter

# 业务功能开发

public class IpCountService {
    private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
    @Autowired
    private HttpServletRequest httpServletRequest;
    public void count(){
        // 获取当前操作的 IP
        String ip = httpServletRequest.getRemoteAddr();
        // 根据 IP 地址从 Map 中取值,并递增
        Integer count = ipCountMap.get(ip);
        if(count == null) {
            ipCountMap.put(ip,1);
        } else {
            ipCountMap.put(ip,count + 1);
        }
    }
}

# 自动配置类

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

# 配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.baozi.autoconfig.IpAutoConfiguration

# 模拟调用

@RestController
@RequestMapping("/books")
public class BookController2 {
    @Autowired
    private IBookService bookService;
    @Autowired
    private IpCountService ipCountService;
    @GetMapping("{currentPage}/{pageSize}")
    public R getPage(@PathVariable Integer currentPage,@PathVariable Integer pageSize,Book book){
        ipCountService.count();
        IPage<Book> page = new Page<Book>(currentPage,pageSize);
        if(currentPage > page.getPages()){
            return new R(true,bookService.getPage(page,book));
        }
        return new R(true,bookService.getPage(page,book));
    }
}

# 设置定时任务

@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.println(String.format("|%s                  |%d     |",key,value));
    }
    System.out.println("|                    |       |");
    System.out.println("+--------------------+-------+");
}

# 定义属性类,加载对应属性

@ConfigurationProperties(prefix = "tools.ip")
public class IpProperties {
    /**
     * 日志显示周期
     */
    private Long cycle = 5L;
    /**
     * 是否周期内重置数据
     */
    private Boolean cycleReset = false;
    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;
    }
    /**
     * 日志输出模式 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;
        }
    }
}

# 设置加载 Properties 类为 bean

@EnableScheduling
@EnableConfigurationProperties(IpProperties.class)
public class IpAutoConfiguration {
    @Bean
    public IpCountService ipCountService(){
        return new IpCountService();
    }
}

# 根据模式切换数据

@Autowired
private IpProperties ipProperties;
@Scheduled(cron = "0/5 * * * * ?")
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("|                    |       |");
        System.out.println("+--------------------+-------+");
    }else if (ipProperties.getModel().equals(IpProperties.LogModel.SIMPLE.getValue())){
        System.out.println("      IP访问监控");
        System.out.println("+-----ip-address-----+");
        for (Map.Entry<String, Integer> entry : ipCountMap.entrySet()) {
            String key = entry.getKey();
            System.out.println(String.format("|%18s  |",key));
        }
        System.out.println("|                    |");
        System.out.println("+--------------------+");
    }
    if(ipProperties.getCycleReset()){
        ipCountMap.clear();
    }
}

# 配置信息

tools:
  ip:
    cycle: 5
    cycle-reset: true
    model: "simple"

# 自定义 bean 名称

@ConfigurationProperties(prefix = "tools.ip")
@Component("ipProperties")
public class IpProperties {
}

# 放弃使用配置属性创建 bean 方式,改为手工控制

@EnableScheduling
//@EnableConfigurationProperties(IpProperties.class)
@Import(IpProperties.class)
public class IpAutoConfiguration {
    @Bean
    public IpCountService ipCountService(){
        return new IpCountService();
    }
}

# 使用 #{beanName.attrName} 读取 bean 的属性

@Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?")
public void print(){
}

# 自定义拦截器

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

# 设置核心配置类,加载拦截器

@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(ipCountinterceptor()).addPathPatterns("/**");
    }
    @Bean
    public IpCountinterceptor ipCountinterceptor(){
        return new IpCountinterceptor();
    }
}

# 开启 yml 提示

# 导入配置处理器坐标

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
</dependency>

# 进行自定义提示功能开发

"hints": [
    {
      "name": "tools.ip.model",
      "values": [
        {
          "value": "detail",
          "description": "详细模式."
        },
        {
          "value": "simple",
          "description": "极简模式."
        }
      ]
    }
]

# 核心原理

# SpringBoot 启动流程

# 初始化各种属性,加载成对象

  • 读取环境属性(Environment)
  • 系统配置(spring.factories)
  • 参数(Arguments、application.yml)

# 创建 Spring 容器对象 ApplicationContext,加载各种配置

# 在容器创建前,通过监听器机制,应对不同阶段加载数据,更新数据的需求

# 容器初始化过程中追加各种功能,例如统计时间,输出日志等

# 容器类型选择

# 监听器

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);
# 监听器执行了对应的操作步骤

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Baozi 微信支付

微信支付

Baozi 支付宝

支付宝

Baozi 微信

微信