SpringBoot 学习笔记(四) - 原理篇

一、自动配置

1.1 bean加载方式

bean的加载方式1 - xml方式声明bean

导入依赖:

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.9</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.17</version></dependency>
</dependencies>

配置applicationContext1.xml

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

测试:

public class App1 {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml");//        Object cat = ctx.getBean("cat");//        System.out.println(cat); // com.ming.bean.Cat@59494225//        Dog dog = ctx.getBean(Dog.class);//        System.out.println(dog); // com.ming.bean.Dog@4566e5bdString[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);/*** cat* com.ming.bean.Dog#0* dataSource* com.alibaba.druid.pool.DruidDataSource#0* com.alibaba.druid.pool.DruidDataSource#1*/}}
}

bean的加载方式2 - XML+注解方式声明bean

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.ming.bean,com.ming.config"/>
</beans>
  • 使用@Component及其衍生注解@Controller 、@Service、@Repository定义bean
@Component("tom")
public class Cat {}@Controller("jerry")
public class Mouse {}@Service
public class BookServiceImpl01 implements BookService {@Overridepublic void check() {System.out.println("BookServiceImpl01 ..");}
}// ....
  • 使用@Bean定义第三方bean,并将所在类定义为配置类或Bean
@Configuration
public class DbConfig {/*** 加载第三方bean* @return*/@Beanpublic DruidDataSource dataSource(){DruidDataSource druidDataSource = new DruidDataSource();return druidDataSource;}
}

测试:

public class App2 {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext2.xml");String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);/*** tom* jerry* bookServiceImpl01* dbConfig* org.springframework.context.annotation.internalConfigurationAnnotationProcessor* org.springframework.context.annotation.internalAutowiredAnnotationProcessor* org.springframework.context.annotation.internalCommonAnnotationProcessor* org.springframework.context.event.internalEventListenerProcessor* org.springframework.context.event.internalEventListenerFactory* dataSource*/}}
}

bean的加载方式3 - 注解方式声明配置类

标注注解的类同上
配置类如下所示:

@ComponentScan({"com.ming.bean","com.ming.config"})
public class SpringConfig3 {}

如果通过扫描的方式加入(new AnnotationConfigApplicationContext(SpringConfig3.class)),
则SpringConfig3类上的 @Configuration可以省略

测试:

public class App3 {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);/*** org.springframework.context.annotation.internalConfigurationAnnotationProcessor* org.springframework.context.annotation.internalAutowiredAnnotationProcessor* org.springframework.context.annotation.internalCommonAnnotationProcessor* org.springframework.context.event.internalEventListenerProcessor* org.springframework.context.event.internalEventListenerFactory* springConfig3* tom* jerry* bookServiceImpl01* dbConfig* dataSource*/}}
}
扩展1:

初始化实现FactoryBean接口的类,实现对bean加载到容器之前的批处理操作

public class DogFactoryBean implements FactoryBean<Dog> {@Overridepublic Dog getObject() throws Exception {Dog dog = new Dog();// 进行dog对象的相关初始化return dog;}@Overridepublic Class<?> getObjectType() {return Dog.class;}@Overridepublic boolean isSingleton() {return false;}
}

使用方式:返回的不是DogFactoryBean对象,而是DogFactoryBean对象创建出来的Dog对象

@ComponentScan({"com.ming.bean","com.ming.config"})
public class SpringConfig3 {@Beanpublic DogFactoryBean dog(){return new DogFactoryBean();}
}

测试:

public class App3 {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}System.out.println(ctx.getBean("dog"));}
}
/*** org.springframework.context.annotation.internalConfigurationAnnotationProcessor* org.springframework.context.annotation.internalAutowiredAnnotationProcessor* org.springframework.context.annotation.internalCommonAnnotationProcessor* org.springframework.context.event.internalEventListenerProcessor* org.springframework.context.event.internalEventListenerFactory* springConfig3* tom* jerry* bookServiceImpl01* dbConfig* dataSource* dog* com.ming.bean.Dog@65d6b83b*/
扩展2:

如何在同时加载配置文件和配置类?(系统迁移)

@ImportResource({"applicationContext3.xml"})
public class SpringConfig32 {
}

思考:若配置文件1里面加载了cat,配置文件2里面也加载cat会发生什么?

如下所示:配置文件1

<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.ming.bean.Cat"><property name="name" value="tom1"/></bean>
</beans>

配置文件2

<?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.ming.bean.Cat"><property name="name" value="tom2"/></bean>
</beans>

配置类:

@ImportResource({"applicationContext3.xml","applicationContext32.xml"})
public class SpringConfig32 {
}

结果:与@ImportResource的导入顺序有关,配置文件2覆盖配置文件1的bean,另外 在配置类中声明Bean,也会被覆盖,优先级最低

public class App32 {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig32.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}System.out.println("--------------");Cat cat = (Cat) ctx.getBean("cat");System.out.println(cat.getName()); // tom2}
}
扩展3:
  • 使用proxyBeanMethods=true可以保障调用此方法得到的对象是从容器中获取的而不是重新创建的
@Configuration(proxyBeanMethods = true) // 默认为true
public class SpringConfig33 {@Beanpublic Cat cat(){System.out.println("cat init ...");return new Cat();}
}

测试:

public class App33 {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig33.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}System.out.println("--------------------");System.out.println(ctx.getBean("springConfig33")); ///*** 当Configuration注解的proxyBeanMethods属性为true(默认)时:* 打印结果为:com.ming.config.SpringConfig33$$EnhancerBySpringCGLIB$$1939a483@21a947fe* 从打印结果可以看出,这个对象是由CGLIB动态代理生成的代理对象* 改为false后:* 打印结果为:com.ming.config.SpringConfig33@2c039ac6*/SpringConfig33 springConfig33 = ctx.getBean("springConfig33", SpringConfig33.class);System.out.println(springConfig33.cat());System.out.println(springConfig33.cat());/*** 当proxyBeanMethods属性为true(默认)时:*  打印结果都为 com.ming.bean.Cat@1dd92fe2 ,同一个对象* 改为false后:*  打印结果不同:*  com.ming.bean.Cat@ed9d034*  com.ming.bean.Cat@6121c9d6*/}
}

bean的加载方式4- 使用@Import()注入bean

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

此形式可以有效的降低源代码与Spring技术的耦合度,在spring技术底层及诸多框架的整合中大量使用

测试:

public class App4 {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}System.out.println("------------------");System.out.println(ctx.getBean(Dog.class));}
}
/*** org.springframework.context.annotation.internalConfigurationAnnotationProcessor* org.springframework.context.annotation.internalAutowiredAnnotationProcessor* org.springframework.context.annotation.internalCommonAnnotationProcessor* org.springframework.context.event.internalEventListenerProcessor* org.springframework.context.event.internalEventListenerFactory* springConfig4* com.ming.bean.Dog* ------------------* com.ming.bean.Dog@9a7504c*/
扩展4:

除了可以使用@Import()注解注入普通的Bean外,还可以加载配置类,并且在配置类中声明的bean也生效

@Import({DbConfig.class})
public class SpringConfig42 {}
  • 配置类可以不加@Configuration注解
// @Configuration
public class DbConfig {@Beanpublic DruidDataSource dataSource(){DruidDataSource druidDataSource = new DruidDataSource();return druidDataSource;}
}

测试:

public class App42 {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig42.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}}
}
/*** org.springframework.context.annotation.internalConfigurationAnnotationProcessor* org.springframework.context.annotation.internalAutowiredAnnotationProcessor* org.springframework.context.annotation.internalCommonAnnotationProcessor* org.springframework.context.event.internalEventListenerProcessor* org.springframework.context.event.internalEventListenerFactory* springConfig42* com.ming.config.DbConfig* dataSource*/

bean的加载方式5 - 硬编码方式注入(register/registerBean方法)

  • 使用上下文对象在容器初始化完毕后注入bean
public class App5 {public static void main(String[] args) {AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig5.class);// 上下文容器对象已经初始化完毕后,手工加载bean。 - registerBean/registerctx.registerBean("tom", Cat.class);ctx.register(Dog.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}}
}
/*** org.springframework.context.annotation.internalConfigurationAnnotationProcessor* org.springframework.context.annotation.internalAutowiredAnnotationProcessor* org.springframework.context.annotation.internalCommonAnnotationProcessor* org.springframework.context.event.internalEventListenerProcessor* org.springframework.context.event.internalEventListenerFactory* springConfig5* tom* dog*/

bean的加载方式6 - ImportSelector接口

  • 导入实现了ImportSelector接口的类,实现对导入源的编程式处理
public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {return new String[]{"com.ming.bean.Cat","com.ming.bean.Dog"};}
}

配置类导入MyImportSelector

@Import(MyImportSelector.class)
public class SpringConfig6 {
}

测试:

public class App6 {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig6.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}}
}
/*** org.springframework.context.annotation.internalConfigurationAnnotationProcessor* org.springframework.context.annotation.internalAutowiredAnnotationProcessor* org.springframework.context.annotation.internalCommonAnnotationProcessor* org.springframework.context.event.internalEventListenerProcessor* org.springframework.context.event.internalEventListenerFactory* springConfig6* tom # 因为Cat类上有@Component("tom")* com.ming.bean.Dog*/

扩展:selectImports方法的使用技巧

public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {System.out.println("==============");// 获取的类名System.out.println(""+annotationMetadata.getClassName()); // com.ming.config.SpringConfig6// 获取的类名上是否注解:org.springframework.context.annotation.ConfigurationSystem.out.println(annotationMetadata.hasAnnotation("org.springframework.context.annotation.Configuration")); // true// 获取类上注解的属性,如 @ComponentScan(basePackages = {"com.ming"})Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes("org.springframework.context.annotation.ComponentScan");System.out.println(attributes); // {basePackageClasses=[], basePackages=[com.ming], excludeFilters=[], includeFilters=[], lazyInit=false, nameGenerator=interface org.springframework.beans.factory.support.BeanNameGenerator, resourcePattern=**/*.class, scopeResolver=class org.springframework.context.annotation.AnnotationScopeMetadataResolver, scopedProxy=DEFAULT, useDefaultFilters=true, value=[com.ming]}System.out.println("==============");return new String[]{"com.ming.bean.Cat","com.ming.bean.Dog"};}//    @Override//    public String[] selectImports(AnnotationMetadata annotationMetadata) {//        // 还可以进行条件的判定,判定完毕后决定装载哪个bean//        boolean flag = annotationMetadata.hasAnnotation("org.springframework.context.annotation.Configuration");//        if(flag){//            return new String[]{"com.ming.bean.Cat"};//        }else{//            return new String[]{"com.ming.bean.Dog"};//        }//    }
}

bean的加载方式7 - ImportBeanDefinitionRegistrar接口

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

配置类注入:

@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringConfig7 {}

测试:

public class App7 {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig7.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}}
}
/*** org.springframework.context.annotation.internalConfigurationAnnotationProcessor* org.springframework.context.annotation.internalAutowiredAnnotationProcessor* org.springframework.context.annotation.internalCommonAnnotationProcessor* org.springframework.context.event.internalEventListenerProcessor* org.springframework.context.event.internalEventListenerFactory* springConfig7* dog1*/

bean的加载方式8 - BeanDefinitionRegistryPostProcessor接口

  • 导入实现了BeanDefinitionRegistryPostProcessor接口的类,通过BeanDefinition的注册器注册实名bean, 实现对容器中bean的最终裁定

场景构建:在一个配置类上的@Import()注解可以导入多个bean,如果此时导入的bean名字相同,一般来说配置靠后的会覆盖前面的配置,为了最终统一得到想要的bean,可以使用实现BeanDefinitionRegistryPostProcessor的方式完成
声明BeanDefinitionRegistryPostProcessor接口实现类:

public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl04.class).getBeanDefinition();beanDefinitionRegistry.registerBeanDefinition("bookService", beanDefinition);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {}
}
  • @Import({BookServiceImpl01.class })
@Service("bookService")
public class BookServiceImpl01 implements BookService {@Overridepublic void check() {System.out.println("BookServiceImpl01 ..");}
}
  • @Import({MyImportBeanDefinitionRegistrar81.class, })
public class MyImportBeanDefinitionRegistrar81 implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl02.class).getBeanDefinition();registry.registerBeanDefinition("bookService",beanDefinition);}
}
  • @Import({MyImportBeanDefinitionRegistrar82.class, })
public class MyImportBeanDefinitionRegistrar82 implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl03.class).getBeanDefinition();registry.registerBeanDefinition("bookService",beanDefinition);}
}
  • @Import({MyPostProcessor.class, })
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl04.class).getBeanDefinition();beanDefinitionRegistry.registerBeanDefinition("bookService", beanDefinition);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {}
}

导入配置类:

//@Import({BookServiceImpl01.class })  // 此时运行App8.java 打印出 BookServiceImpl01 ..
//@Import({BookServiceImpl01.class, MyImportBeanDefinitionRegistrar81.class, }) // 此时运行App8.java 打印出 BookServiceImpl02 .. ..
//@Import({BookServiceImpl01.class, MyImportBeanDefinitionRegistrar81.class, MyImportBeanDefinitionRegistrar82.class}) // 此时运行App8.java 打印出 BookServiceImpl03 .. .. ..
//@Import({BookServiceImpl01.class, MyImportBeanDefinitionRegistrar82.class, MyImportBeanDefinitionRegistrar81.class}) // 此时运行App8.java 打印出 BookServiceImpl02 .. ..
@Import({MyPostProcessor.class, BookServiceImpl01.class, MyImportBeanDefinitionRegistrar82.class, MyImportBeanDefinitionRegistrar81.class, }) // 此时运行App8.java 打印出 BookServiceImpl04 .. .. .. ..
public class SpringConfig8 {}

测试:

public class App8 {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig8.class);BookService bookService = ctx.getBean("bookService", BookService.class);bookService.check();}
}

小结:

image.png

1.2 bean加载控制

编程式

bean的加载控制指根据特定情况对bean进行选择性加载以达到适用于项目的目标。
故只有上述8种加载方式的后四种可以实现bean的加载控制

用一种方式举例:实现ImportSelector接口的方式

public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {/*** 功能描述:如果有Mouse这个类的话,就加载cat,* 如果没有Wolf类的话,就不加载cat*/try {
//            Class<?> clazz = Class.forName("com.ming.bean.Mouse");Class<?> clazz = Class.forName("com.ming.bean.Wolf");if (clazz != null){return new String[]{"com.ming.bean.Cat"};}} catch (ClassNotFoundException e) {return new String[0];}return null;}
}

配置类加载MyImportSelector类

@Import(MyImportSelector.class)
public class SpringConfig {
}

测试:前提条件com.ming.bean包下,有Mouse,没有Wolf;

  • 当满足第一种情况下,(有Mouse,加载Cat)
/*** org.springframework.context.annotation.internalConfigurationAnnotationProcessor* org.springframework.context.annotation.internalAutowiredAnnotationProcessor* org.springframework.context.annotation.internalCommonAnnotationProcessor* org.springframework.context.event.internalEventListenerProcessor* org.springframework.context.event.internalEventListenerFactory* springConfig* com.ming.bean.Cat  # 因为com.ming.bean包下有Mouse类*/
  • 当满足第二种情况下,(没有Wolf,不加载Cat)
/*** org.springframework.context.annotation.internalConfigurationAnnotationProcessor* org.springframework.context.annotation.internalAutowiredAnnotationProcessor* org.springframework.context.annotation.internalCommonAnnotationProcessor* org.springframework.context.event.internalEventListenerProcessor* org.springframework.context.event.internalEventListenerFactory* springConfig*/

注解式

使用@Conditional注解的派生注解设置各种组合条件控制bean的加载

  • 添加SpringBoot相关的jar包
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.5.3</version>
</dependency>

spring提供默认规范,springboot提供实现

前提条件com.ming.bean包下,有Mouse,没有Wolf;

  • 匹配指定类
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
public class SpringConfig2 {@Bean// com.ming.bean包下有Mouse类,则加载 cat
//    @ConditionalOnClass(name = "com.ming.bean.Mouse") // com.ming.bean包下有Wolf类,则加载 cat@ConditionalOnClass(name = "com.ming.bean.Wolf") public Cat cat(){return new Cat();}
}
  • 未匹配指定类
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
public class SpringConfig2 {@Bean// com.ming.bean包下没有Mouse类,则加载 cat@ConditionalOnMissingClass("com.ming.bean.Mouse")// com.ming.bean包下没有Wolf类,则加载 cat
//    @ConditionalOnMissingClass("com.ming.bean.Wolf")public Cat cat(){return new Cat();}
}
  • 匹配指定类型的bean
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
@Import(Mouse.class)
public class SpringConfig2 {@Bean// 容器中有名称为com.ming.bean.Mouse的bean,则加载 cat2//    @ConditionalOnBean(name = "com.ming.bean.Mouse")// 容器中有名称为jerry的bean,则加载 cat2//    @ConditionalOnBean(name = "jerry")// 容器中有名称为com.ming.bean.Wolf的bean,则加载 cat2@ConditionalOnBean(name = "com.ming.bean.Wolf")public Cat cat2(){return new Cat();}
}
  • 未匹配指定类型的bean
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
@Import(Mouse.class)
public class SpringConfig2 {@Bean// 容器中没有名称为com.ming.bean.Mouse的bean,则加载 cat2//    @ConditionalOnMissingBean(name = "com.ming.bean.Mouse")// 容器中没有名称为com.ming.bean.Wolf的bean,则加载 cat2@ConditionalOnMissingBean(name = "com.ming.bean.Wolf")  public Cat cat2(){return new Cat();}
}
  • 匹配指定环境
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
public class SpringConfig2 {@Bean// 当是web环境时,才加载//    @ConditionalOnWebApplication// 当不是web环境时,才加载@ConditionalOnNotWebApplicationpublic Cat cat3(){return new Cat();}
}

小结

image.png

1.3 bean依赖属性配置

案例描述:
要求在application.yml中传入业务所需要的属性值,并在原始位置声明默认值;
为了实现这一效果,我们先来配置一个基本的环境
业务类:CartoonCatAndMouse

@Component
public class CartoonCatAndMouse {private Cat cat;private Mouse mouse;CartoonCatAndMouse(){cat = new Cat();cat.setName("tom");cat.setAge(3);mouse = new Mouse();mouse.setName("jerry");mouse.setAge(4);}public void play(){System.out.println(cat.getAge()+"岁的"+cat.getName()+"和"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了~~~");}
}

实体类:Cat / Mouse

@Data
public class Cat {private String name;private Integer age;
}@Data
public class Mouse {private String name;private Integer age;
}

主类:

@SpringBootApplication
public class App1 {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App1.class);CartoonCatAndMouse cartoonCatAndMouse = context.getBean(CartoonCatAndMouse.class);cartoonCatAndMouse.play();}
}
// 3岁的tom和4岁的jerry打起来了~~~

改进: 增加配置文件application.yml 和 对应的属性类, (将业务功能bean运行需要的资源抽取成独立的属性类(******Properties),设置读取配置文件信息)
新增:

cartoon:cat:name: "TOM"age: 6mouse:name: "JERRY"age: 8
@Component
@Data
@ConfigurationProperties(prefix = "cartoon")
public class CartoonProperties {private Cat cat;private Mouse mouse;
}

修改业务类CartoonCatAndMouse读取属性的方式:

@Component
public class CartoonCatAndMouse {private Cat cat;private Mouse mouse;private CartoonProperties cartoonProperties;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()+"打起来了~~~");}
}

测试效果:6岁的TOM和8岁的JERRY打起来了~~~

完善:

  • CartoonProperties 不应该强制声明成Bean,因为,没有在配置文件中声明的话,即用默认的即可,无需加载这个Bean
    • 解决:去掉CartoonProperties类上的@Component注解,在调用方(CartoonCatAndMouse)类上加@EnableConfigurationProperties(CartoonProperties.class)注解
  • CartoonCatAndMouse 也不应该强制声明成Bean,因为,只有在调用方(App1)使用到的时候,才应该加载该Bean
    • 解决:去掉CartoonCatAndMouse 类上的@Component注解,在调用方(App1)类上加@Import(CartoonCatAndMouse.class)注解

小结:

image.png

1.4 自动配置原理

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

源码解析:
SpringBoot的自动配置是由@SpringBootApplication实现的,所以我们来先看这个注解内部构造:

/*** @SpringBootConfiguration*     @Configuration # 里面有一个proxyBeanMethods 属性,控制是否开启代理模式创建bean,详解扩展3*         @Component*     @Indexed # 在编译时扫描 @Indexed 注解,确定 bean,生成索引文件。先看如下 Spring 官网* @EnableAutoConfiguration*     @AutoConfigurationPackage*         @Import({AutoConfigurationPackages.Registrar.class}) # 重点在此*     @Import({AutoConfigurationImportSelector.class}) # 重点在此* @ComponentScan( # 组件扫描 排除一些不需要的Bean*     excludeFilters = {*         @ComponentScan.Filter(type = FilterType.CUSTOM,*         classes = {TypeExcludeFilter.class}),*         @ComponentScan.Filter(type = FilterType.CUSTOM,*         classes = {AutoConfigurationExcludeFilter.class})*     })*//*** 下面我们来看一下重点的两个:*  1. @Import({AutoConfigurationPackages.Registrar.class})*  2. @Import({AutoConfigurationImportSelector.class})*/@SpringBootApplication
public class App1 {public static void main(String[] args) {SpringApplication.run(App1.class);}
}
  1. @Import({AutoConfigurationPackages.Registrar.class})
public abstract class AutoConfigurationPackages {private static final Log logger = LogFactory.getLog(AutoConfigurationPackages.class);private static final String BEAN = AutoConfigurationPackages.class.getName();// 实现ImportBeanDefinitionRegistrar接口,所以推测出是要注册某个Bean到容器中static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {Registrar() {}public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 调用register方法,向容器注入一个beanAutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));}public Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));}}// 进入 registerpublic static void register(BeanDefinitionRegistry registry, String... packageNames) {if (registry.containsBeanDefinition(BEAN)) {AutoConfigurationPackages.BasePackagesBeanDefinition beanDefinition = (AutoConfigurationPackages.BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);beanDefinition.addBasePackages(packageNames);} else {// BEAN: 当前类的全路径名 org.springframework.boot.autoconfigure.AutoConfigurationPackages// packageNames : com.ming : 当前App1类所在的包registry.registerBeanDefinition(BEAN, new AutoConfigurationPackages.BasePackagesBeanDefinition(packageNames));}}static final class BasePackagesBeanDefinition extends GenericBeanDefinition {private final Set<String> basePackages = new LinkedHashSet();// 进入BasePackagesBeanDefinition构造器BasePackagesBeanDefinition(String... basePackages) {this.setBeanClass(AutoConfigurationPackages.BasePackages.class);this.setRole(2);// 添加 扫描路径 com.mingthis.addBasePackages(basePackages);}public Supplier<?> getInstanceSupplier() {return () -> {return new AutoConfigurationPackages.BasePackages(StringUtils.toStringArray(this.basePackages));};}private void addBasePackages(String[] additionalBasePackages) {this.basePackages.addAll(Arrays.asList(additionalBasePackages));}}// .....
}

经过断点模式,发现(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames()是我们当前的App1类所在的包名。
image.png
结论:这个@Import 的作用是设置当前配置所在的包作为扫描包,后续要针对当前的包进行扫描

  1. @Import({AutoConfigurationImportSelector.class})
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {// 实现DeferredImportSelector 接口, 发现 DeferredImportSelector 的 getImportGroup 方法,最终返回AutoConfigurationGroup对象,// AutoConfigurationGroup 对象又实现了 DeferredImportSelector的Group 方法,在DeferredImportSelector的Group中调用process()方法,// 所以入口是process()方法private static class AutoConfigurationGroup implements Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {// 主要看getAutoConfigurationEntry方法public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());});AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);this.autoConfigurationEntries.add(autoConfigurationEntry);Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();while(var4.hasNext()) {String importClassName = (String)var4.next();this.entries.putIfAbsent(importClassName, annotationMetadata);}}}// 跟进,getCandidateConfigurations()protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {// ....List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);}}// 跟进,loadFactoryNames()protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");return configurations;}// 返回EnableAutoConfiguration的全路径类名,以便在调用loadFactoryNames时,作为参数传入protected Class<?> getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;}
}

调用SpringFactoriesLoader类中的loadFactoryNames()方法:

public final class SpringFactoriesLoader {// 跟进,loadSpringFactories()public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {// 获取类型名,此时的factoryTypeName: org.springframework.boot.autoconfigure.EnableAutoConfigurationString factoryTypeName = factoryType.getName();// 返回 spring.factories 文件中,所有以factoryTypeName 为key的value,存入list中return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());}// 发现读取了一个 META-INF/spring.factories 文件private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {Map<String, List<String>> result = (Map)cache.get(classLoader);if (result != null) {return result;} else {HashMap result = new HashMap();try {Enumeration urls = classLoader.getResources("META-INF/spring.factories");return result;} catch (IOException var14) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);}}}
}

spring.factories 位置:
image.png
内容:看EnableAutoConfiguration类下的
image.png
结论:这个@Import 的作用:利用AutoConfigurationImportSelector给容器中导入一些组件,具体就是最终就是将类路径下** META-INF/spring.factories **里面配置的所有EnableAutoConfiguration的值加入到了容器中;也就是xxxAutoConfiguration,这些都是用来做自动配置的。

至此,所有的技术集已经全部加载,完成了第四步;
接下来,我们查看spring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration,选取一个redis相关的配置:
image.png
RedisAutoConfiguration.java

@Configuration(proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})  // 当类库中存在RedisOperations接口时才加载,所以要导入redis的依赖
@EnableConfigurationProperties({RedisProperties.class})  // RedisProperties类中存 所有redis配置
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {public RedisAutoConfiguration() {}@Bean@ConditionalOnMissingBean(name = {"redisTemplate"}) // 如果 没有自定义 redisTemplate ,则使用他封装的@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}@Bean@ConditionalOnMissingBean@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}
}
@ConfigurationProperties(prefix = "spring.redis"
)
public class RedisProperties {private int database = 0;private String url;private String host = "localhost";private String username;private String password;private int port = 6379;private boolean ssl;private Duration timeout;private Duration connectTimeout;private String clientName;private RedisProperties.ClientType clientType;private RedisProperties.Sentinel sentinel;private RedisProperties.Cluster cluster;private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
}

对比导入redis依赖前后,容器中的bean:

  • 导入redis的依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>3.0.4</version>
</dependency>
  • 导入前:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
app1
org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
com.ming.bean.CartoonCatAndMouse
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
org.springframework.boot.context.internalConfigurationPropertiesBinderFactory
org.springframework.boot.context.internalConfigurationPropertiesBinder
org.springframework.boot.context.properties.BoundConfigurationProperties
org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar.methodValidationExcludeFilter
cartoon-com.ming.bean.CartoonProperties
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
springApplicationAdminRegistrar
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration
forceAutoProxyCreatorToUseClassProxying
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
applicationAvailability
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
lifecycleProcessor
spring.lifecycle-org.springframework.boot.autoconfigure.context.LifecycleProperties
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration
spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties
org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration
spring.sql.init-org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties
org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
taskExecutorBuilder
applicationTaskExecutor
spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration
scheduledBeanLazyInitializationExcludeFilter
taskSchedulerBuilder
spring.task.scheduling-org.springframework.boot.autoconfigure.task.TaskSchedulingProperties
org.springframework.aop.config.internalAutoProxyCreator

没有redis相关的信息

  • 导入后:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
app1
org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
com.ming.bean.CartoonCatAndMouse
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
org.springframework.boot.context.internalConfigurationPropertiesBinderFactory
org.springframework.boot.context.internalConfigurationPropertiesBinder
org.springframework.boot.context.properties.BoundConfigurationProperties
org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar.methodValidationExcludeFilter
cartoon-com.ming.bean.CartoonProperties
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
springApplicationAdminRegistrar
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration
forceAutoProxyCreatorToUseClassProxying
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
applicationAvailability
org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration
lettuceClientResources
redisConnectionFactory
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
redisTemplate
stringRedisTemplate
spring.redis-org.springframework.boot.autoconfigure.data.redis.RedisProperties
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
lifecycleProcessor
spring.lifecycle-org.springframework.boot.autoconfigure.context.LifecycleProperties
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration
persistenceExceptionTranslationPostProcessor
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration
reactiveRedisTemplate
reactiveStringRedisTemplate
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration
keyValueMappingContext
redisCustomConversions
redisReferenceResolver
redisConverter
redisKeyValueAdapter
redisKeyValueTemplate
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration
spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties
org.springframework.boot.autoconfigure.netty.NettyAutoConfiguration
spring.netty-org.springframework.boot.autoconfigure.netty.NettyProperties
org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration
spring.sql.init-org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties
org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
taskExecutorBuilder
applicationTaskExecutor
spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration
scheduledBeanLazyInitializationExcludeFilter
taskSchedulerBuilder
spring.task.scheduling-org.springframework.boot.autoconfigure.task.TaskSchedulingProperties
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
platformTransactionManagerCustomizers
spring.transaction-org.springframework.boot.autoconfigure.transaction.TransactionProperties
org.springframework.aop.config.internalAutoProxyCreator

有redis相关的信息

扩展:
SpringFactories机制

  1. Java SPI机制的延伸和扩展
  2. Spring框架的基础机制,在Spring以及SpringBoot源码中到处可见
  3. 可以基于它来实现SpringBoot的自动配置功能
  4. 它的核心逻辑是从classpath中读取到所有jar包中的配置文件META-IF/spring.factories,然后根据指定的key从配置文件中解析出对应的value值

image.png

小结:

image.png

1.5 变更自动配置

添加自动配置

  • 添加自动配置文件(META-INF/spring.factories)
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ming.bean.CartoonCatAndMouse
  • 在CartoonCatAndMouse类上添加@ConditionalOnClass
@ConditionalOnClass(name = "org.springframework.data.redis.core.RedisOperations") // 借用redis的jar, 当有这个类时,注册这个bean
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse {private Cat cat;private Mouse mouse;private CartoonProperties cartoonProperties;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()+"打起来了~~~");}
}
  • 测试:删除之前的手动导入Bean - @Import(CartoonCatAndMouse.class);加上redis依赖;
@SpringBootApplication
//@Import(CartoonCatAndMouse.class)
public class App1 {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App1.class);CartoonCatAndMouse cartoonCatAndMouse = context.getBean(CartoonCatAndMouse.class);cartoonCatAndMouse.play();}
}

删除系统自动配置

  • 在yml 文件中配置
spring:autoconfigure:exclude:- org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration- org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
  • 使用注解
@SpringBootApplication(excludeName = "org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration")

小结:

image.png

二、自定义starter

2.1 案例:统计独立IP访问次数

功能描述:

  1. 每次访问网站行为均进行统计
  2. 后台每10s输出1次监控信息(格式:IP + 访问次数)

实现方案:

  1. 数据记录位置:Map / Redis
  2. 功能触发位置:每次web请求(拦截器)
    1. 步骤1:降低难度,主动调用,仅统计单一操作访问次数(例如查询)
    2. 步骤2:开发拦截器
  3. 业务参数(配置项)
    1. 输出频度:默认10s
    2. 数据特征:累计数据 / 阶段数据,默认累计数据
    3. 输出格式:详细模式 / 极简模式
  4. 校验环境:设置加载条件

具体实现:

  • 环境搭建:

参照:TangGuoNiuBi/spring-boot-study
导入数据:tbl_book.sql
访问url:http://localhost:81/pages/books.html
image.png

2.2 自定义starter

新建模块及基础功能开发:ip_spring_boot_starter

导入依赖:

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

编写业务功能代码:第一版

public class IpCountService {private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();@Autowired// 当前的request对象的注入工作,由使用当前starter的工程提供自动装配private HttpServletRequest httpServletRequest;public void count(){// 每次调用当前操作,就记录当前访问IP,然后累加访问次数// 1. 获取当前操作的IP地址String ip = httpServletRequest.getRemoteAddr();System.out.println("-----------------------" + ip);// 2. 根据IP操作地址从Map取值,并递增Integer count = ipCountMap.get(ip);if (count == null){ipCountMap.put(ip,1);}else {ipCountMap.put(ip,count + 1);}}
}

自动配置类:

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

在META-INF/spring.factories文件中配置:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.ming.autoconfig.IpAutoConfiguration

在本地maven仓库中安装自定义starter:

mvn clear
mvn install

切记使用之前先clean后install安装到maven仓库,确保资源更新

  • 简单测试

在需要添加的计数功能的工程中导入自定义starter

<dependency><groupId>cn.ming</groupId><artifactId>ip_spring_boot_starter</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>

模拟调用:

@Autowired
private IpCountService ipCountService;@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize, Book book) {ipCountService.count();IPage<Book> page = bookService.getPage(currentPage, pageSize, book);// 如果当前页面值大于总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值if (currentPage > page.getPages()) {page = bookService.getPage((int) page.getPages(), pageSize, book);}return new R(true, page);
}

预想结果:每次刷新,点击,出现下面内容;

-----------------------0:0:0:0:0:0:0:1

至此已经完成基础功能,下面开发定时任务功能

功能开发之定时任务功能

添加@EnableScheduling注解,开启定时任务

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

添加业务方法,打印map中存的信息

public class IpCountService {private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();@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("|%18s  |%5d  |",key,value));}//System.out.println(String.format("|%18s  |%5d  |","abc",20));System.out.println("+--------------------+-------+");}public static void main(String[] args) {new IpCountService().print();}
}

注意:String.format()的使用

功能开发之属性动态配置

定义属性类:

@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;}}// getter / setter 
}

对应配置文件:

tools:ip:cycle: 10cycle-reset: falsemodel: "simple"

测试时使用,实际引入时,要清空yaml文件

在配置类上 设置加载Properties类为bean

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

在业务类中追加逻辑代码

public class IpCountService {private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();@Autowiredprivate 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()) {System.out.println(String.format("|%18s  |%5d  |",entry.getKey(),entry.getValue()));}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()) {System.out.println(String.format("|%18s  |",entry.getKey()));}System.out.println("+--------------------+");}// 判断是否清空数据if(ipProperties.getCycleReset()){ipCountMap.clear();}}
}

测试 tools.ip.cycle-resettools.ip.model ,输出符合预期;

tools.ip.cycle的处理:
处理目标:使用属性类的方式读取cycle属性加载到@Scheduled(cron = "0/5 * * * * ?")位置上去
使用#{beanName.属性名}的方式,可以将IpProperties的cycle注入到注解中。

Tips:
但是,此时的IpProperties类的beanName是tools.ip-cn.ming.properties.IpProperties,所以直接使用的话会报错。
因为当前的beanName是由配置类上的@EnableConfigurationProperties(IpProperties.class注解自动生成的,所以,我们将这个注解删除,在IpProperties类上加上@Component("ipProperties")注解,最后在到配置类上引入即可;

属性类:自定义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;}}// setter / getter
}

配置类: 放弃配置属性创建bean方式,改为手工控制

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

业务类使用: 使用#{beanName.attrName}读取bean的属性。

public class IpCountService {private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();@Autowired// 当前的request对象的注入工作,由使用当前starter的工程提供自动装配private HttpServletRequest httpServletRequest;public void count(){// 每次调用当前操作,就记录当前访问IP,然后累加访问次数// 1. 获取当前操作的IP地址String ip = httpServletRequest.getRemoteAddr();System.out.println("-----------------------"+ip);// 2. 根据IP操作地址从Map取值,并递增Integer count = ipCountMap.get(ip);if (count == null){ipCountMap.put(ip,1);}else {ipCountMap.put(ip,count + 1);}}@Autowiredprivate IpProperties ipProperties;@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()) {System.out.println(String.format("|%18s  |%5d  |",entry.getKey(),entry.getValue()));}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()) {System.out.println(String.format("|%18s  |",entry.getKey()));}System.out.println("+--------------------+");}// 判断是否清空数据if(ipProperties.getCycleReset()){ipCountMap.clear();}}
}

功能开发之添加拦截器

在ip_spring_boot_starter模块中添加拦截器:

package cn.ming.interceptor;
public class IpInterceptor implements HandlerInterceptor {@Autowiredprivate IpCountService ipCountService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {ipCountService.count();return true;}
}

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

@Configuration(proxyBeanMethods = true)
public class SpringMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(ipInterceptor()).addPathPatterns("/**");}@Beanpublic IpInterceptor ipInterceptor(){return new IpInterceptor();}
}

记得删除之前测试用的ipCountService.count();

至此,自定义starter已经全部开发完,实现,导入依赖,添加统计功能,移出依赖,不影响自身运行;

2.3 辅助功能开发

yml提示功能开发

  1. 导入依赖:
<!-- yml 自动提示包,target生成json文件后,复制到本项目的META-INF目录下,最后移除此依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
  1. 执行 mvn clearmvn install
  2. 复制target/classes/META-INF目录下的spring-configuration-metadata.json文件到src/main/resources/META-INF/spring-configuration-metadata.json
  3. 删除多余的spring-configuration-metadata.json和 依赖
  4. 在json文件中 进行自定义提示功能开发
"hints": [{"name": "tools.ip.model","values": [{"value": "detail","description": "详细模式."},{"value": "simple","description": "极简模式."}]}
]
  1. 测试:

image.png

三、核心原理

SpringBoot启动流程

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

小结:

  1. 加载数据
  2. 创建容器

ClassName[行号] -> 执行代码 // 解释

SpringbootDemo05Application[10] -> SpringApplication.run(SpringbootDemo05Application.class, args);SpringApplication[825] -> return run(new Class[]{primarySource}, args);SpringApplication[829] -> return (new SpringApplication(primarySources)).run(args);SpringApplication[829] -> SpringApplication(primarySources)// 加载各种配置信息,初始化各种配置对象SpringApplication[101] -> this((ResourceLoader)null, primarySources);SpringApplication[104] -> public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {SpringApplication[105] -> this.sources = new LinkedHashSet();  // 创建LinkedHashSetSpringApplication[106] -> this.bannerMode = Mode.CONSOLE;  // 该属性是用来打印springboot启动时的图标。// Mode是org.springframework.boot.Banner接口的静态enum类型//     OFF: 关闭//     CONSOLE: 打印到控制台//     LOG: 打印到日志SpringApplication[107] -> this.logStartupInfo = true;  // true: 打印jvm的启动和运行信息。包括启动类,java版本,pid等SpringApplication[108] -> this.addCommandLineProperties = true;  // true: 通过命令行参数,向application.properties文件中添加属性;例如 java -jar test.jar -server.port=80,(设置端口号)SpringApplication[109] -> this.addConversionService = true;// true: 加载默认的类型转换和格式化类(ApplicationConversionService); 详情参考 ApplicationConversionService[152] -> public static void addApplicationConverters(ConverterRegistry registry)SpringApplication[110] -> this.headless = true;// 开启java的headless模式。此模式允许java在没有显示器、鼠标等设备缺失的情况下启动服务,对于部署在服务器的程序来说是很有必要的。SpringApplication[111] -> this.registerShutdownHook = true;// 创建线程,该线程用来在Java程序关闭后释放资源SpringApplication[112] -> this.additionalProfiles = Collections.emptySet();// 默认为空,即使用springboot默认配置文件。可以调用SpringApplication的setAdditionalProfiles()方法,实现springboot中的profile属性的指定,用于切换环境SpringApplication[113] -> this.isCustomEnvironment = false;// 加载环境配置SpringApplication[114] -> this.lazyInitialization = false;// 懒加载SpringApplication[115] -> this.applicationContextFactory = ApplicationContextFactory.DEFAULT;// TODOSpringApplication[116] -> this.applicationStartup = ApplicationStartup.DEFAULT;// TODOSpringApplication[117] -> this.resourceLoader = resourceLoader;// 初始化资源加载器SpringApplication[118] -> Assert.notNull(primarySources, "PrimarySources must not be null");SpringApplication[119] -> this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));// 形参的primarySources: Class类型的SpringbootDemo05Application// 初始化配置类的类名信息(格式转换)(将可变类型转换为集合类型存入成员变量中)SpringApplication[120] -> this.webApplicationType = WebApplicationType.deduceFromClasspath();// 确认当前容器加载的类型WebApplicationType[25] -> static WebApplicationType deduceFromClasspath(){}// WebApplicationType enum类中有三种类型["NONE","SERVLET","REACTIVE"]//      NONE: 需导入spring-boot-starter依赖//      SERVLET: 需导入的spring-boot-starter-web依赖//      REACTIVE: 需导入spring-boot-starter-webflux依赖SpringApplication[121] -> this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();// 获取系统配置引导信息SpringApplication[127] -> private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() {}// 在 META-INF/spring.factories 中查找 以 Bootstrapper.class / BootstrapRegistryInitializer.class 为key的value值SpringApplication[122] -> this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));// 在 META-INF/spring.factories 中查找 以 ApplicationContextInitializer.class 为key的value值// 获取ApplicationContextInitializer.class对应的实例SpringApplication[123] -> this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));// 在 META-INF/spring.factories 中查找 以 ApplicationListener.class 为key的value值// 初始化监听器,对初始化过程及运行过程进行干预SpringApplication[124] -> this.mainApplicationClass = this.deduceMainApplicationClass();// 初始化了引导类(SpringbootDemo05Application)的类名信息,备用SpringApplication[829] -> (new SpringApplication(primarySources)).run(args);// 初始化容器,得到一个ApplicationContext对象SpringApplication[155] -> StopWatch stopWatch = new StopWatch();// 声明一个计时器,用于统计SpringApplication[156] -> stopWatch.start();// 计时器开始SpringApplication[157] -> DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();// 系统引导信息对应的上下文对象SpringApplication[194] -> private DefaultBootstrapContext createBootstrapContext() {SpringApplication[195] ->     DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();SpringApplication[196] ->     this.bootstrapRegistryInitializers.forEach((initializer) -> {// List<BootstrapRegistryInitializer> bootstrapRegistryInitializers;// 遍历在之前`加载各种配置信息,初始化各种配置对象`阶段加载好的 bootstrapRegistryInitializers 集合SpringApplication[197] ->         initializer.initialize(bootstrapContext);SpringApplication[198] ->     });SpringApplication[199] ->     return bootstrapContext;SpringApplication[200] -> }SpringApplication[158] -> ConfigurableApplicationContext context = null;// 创建容器对象SpringApplication[159] -> this.configureHeadlessProperty();// 模拟输入输出信号,避免出现因缺少外设导致的信号传递失败,进而引发错误(模拟显示器,键盘,鼠标等)SpringApplication[268] -> private void configureHeadlessProperty() {SpringApplication[269] ->     System.setProperty("java.awt.headless", System.getProperty("java.awt.headless", Boolean.toString(this.headless)));// 为系统变量添加 java.awt.headless = trueSpringApplication[270] -> }SpringApplication[160] -> SpringApplicationRunListeners listeners = this.getRunListeners(args);// 获取当前注册的所有监听器SpringApplication[161] -> listeners.starting(bootstrapContext, this.mainApplicationClass);// 监听器执行了对应的操作步骤SpringApplication[163] -> try {SpringApplication[164] ->     ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 获取参数 argsSpringApplication[165] ->     ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);// 将前期读取的数据加载成了一个环境对象,用来描述信息SpringApplication[166] ->     this.configureIgnoreBeanInfo(environment);// 做了一个配置,备用SpringApplication[167] ->     Banner printedBanner = this.printBanner(environment);// 初始化启动图标 使用到之前配置的`SpringApplication[106] -> this.bannerMode = Mode.CONSOLE;` SpringApplication[168] ->     context = this.createApplicationContext();// 根据之前的容器类型,创建一个容器上下文// 如果是NONE: 则context容器为 org.springframework.context.annotation.AnnotationConfigApplicationContext@386f0da3// 如果是SERVLET: 则context容器为 org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@8dbfffbSpringApplication[169] ->     context.setApplicationStartup(this.applicationStartup);// 设置启动模式SpringApplication[170] ->     this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 对容器进行设置,参数来源于前期的设定SpringApplication[171] ->     this.refreshContext(context);// 刷新容器环境,(Spring IOC容器初始化经典原理)【spring默认的onRefresh()方法是空的,springboot将onRefresh()方法重写,加入了tomcat的启动】SpringApplication[172] ->     this.afterRefresh(context, applicationArguments);// 刷新完毕后做后处理 空方法SpringApplication[173] ->     stopWatch.stop();// 计时器结束SpringApplication[174] ->     if (this.logStartupInfo) {// 判定是否记录启动时间的日志,默认为trueSpringApplication[175] ->         (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);// 创建日志对应的对象,输出日志信息,包含启动时间// 控制台打印 : 2023-08-16 20:10:25.288  INFO 1932 --- [           main] com.ming.SpringbootDemo05Application     : Started SpringbootDemo05Application in 18.095 seconds (JVM running for 20.728)SpringApplication[176] ->     }SpringApplication[178] ->     listeners.started(context);// 监听器执行了对应的操作步骤SpringApplication[179] ->     this.callRunners(context, applicationArguments);SpringApplication[180] -> } catch (Throwable var10) {SpringApplication[181] ->     this.handleRunFailure(context, var10, listeners);// 异常处理SpringApplication[182] ->     throw new IllegalStateException(var10);SpringApplication[183] -> }SpringApplication[185] -> try {SpringApplication[186] ->     listeners.running(context);// 监听器执行了对应的操作步骤SpringApplication[187] ->     return context;SpringApplication[188] -> } catch (Throwable var9) {SpringApplication[189] ->     this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);SpringApplication[190] ->     throw new IllegalStateException(var9);SpringApplication[191] -> }

参考博客:

  • https://blog.csdn.net/weixin_45525272/article/details/126502991
  • https://blog.csdn.net/A_art_xiang/article/details/122437676

容器类型选择

监听器

监听器类型:

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

SpringBoot中tomcat启动流程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/186939.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Docker】设置容器系统字符集zh_CN.UTF-8退出失效:关于Docker容器配置环境变量,再次进入失效问题

设置容器系统字符集zh_CN.UTF-8退出失效&#xff1a;关于Docker容器配置环境变量&#xff0c;再次进入失效问题 修改正在运行的Docker容器内的字符集: 先进入Docker容器&#xff1a;docker exec -it 容器ID /bin/bash查看是否支持中文字符集&#xff1a;locale -a | grep zh&a…

蓝桥杯:分数

题目 思路 等比数列求和&#xff0c;手算然后输出 代码&#xff08;已过&#xff09; #include <iostream> using namespace std; int main() {// 请在此输入您的代码int a1024*1024-1;int b1024*512;cout<<a<<"/"<<b;return 0; }

数据分析实战 | 线性回归——女性身高与体重数据分析

目录 一、数据集及分析对象 二、目的及分析任务 三、方法及工具 四、数据读入 五、数据理解 六、数据准备 七、模型训练 八、模型评价 九、模型调参 十、模型预测 实现回归分析类算法的Python第三方工具包比较常用的有statsmodels、statistics、scikit-learn等&#…

Python+reuqests自动化接口测试

1.最近自己在摸索Pythonreuqests自动化接口测试&#xff0c;要实现某个功能&#xff0c;首先自己得有清晰的逻辑思路&#xff01;这样效率才会很快&#xff01; 思路--1.通过python读取Excel中的接口用例&#xff0c;2.通过python的函数调用&#xff0c;get/Post 进行测试&…

操作系统·处理机调度死锁

3.1 处理机调度概述 3.1.1 处理机调度概述 高级调度 (High level Scheduling)决定把外存上哪些作业调入内存、创建进程、分配资源。高级调度又称作业调度、长程调度或宏观调度。只在批处理系统中有高级调度。 中级调度 (Middle level Scheduling)完成进程的部分或全部在内、…

倍福CX9020 Windows CE6.0安装中文字库方法(附字库文件)

应用背景介绍 倍福的EPC产品有些是附带Windows CE系统的&#xff0c;例如CX9020&#xff0c;而且多数系统都是英文的&#xff0c;而且没有附带中文的字库&#xff0c;如果想要在PLC HMI中使用中文进行显示就无法实现&#xff0c;经常有工程师在电脑上编好程序和界面以后测试没…

2023年11月PHP测试覆盖率解决方案

【题记&#xff1a;最近进行了ExcelBDD PHP版的开发&#xff0c;查阅了大量资料&#xff0c;发现PHP测试覆盖率解决方案存在不同的历史版本&#xff0c;让我花费了蛮多时间&#xff0c;为了避免后人浪费时间&#xff0c;整理本文&#xff0c;而且网上没有给出Azure DevOps里面P…

移动医疗科技:开发互联网医院系统源码

在这个数字化时代&#xff0c;互联网医院系统成为了提供便捷、高效医疗服务的重要手段。本文将介绍利用移动医疗科技开发互联网医院系统的源码&#xff0c;为医疗行业的数字化转型提供有力支持。 智慧医疗、互联网医院这一类平台可以通过线上的形式进行部分医疗服务&#xff…

王道数据结构课后代码题p150 15.设有一棵满二叉树(所有结点值均不同),已知其先序序列为 pre,设计一个算法求其后序序列post。(c语言代码实现)

对一般二叉树&#xff0c;仅根据先序或后序序列&#xff0c;不能确定另一个遍历序列。但对满二叉树&#xff0c;任意一个结点的左、右子树均含有相等的结点数&#xff0c;同时&#xff0c;先序序列的第一个结点作为后序序列的最后个结点。 本题代码如下 void pretopost(char …

浅谈泛在电力物联网在智能配电系统应用

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;在社会经济和科学技术不断发展中&#xff0c;配电网实现了角色转变&#xff0c;传统的单向供电服务形式已经被双向能流服务形式取代&#xff0c;社会多样化的用电需求也得以有效满足。随着物联网技术的发展&am…

基于SSM的社区生鲜电商平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

python爬虫怎么翻页 ?

首先&#xff0c;你需要安装相关的库。在你的命令行窗口中&#xff0c;输入以下命令来安装所需的库&#xff1a; pip install requests beautifulsoup4然后&#xff0c;你可以使用以下代码来爬取网页内容并翻页&#xff1a; package mainimport ("fmt""net/htt…

【ARM Trace32(劳特巴赫) 使用介绍 3 - trace32 访问运行时的内存】

请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 1.1 trace32 访问运行时的内存1.1.1 侵入式 运行时内存访问1.1.2 非侵入式运行时访问1.1.3 缓存一致性的非侵入式运行时访问 1.2 Trace32 侵入式和非侵入式 运行时访问1.2.1 侵入式访问1.2.2 非侵入式运行时访问 1…

【Redis缓存架构实战常见问题剖析】

文章目录 一、Redis缓存架构实战剖析1.1、大规模的商品缓存数据冷热分离机制1.2、缓存击穿导致线上数据压力暴增解决方案1.3、缓存穿透及其解决方案剖析1.4、突发性的热点缓存数重建导致系统压力暴增问题分析1.5、Redis分布式锁解决缓存与数据库双写不一致问题剖析1.6、利用多级…

论文速览 | TRS 2023: 使用合成微多普勒频谱进行城市鸟类和无人机分类

注1:本文系“最新论文速览”系列之一,致力于简洁清晰地介绍、解读最新的顶会/顶刊论文 论文速览 | TRS 2023: Urban Bird-Drone Classification with Synthetic Micro-Doppler Spectrograms 原始论文:D. White, M. Jahangir, C. J. Baker and M. Antoniou, “Urban Bird-Drone…

14:00面试,14:06就出来了,问的问题有点变态。。。。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到5月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

swift语言用哪种库适合做爬虫?

目录 1、Alamofire 2、URLSession 3、YepHttp 4、Kickbox 5、Vapor 注意事项 总结 在Swift语言中&#xff0c;可以使用第三方库来帮助进行网络爬虫的开发。以下是几个适合Swift语言使用的爬虫库&#xff0c;以及相应的代码示例&#xff1a; 1、Alamofire Alamofire是Sw…

深入理解ClickHouse跳数索引

一、跳数索引​ 影响ClickHouse查询性能的因素很多。在大多数场景中&#xff0c;关键因素是ClickHouse在计算查询WHERE子句条件时是否可以使用主键。因此&#xff0c;选择适用于最常见查询模式的主键对于表的设计至关重要。 然而&#xff0c;无论如何仔细地调优主键&#xff…

Spring 常见面试题

1、Spring概述 1.1、Spring是什么? Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题Spring最根本的使命是解决企业级应用开发的复杂性&#xff0c;即简化Java开发。这些功能的底层都依赖于它的两个核心特性&#xff0c;也就是…

新能源汽车高压线束是如何快速连接到测试设备上进行电性能测试的

快速连接形成稳定的电测试在新能源行业里面是很常见的测试场景&#xff0c;比如说在新能源汽车行业的电池包、电机、电控制器的电性能测试中会有很多高压线束&#xff0c;需要将这些线束和电池包、电控制器、电机与测试设备快速连接在一起进行相关的EOL/DCR测试。 新能源汽车高…