IOC的使用
@Configuration是入口
在Spring中,在一个类的上面增加@Configuration
注解,这个就表示当前为配置类,可以代替xml
形式的配置形式。在类中可以定义各种bean的定义,这些定义会被AnnotationConfigApplicationContext
容器扫描。
1 2 3 4 5
| @Configuration public class RzConfig {
}
|
注入的@Configuration
对象可以通过configApplicationContext.getBeanDefinitionNames()
方法获得对应的集合
1 2 3 4 5
| AnnotationConfigApplicationContext configApplicationContext=new AnnotationConfigApplicationContext(RzConfig.class); String[] beanDefinitionNames = configApplicationContext.getBeanDefinitionNames(); for (int i = 0; i < beanDefinitionNames.length; i++) { RzLogger.info("获得注入的类:"+beanDefinitionNames[i]); }
|
1 2 3 4 5 6
| 23:24:18,150 INFO [main] (RzLogger.java:12) - 获得注入的类:org.springframework.context.annotation.internalConfigurationAnnotationProcessor 23:24:18,154 INFO [main] (RzLogger.java:12) - 获得注入的类:org.springframework.context.annotation.internalAutowiredAnnotationProcessor 23:24:18,154 INFO [main] (RzLogger.java:12) - 获得注入的类:org.springframework.context.annotation.internalCommonAnnotationProcessor 23:24:18,154 INFO [main] (RzLogger.java:12) - 获得注入的类:org.springframework.context.event.internalEventListenerProcessor 23:24:18,154 INFO [main] (RzLogger.java:12) - 获得注入的类:org.springframework.context.event.internalEventListenerFactory 23:24:18,154 INFO [main] (RzLogger.java:12) - 获得注入的类:rzConfig
|
@Bean的使用
@Bean
代表一个类实体,可以定义一个Id
,类似代码:
1 2 3
| <beans> <bean id="xxx" class="com.xxx.xxx"/> </beans>
|
可以在@Configuration
定义一个@Bean
对象:
1 2 3 4
| @Bean public Person getPerson() { return new Person(); }
|
扫描出来的实例:
1 2 3 4 5 6 7 8
| 23:31:50,092 INFO [main] (RzLogger.java:12) - 获得注入的类:org.springframework.context.annotation.internalConfigurationAnnotationProcessor 23:31:50,094 INFO [main] (RzLogger.java:12) - 获得注入的类:org.springframework.context.annotation.internalAutowiredAnnotationProcessor 23:31:50,094 INFO [main] (RzLogger.java:12) - 获得注入的类:org.springframework.context.annotation.internalCommonAnnotationProcessor 23:31:50,094 INFO [main] (RzLogger.java:12) - 获得注入的类:org.springframework.context.event.internalEventListenerProcessor 23:31:50,095 INFO [main] (RzLogger.java:12) - 获得注入的类:org.springframework.context.event.internalEventListenerFactory 23:31:50,095 INFO [main] (RzLogger.java:12) - 获得注入的类:rzConfig 23:31:50,095 INFO [main] (RzLogger.java:12) - 获得注入的类:getPerson
|
对于Spring中的@Bean
来说,如果一般不特殊指定Id,id是获得对象的方法名。
对于容器中创建的对象,都是单例模式。在容器创建完成的时候,就已经把实例创建完成了。 可以通过getBean
获取多次实例,来证明是不是同一个对象:
1 2 3
| Person person = configApplicationContext.getBean(Person.class); Person person1 = configApplicationContext.getBean(Person.class); System.out.println(person==person1);
|
运行结果为true
@Lazy实现懒加载
在创建Bean对象的时候,在Person
类的构造函数中增加一个打印log的日志:
1 2 3
| public Person(){ RzLogger.info("创建Person实例"); }
|
打印结果:
1 2 3 4 5
| 创建Person实例 .... 23:37:13,800 INFO [main] (RzLogger.java:12) - 获得注入的类:rzConfig 23:37:13,800 INFO [main] (RzLogger.java:12) - 获得注入的类:getPerson true
|
可以看出,并不是在bean使用的时候才创建类的对象,而是使用已经创建好的类。
@Lazy
从字面上意思可以理解为懒加载,为了就是在使用的时候,才真正的创建实例(不推荐使用)。
1 2 3 4 5
| @Lazy @Bean public Person getPerson() { return new Person(); }
|
1 2 3 4 5 6
| ..... 23:45:17,716 INFO [main] (RzLogger.java:12) - 获得注入的类:rzConfig 23:45:17,717 INFO [main] (RzLogger.java:12) - 获得注入的类:getPerson 23:45:17,745 INFO [main] (RzLogger.java:12) - 创建Person实例 true
|
@Scope实现多例
@Scope
代表对象在spring容器(IOC容器)中的生命周期,也可以理解为对象在spring容器中创建方式。 实现的方式有很多:
1 2 3 4 5 6 7 8 9 10
| @Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE) 这个是说在每次注入的时候回自动创建一个新的bean实例 @Scope(value=ConfigurableBeanFactory.SCOPE_SINGLETON) 单例模式,在整个应用中只能创建一个实例 @Scope(value=WebApplicationContext.SCOPE_GLOBAL_SESSION) 全局session中的一般不常用 @Scope(value=WebApplicationContext.SCOPE_APPLICATION) 在一个web应用中只创建一个实例 @Scope(value=WebApplicationContext.SCOPE_REQUEST) 在一个请求中创建一个实例 @Scope(value=WebApplicationContext.SCOPE_SESSION) 每次创建一个会话中创建一个实例
proxyMode=ScopedProxyMode.INTERFACES创建一个JDK代理模式 proxyMode=ScopedProxyMode.TARGET_CLASS基于类的代理模式 proxyMode=ScopedProxyMode.NO(默认)不进行代理
|
在@Bean注解上在增加一个@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
注解,可以实现多实例的注入:
1 2 3 4 5
| @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Bean public Person getPerson() { return new Person(); }
|
执行结果:
1 2 3 4 5 6 7
| ..... 23:53:21,543 INFO [main] (RzLogger.java:12) - 获得注入的类:rzConfig 23:53:21,544 INFO [main] (RzLogger.java:12) - 获得注入的类:getPerson 23:53:21,583 INFO [main] (RzLogger.java:12) - 创建Person实例 23:53:21,587 INFO [main] (RzLogger.java:12) - 创建Person实例 false
|
@ComponentScan
@ComponentScan
注解是告诉Spring 从哪里可以找到Bean,一旦被指定了,Spring将会将在被指定的包及其下级的包(sub packages)中寻找bean。
1
| @ComponentScan(value = "learnJava.spring.*")
|
Filter
如果说@ComponentScan
告诉了在哪能够找到Bean
,则Filter
是告诉如何去注入@Bean
. 例如:
1 2 3 4 5
| ANNOTATION:注解类型 ASSIGNABLE_TYPE:ANNOTATION:指定的类型 ASPECTJ:按照Aspectj的表达式,基本上不会用到 REGEX:按照正则表达式 CUSTOM:自定义规则
|
使用CUSTOM
规则的时候,需要实现TypeFilter
接口。例如, 加载Controller
结尾的类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class SpRzTypeFilters implements TypeFilter {
@Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); ClassMetadata classMetadata = metadataReader.getClassMetadata(); Resource resource = metadataReader.getResource(); String clazzName=classMetadata.getClassName(); RzLogger.info("自定义Type获得当前类的信息:",clazzName); if(clazzName.endsWith("Controller"))return true; return false; } }
|
1 2 3 4 5 6 7 8
| @Configuration @ComponentScan(value = "learnJava.spring.*",includeFilters = { @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {SpRzTypeFilters.class}) },useDefaultFilters = false) public class RzConfig02 {
}
|
@Conditional实现条件注入
@Conditional
可以根据Condition
的返回值来决定要不要注入当前变量
Condition
是一个函数式接口,内部只有一个matches方法。Spring是否注入,根据这个函数的返回值true/false
.
一个场景
如果我们想实现一个在Linux和Window下分别加载不同的Config的功能。首先我们先实现两个Condition
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class LinuxCondition implements Condition {
@Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); ClassLoader classLoader = context.getClassLoader(); Environment environment = context.getEnvironment(); BeanDefinitionRegistry registry = context.getRegistry(); RzLogger.infoObject(context); RzLogger.infoObject(metadata); String env=environment.getProperty("os.name"); if(env.contains("Linux")){ return true; } return false; } }
public class WinCondition implements Condition {
@Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); ClassLoader classLoader = context.getClassLoader(); Environment environment = context.getEnvironment(); BeanDefinitionRegistry registry = context.getRegistry(); RzLogger.infoObject(context); RzLogger.infoObject(metadata); String env=environment.getProperty("os.name"); if(env.contains("Windows")){ return true; } return false; } }
|
在@Bean的注解的基础上再增加一个Condition
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Configuration public class RzConfig07 {
@Bean("Windows") @Conditional({WinCondition.class}) public WinConfig getWinConfig(){ return new WinConfig(); } @Bean("Linux") @Conditional(value = {LinuxCondition.class}) public LinuxConfig getLinuxConfig(){ return new LinuxConfig(); }
}
|
当执行注入的时候,容器会根据String env=environment.getProperty("os.name");
来判断是加载哪一个Bean
@Import的用法
@Import
的注解是@Bean
注解的高级用法。@Import
提供了可以直接导入和高级导入的方式,也可以很简单的实现自定义注解实现注入对象,提供了第三方组件与Spring集成的方法。
ImportSelector导入数组类
使用可以单个直接导入
1 2 3
| @Import({Dog.class, Cat.class})
|
如果想导入的类一次性很多,需要使用 ImportSelector
接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class RzImportSelector implements ImportSelector {
@Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{ "learnJava.spring.sprz08.Fish" }; } }
|
导入时需要声明RzImportSelector
的类型:
1 2 3
| @Import({Dog.class, Cat.class, RzImportSelector.class})
|
ImportBeanDefinitionRegistrar的使用
如果在导入的时候,我们需要对类对象进项一些高级操作
,比如:C类对AB类都有依赖,如果必须要等到AB加载完,再加载C
ImportBeanDefinitionRegistrar
接口提供了registerBeanDefinitions
方法,该方法有两个参数:
AnnotationMetadata importingClassMetadata: 当前类的注册信息
BeanDefinitionRegistry registry:BeanDefinition:注册类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class RzImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyImportAnnotation.class.getName())); boolean dogRegister = registry.containsBeanDefinition("learnJava.spring.sprz08.Dog"); boolean catRegister = registry.containsBeanDefinition("learnJava.spring.sprz08.Cat"); if (dogRegister && catRegister) { RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Pig.class); MutablePropertyValues propertyValues = new MutablePropertyValues(); rootBeanDefinition.setPropertyValues(propertyValues); registry.registerBeanDefinition("pig", rootBeanDefinition); } } }
|
同样需要在入口出增加@Import({RzImportBeanDefinitionRegistrar.class})
自定义注解实现注入
自定义注解的使用,可以是第三方组件能够集成到Spring的框架中,可以基于RzImportBeanDefinitionRegistrar
简单的实现一个注解:
1 2 3 4 5
| @Retention(RetentionPolicy.RUNTIME) @Import({RzImportBeanDefinitionRegistrar.class}) public @interface MyImportAnnotation { }
|
使用的时候直接使用@MyImportAnnotation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Configuration @MyImportAnnotation public class RzConfig08 {
@Bean public RzFactoryBean getRzFactoryBean() { return new RzFactoryBean(); } }
|
FactoryBean的使用
在Spring创建对象的时候,一般都是都是通过反射的方式来创建,某些情况下创建实体的Bean的过程比较复杂,就需要允许调用方实现实例的创建方法。用户可以通过实现FactoryBean
接口来实现创建类实体的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class RzFactoryBean implements FactoryBean<Eal> { @Override public Eal getObject() throws Exception { RzLogger.info("创建Eal实例"); return new Eal(); } @Override public Class<?> getObjectType() { return Eal.class; } @Override public boolean isSingleton() { return true; } }
|
使用时与正常的类相同,直接用@Import
导入即可。
@Value的使用
@Value
主要是给字段赋值,主要有3中方式:
- 直接赋值
- 通过
SPEL
表达式
- 读取配置文件中的值
1 2 3 4 5 6 7 8
| @Value("Rz") private String userName; @Value("付威") private String userRealName; @Value("#{20-2}") private Integer age; @Value("${Author.version}") private String version;
|
几种注入的方式优先级 和不同
在注入的注解中,有几种方式:@Autowired
,@Resource
,@Inject
,对于他们需要了解一些使用上的区别:
@Autowired
:@Autowired
按byType自动注入(可以通过@Qualifier
实现ByName注入),通过AutowiredAnnotationBeanPostProcessor
类实现的依赖注入,可以再构造函数和Setter上面使用,注入的优先级最高,@Autowired(required = false)
支持没找到类不报错,推荐使用。
@Resource
:@Resource
默认按name注入,可以通过name和type属性进行选择性注入
@Inject
: @Inject
是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Named
@Qualifier和@Primary
如果在容器中有两个类的不同实现时,会产生Bean冲突的异常。使用@Qualifier和@Primary
可以定义优先使用的Bean
使用Qualifier注解,选择一个对象的名称,通常比较常用
Primary可以理解为默认优先选择,不可以同时设置多个,内部实质是设置BeanDefinition的primary属性