# 自动配置
# 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"> |
| |
| <bean id="cat" class="com.baozi.bean.Cat"/> |
| |
| <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 |
| "> |
| |
| |
| <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
| |
| @Configuration |
| public class Dbconfig { |
| @Bean |
| public DruidDataSource getDataSource(){ |
| DruidDataSource ds = new DruidDataSource(); |
| return ds; |
| } |
| } |
# Bean 的加载方式(三)
# 注解方式声明配置类
| @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 可以保证调用此方法得到的对象是从容器中获取的而不是重新创建的
| |
| @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 的加载方式(五)
| 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 加载控制
| 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) { |
| |
| return new String[0]; |
| } |
| return null; |
| } |
| } |
# 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() + "打起来了"); |
| } |
| } |
# 自动配置原理
- 收集 Spring 开发者的编程习惯,整理开发过程中使用的常用技术列表 ——> 技术集 A
- 手机常用技术(技术集 A)的使用参数,整理开发过程中每个技术常用的设置列表 ——> 设置集 B
- 初始化 SpringBoot 基础环境,加载用户自定义的 bean 和导入的其他坐标,形成初始化环境
- 将技术集 A 包含的所有技术都定义出来,在 Spring/SpringBoot 启动时默认全部加载
- 将技术集 A 中具有使用条件的技术约定出来,设置成按条件加载,由开发者决定是否使用该技术(与初始化比对)
- 将设置集 B 作为默认配置加载,减少开发者工作量
- 开放设置集 B 的配置覆盖接口,由开发者根据自身需要决定是否覆盖默认配置
# 变更自动配置
- 自定义自动配置(META-INF/spring.factories)
| // Auto Configure |
| org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
| com.baozi.bean.CartoonCatAndMouse |
| 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(){ |
| |
| String ip = httpServletRequest.getRemoteAddr(); |
| |
| 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 |
| |
| @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);
# 监听器执行了对应的操作步骤