Spring5源码分析之启动类的相关接口和注解
一些基础但是核心的知识总结:
- Spring Boot项目启动的时候需要加@Configuration、 @ComponentScan
- @Configuration + @Bean 把第三方jar包注入到容器中。
- 内部的直接 @Service @Controller等等之类配合 @ComponentSscan 的就OK了
- @Scope可以实现单例
- 对于启动默认是饿汉式调用时候创建(但是项目启动时候比较耗费时间),另外一种是调用时候创建
- @ComponentScan有排除的用法,排除那个组件 需要哪个组件可以控制
- 在config类上面加 @ComponentScan然后去控制其他注解的注入情况
- 使用@Condition多条件注册bean对象,根据环境判断进行抉择
- @Import快速注入第三方bean对象
- SpringBoot Emablexxx开启原理
- 基于ImportBeanDefinitionRegister注册bean
- 基于FactoryBean注册Bean对象
Spring的扩展接口: condition
对于控制某个Bean,满足某个条件后才可以允许注册到容器中:
随便写个系统实体类吧:
public class OS { private String name; private Integer version; public OS() { } @Override public String toString() { return "hello ********************* OS{" + "name='" + name + '\'' + ", version=" + version + '}'; } }
配置文件(相当于xml)
@Configuration public class MyConfig { @Bean @Conditional(MyCondition.class) public OS os(){ System.out.println("ttttttttttttttttttttt"); return new OS(); } }
控制条件:
public class MyCondition implements Condition { /** * * @param conditionContext 获取当前上下文 * @param annotatedTypeMetadata 获取到当前注解的细节 * @return */ public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { Environment environment = conditionContext.getEnvironment(); String osName = environment.getProperty("os.name"); if (osName.equals("Windows 7")){ return false; } if (osName.equals("Windows 10")){ return true; } return false; } }
测试类:
public class test { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String name : beanDefinitionNames){ System.out.println(name); } OS osTest = (OS)applicationContext.getBean("os"); System.out.println(osTest); } }
结果:
2. @Import注解
思考 为啥使用@Import注解呢?
@Bean注解应用场景是什么呢? 注册加载外部的jar。如果有30个bean,逐个去写也是非常麻烦的啊
所以就用@Import简化操作,将外部的jar包注入到Spring ioc容器中。等同于@Bean。@Import注册的Bean对象,id为当前类的全路径。
配置类:
@Configuration @Import(OS.class) public class MyConfig { }
测试方法:
public class test { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String name : beanDefinitionNames){ System.out.println(name); } //Import的特殊性 OS osTest = (OS)applicationContext.getBean("com.toov5.config.beanTest.OS"); System.out.println(osTest); } }
@Import 和 @Bean的区别:
@Bean注册的bean的id是方法名称
@Import当前类完整地址
共同应用场景都是引用外部jar包。
3. @EnableXXX
开启某某功能。底层使用@Import注解实现的
底层调用的就是@Import注解
场景: 封装一个框架,把不支持springboot的,鼓捣成支持springboot的!
实体类:
public class PayEntity { }
注解:
/** * 启动时候加入该注解,开启功能,会将实体类注入到容器中 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({PayEntity.class}) public @interface EnablePayEntity { }
启动:
@Configuration @EnablePayEntity public class MyConfig { }
测试:
public class test { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String name : beanDefinitionNames){ System.out.println(name); } } }
结果:
补充下: 可以@Import多个 ”,“解决。
4. ImportSelector接口 (跟 @Import注解一样的,只不过这个是原生api罢了)
实体类1:
public class MemberEntity { }
实体类2:
public class PersonEntity { }
原生接口的实现:
public class MyImportSeletcor implements ImportSelector { /** * 注解信息 * @param annotationMetadata * @return */ @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{"com.toov5.config.beanTest.MemberEntity", "com.toov5.config.beanTest.PersonEntity"}; } }
启动类:
@Configuration @Import(MyImportSeletcor.class) public class MyConfig { }
测试类:
public class test { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String name : beanDefinitionNames){ System.out.println(name); } } }
结果:
5. ImportBeanDefinitionRegister接口 手动往ioc 注入bean
Spring容器中 Bean的信息,都是由BeanDefinition描述的
可以看下:
各种方法,Bean的各种信息。
实体类:
public class PersonEntity { }
接口实现,手动注入实体类
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { /** * 注解信息 * @param annotationMetadata * @param beanDefinitionRegistry */ @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { //手动注册到ioc容器中 RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(PersonEntity.class); //IOC源码里面肯定有这个的!!! 对象放到IOC容器中就叫注册 (底层是个map 线程安全的) beanDefinitionRegistry.registerBeanDefinition("personEntity", rootBeanDefinition); } }
启动类:
@Configuration @Import(MyImportBeanDefinitionRegistrar.class) public class MyConfig { }
测试类:
public class test { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String name : beanDefinitionNames){ System.out.println(name); } } }
运行结果:
6. 基于FactoryBean
FactoryBean也可以用来注册Bean
启动方式千万万,随便使用:
实体类:
public class PersonEntity { }
FactoryBean:
public class MyFactoryBean implements FactoryBean<PersonEntity> { @Override public PersonEntity getObject() throws Exception { return new PersonEntity(); } @Override public Class<?> getObjectType() { return PersonEntity.class; } @Override public boolean isSingleton() { //默认情况下是true。单例 return true; } //往ioc容器中注入对象 }
启动类:
@Configuration public class MyConfig { @Bean public MyFactoryBean myFactoryBean(){ return new MyFactoryBean(); } }
测试类:
public class test { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); PersonEntity personEntity1 = (PersonEntity)applicationContext.getBean("myFactoryBean"); PersonEntity personEntity2 = (PersonEntity)applicationContext.getBean("myFactoryBean"); System.out.println(personEntity1 == personEntity2); } }
自己写“抄”个注解玩玩
自定义一个注解:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Component public @interface toov5 { }
实体类:
@toov5 public class Hello { }
启动采用扫包的方式:扫包的范围
@Configuration @ComponentScan("com.toov5.config.beanTest.entity") public class MyConfig { }
测试:
public class test { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String name : beanDefinitionNames){ System.out.println(name); } } }
测试结果:
小结: spring 注入bean到容器方式:
@Bean @Import ,一般用于外部的jar包
其他的 @Service @Repository 注入对象底层其实都一样,就是区分不同的场景。使用的时候需要@ComponentScan注解扫包
还有就是实现一系列相应的接口去实现注入Bean 的方式
如下:
-
注解
- @Bean
- @Import
- @EnableXXX(实质@Import)
-
接口
- condition接口 + @Conditional
- ImportSelector接口 + @Bean、@Import
- ImportBeanDefinitionRegister接口+@Bean、@Import
- FactoryBean接口+ @Bean、@Import