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

付威     2019-11-15   10907   31min  

IOC的使用

@Configuration是入口

在Spring中,在一个类的上面增加@Configuration注解,这个就表示当前为配置类,可以代替xml形式的配置形式。在类中可以定义各种bean的定义,这些定义会被AnnotationConfigApplicationContext容器扫描。

@Configuration
public class RzConfig 
{

}

注入的@Configuration对象可以通过configApplicationContext.getBeanDefinitionNames()方法获得对应的集合

AnnotationConfigApplicationContext configApplicationContext=new AnnotationConfigApplicationContext(RzConfig.class);
String[] beanDefinitionNames = configApplicationContext.getBeanDefinitionNames();
for (int i = 0; i < beanDefinitionNames.length; i++) {
    RzLogger.info("获得注入的类:"+beanDefinitionNames[i]);
}
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,类似代码:

<beans>
    <bean id="xxx" class="com.xxx.xxx"/>
</beans>

可以在@Configuration定义一个@Bean对象:

@Bean
public Person getPerson() {
    return new Person();
}

扫描出来的实例:

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获取多次实例,来证明是不是同一个对象:

Person person = configApplicationContext.getBean(Person.class);
Person person1 = configApplicationContext.getBean(Person.class);
System.out.println(person==person1);

运行结果为true

@Lazy实现懒加载

在创建Bean对象的时候,在Person类的构造函数中增加一个打印log的日志:

public Person(){
    RzLogger.info("创建Person实例");
}

打印结果:

创建Person实例
....
23:37:13,800  INFO [main] (RzLogger.java:12) - 获得注入的类:rzConfig
23:37:13,800  INFO [main] (RzLogger.java:12) - 获得注入的类:getPerson
true

可以看出,并不是在bean使用的时候才创建类的对象,而是使用已经创建好的类。

@Lazy从字面上意思可以理解为懒加载,为了就是在使用的时候,才真正的创建实例(不推荐使用)。

@Lazy
@Bean
public Person getPerson() {
    return new Person();
}
.....
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容器中创建方式。 实现的方式有很多:

@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)注解,可以实现多实例的注入:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean
public Person getPerson() {
    return new Person();
}

执行结果:

.....
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。

@ComponentScan(value = "learnJava.spring.*")

Filter

如果说@ComponentScan告诉了在哪能够找到Bean,则Filter是告诉如何去注入@Bean. 例如:

ANNOTATION:注解类型
ASSIGNABLE_TYPE:ANNOTATION:指定的类型
ASPECTJ:按照Aspectj的表达式,基本上不会用到
REGEX:按照正则表达式
CUSTOM:自定义规则

使用CUSTOM规则的时候,需要实现TypeFilter接口。例如, 加载Controller结尾的类。

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;
	}
}
@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类:

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

@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导入数组类

使用可以单个直接导入


@Import({Dog.class, Cat.class})   

如果想导入的类一次性很多,需要使用 ImportSelector接口

public class RzImportSelector implements ImportSelector {
	/**
	 * 返回值
	 * 返回想注入的类的列表
	 * @param importingClassMetadata
	 * @return
	 */
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		return new String[]{
				"learnJava.spring.sprz08.Fish"
		};
	}
}

导入时需要声明RzImportSelector的类型:


@Import({Dog.class, Cat.class, RzImportSelector.class}) 

ImportBeanDefinitionRegistrar的使用

如果在导入的时候,我们需要对类对象进项一些高级操作,比如:C类对AB类都有依赖,如果必须要等到AB加载完,再加载C

ImportBeanDefinitionRegistrar接口提供了registerBeanDefinitions方法,该方法有两个参数:
AnnotationMetadata importingClassMetadata: 当前类的注册信息
BeanDefinitionRegistry registry:BeanDefinition:注册类


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简单的实现一个注解:

@Retention(RetentionPolicy.RUNTIME)
@Import({RzImportBeanDefinitionRegistrar.class})
public @interface MyImportAnnotation {
	 
}

使用的时候直接使用@MyImportAnnotation


@Configuration
@MyImportAnnotation
public class RzConfig08 {
	/**
	 * Import 导入类
	 */
	
	/**
	 * 创建一个Eal的类的FacrotyBean,注入的Eal的对象的实例是经过工厂处理的
	 * @return
	 */
	@Bean
	public RzFactoryBean getRzFactoryBean() {
		return new RzFactoryBean();
	}
	
}

FactoryBean的使用

在Spring创建对象的时候,一般都是都是通过反射的方式来创建,某些情况下创建实体的Bean的过程比较复杂,就需要允许调用方实现实例的创建方法。用户可以通过实现FactoryBean接口来实现创建类实体的方法。


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. 读取配置文件中的值
@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属性

(本文完)

作者:付威

博客地址:http://blog.laofu.online

如果觉得对您有帮助,可以下方的RSS订阅,谢谢合作

如有任何知识产权、版权问题或理论错误,还请指正。

本文是付威的网络博客原创,自由转载-非商用-非衍生-保持署名,请遵循:创意共享3.0许可证

交流请加群113249828: 点击加群   或发我邮件 laofu_online@163.com

付威

获得最新的博主文章,请关注上方公众号