Spring学习笔记-02.Spring 实体的注入

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 {
/**
* 此处写一个Demo 只加载Controller的代码
* metadataReader 读取当前正在扫描的类的信息
* metadataReaderFactory 可以获得其他类的所有信息
*/
@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 {

/**
*
* @param context 判断上下文的环境
* @param metadata 注解信息
* @return
*/
@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 {
/**
*
* @param context 判断上下文的环境
* @param metadata 注解信息
* @return
*/
@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 {
/**
* 返回值
* 返回想注入的类的列表
* @param importingClassMetadata
* @return
*/
@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 {
/**
* 把所有需要添加到容器的bean手工注入到容器中
*
* @param importingClassMetadata 当前类的注册信息
* @param registry BeanDefinition注册类
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
/**
* 当前写的Demo的例子是,只有dog和cat类都有了,才手工注入pig类。
* 使用的场景很少,也很贼,不太建议项目中使用
*/
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 {
/**
* Import 导入类
*/

/**
* 创建一个Eal的类的FacrotyBean,注入的Eal的对象的实例是经过工厂处理的
* @return
*/
@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中方式:

  1. 直接赋值
  2. 通过SPEL表达式
  3. 读取配置文件中的值
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

  1. 使用Qualifier注解,选择一个对象的名称,通常比较常用

  2. Primary可以理解为默认优先选择,不可以同时设置多个,内部实质是设置BeanDefinition的primary属性