导入依赖:
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.9version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.17version>
dependency>
dependencies>
配置applicationContext1.xml
<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"/>
<bean class="com.ming.bean.Dog"/>
<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@4566e5bd
String[] 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
*/
}
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.ming.bean,com.ming.config"/>
beans>
@Component("tom")
public class Cat {}
@Controller("jerry")
public class Mouse {}
@Service
public class BookServiceImpl01 implements BookService {
@Override
public void check() {
System.out.println("BookServiceImpl01 ..");
}
}
// ....
@Configuration
public class DbConfig {
/**
* 加载第三方bean
* @return
*/
@Bean
public 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
*/
}
}
}
标注注解的类同上
配置类如下所示:
@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
*/
}
}
}
初始化实现FactoryBean接口的类,实现对bean加载到容器之前的批处理操作
public class DogFactoryBean implements FactoryBean<Dog> {
@Override
public Dog getObject() throws Exception {
Dog dog = new Dog();
// 进行dog对象的相关初始化
return dog;
}
@Override
public Class<?> getObjectType() {
return Dog.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
使用方式:返回的不是DogFactoryBean对象,而是DogFactoryBean对象创建出来的Dog对象
@ComponentScan({"com.ming.bean","com.ming.config"})
public class SpringConfig3 {
@Bean
public 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
*/
如何在同时加载配置文件和配置类?(系统迁移)
@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
<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
}
}
@Configuration(proxyBeanMethods = true) // 默认为true
public class SpringConfig33 {
@Bean
public 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
*/
}
}
@Import({Dog.class})
public class SpringConfig4 {
}
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
*/
除了可以使用@Import()注解注入普通的Bean外,还可以加载配置类,并且在配置类中声明的bean也生效
@Import({DbConfig.class})
public class SpringConfig42 {
}
// @Configuration
public class DbConfig {
@Bean
public 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
*/
public class App5 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig5.class);
// 上下文容器对象已经初始化完毕后,手工加载bean。 - registerBean/register
ctx.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
*/
public class MyImportSelector implements ImportSelector {
@Override
public 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 {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
System.out.println("==============");
// 获取的类名
System.out.println(""+annotationMetadata.getClassName()); // com.ming.config.SpringConfig6
// 获取的类名上是否注解:org.springframework.context.annotation.Configuration
System.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"};
// }
// }
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public 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
*/
场景构建:在一个配置类上的@Import()注解可以导入多个bean,如果此时导入的bean名字相同,一般来说配置靠后的会覆盖前面的配置,为了最终统一得到想要的bean,可以使用实现BeanDefinitionRegistryPostProcessor的方式完成
声明BeanDefinitionRegistryPostProcessor接口实现类:
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(BookServiceImpl04.class)
.getBeanDefinition();
beanDefinitionRegistry.registerBeanDefinition("bookService", beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
@Service("bookService")
public class BookServiceImpl01 implements BookService {
@Override
public void check() {
System.out.println("BookServiceImpl01 ..");
}
}
public class MyImportBeanDefinitionRegistrar81 implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl02.class).getBeanDefinition();
registry.registerBeanDefinition("bookService",beanDefinition);
}
}
public class MyImportBeanDefinitionRegistrar82 implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl03.class).getBeanDefinition();
registry.registerBeanDefinition("bookService",beanDefinition);
}
}
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(BookServiceImpl04.class)
.getBeanDefinition();
beanDefinitionRegistry.registerBeanDefinition("bookService", beanDefinition);
}
@Override
public 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();
}
}
bean的加载控制指根据特定情况对bean进行选择性加载以达到适用于项目的目标。
故只有上述8种加载方式的后四种可以实现bean的加载控制
用一种方式举例:实现ImportSelector接口的方式
public class MyImportSelector implements ImportSelector {
@Override
public 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;
/**
* 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类
*/
/**
* 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的加载
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.5.3version>
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();
}
}
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();
}
}
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环境时,才加载
@ConditionalOnNotWebApplication
public Cat cat3(){
return new Cat();
}
}
案例描述:
要求在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: 6
mouse:
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打起来了~~~
完善:
@Component
注解,在调用方(CartoonCatAndMouse)类上加@EnableConfigurationProperties(CartoonProperties.class)
注解@Component
注解,在调用方(App1)类上加@Import(CartoonCatAndMouse.class)
注解源码解析:
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);
}
}
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方法,向容器注入一个bean
AutoConfigurationPackages.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));
}
}
// 进入 register
public 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.ming
this.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类所在的包名。
结论:这个@Import 的作用是设置当前配置所在的包作为扫描包,后续要针对当前的包进行扫描
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.EnableAutoConfiguration
String 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 位置:
内容:看EnableAutoConfiguration类下的
结论:这个@Import 的作用:利用AutoConfigurationImportSelector给容器中导入一些组件,具体就是最终就是将类路径下** META-INF/spring.factories **里面配置的所有EnableAutoConfiguration的值加入到了容器中;也就是xxxAutoConfiguration,这些都是用来做自动配置的。
至此,所有的技术集已经全部加载,完成了第四步;
接下来,我们查看spring.factories
中的org.springframework.boot.autoconfigure.EnableAutoConfiguration
,选取一个redis相关的配置:
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:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
<version>3.0.4version>
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机制
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ming.bean.CartoonCatAndMouse
@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()+"打起来了~~~");
}
}
@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();
}
}
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
- org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
@SpringBootApplication(excludeName = "org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration")
功能描述:
参照:TangGuoNiuBi/spring-boot-study
导入数据:tbl_book.sql
访问url:http://localhost:81/pages/books.html
导入依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
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 {
@Bean
public 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.minggroupId>
<artifactId>ip_spring_boot_starterartifactId>
<version>0.0.1-SNAPSHOTversion>
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 {
@Bean
public 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: 10
cycle-reset: false
model: "simple"
测试时使用,实际引入时,要清空yaml文件
在配置类上 设置加载Properties类为bean
@EnableScheduling
@EnableConfigurationProperties(IpProperties.class)
public class IpAutoConfiguration {
@Bean
public IpCountService ipCountService(){
return new IpCountService();
}
}
在业务类中追加逻辑代码
public class IpCountService {
private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
@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()) {
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-reset
和 tools.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 {
@Bean
public 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);
}
}
@Autowired
private 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 {
@Autowired
private IpCountService ipCountService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ipCountService.count();
return true;
}
}
设置核心配置类,加载拦截器
@Configuration(proxyBeanMethods = true)
public class SpringMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(ipInterceptor()).addPathPatterns("/**");
}
@Bean
public IpInterceptor ipInterceptor(){
return new IpInterceptor();
}
}
记得删除之前测试用的
ipCountService.count();
至此,自定义starter已经全部开发完,实现,导入依赖,添加统计功能,移出依赖,不影响自身运行;
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
dependency>
mvn clear
和 mvn install
spring-configuration-metadata.json
文件到src/main/resources/META-INF/spring-configuration-metadata.json
spring-configuration-metadata.json
和 依赖"hints": [
{
"name": "tools.ip.model",
"values": [
{
"value": "detail",
"description": "详细模式."
},
{
"value": "simple",
"description": "极简模式."
}
]
}
]
小结:
- 加载数据
- 创建容器
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();
// 创建LinkedHashSet
SpringApplication[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;
// TODO
SpringApplication[116] -> this.applicationStartup = ApplicationStartup.DEFAULT;
// TODO
SpringApplication[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 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 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 = true
SpringApplication[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);
// 获取参数 args
SpringApplication[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@8dbfffb
SpringApplication[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) {
// 判定是否记录启动时间的日志,默认为true
SpringApplication[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] -> }
参考博客:
监听器类型: