Spring中自动装配常用的注解和接口
这里写目录标题
一级目录
二级目录
三级目录
1、@Autowired、@Primary、@Qualifier
-
基本使用
这个可能是平时用到做多的注解了,我们平时用到最多的了,我们通过@Repository或者@Service等一系列注解将类注入容器后,我们就可以通过@Autowired注解来实践自动装配了,下面是简单的示例:
Dao
@Repository
public class CarDao {
//...
}
Service
@Service
public class CarService {
@Autowired
private CarDao carDao;
//...
}
难道@Autowired注解就只有这个一种用法吗?其实不然,我们先来看看@Autowired注解的源码
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
/**
* Declares whether the annotated dependency is required.
* <p>Defaults to {@code true}.
*/
boolean required() default true;
}
在@Target中我们可以看到,@Autowired注解还可以用在构造器、方法、参数以及set方法上。而且还发现有一个required参数默认是true,如果是false的话,就表示标有@Autowired的属性可以为空。下面是示例:
// 调用有参构造器来初始化
@Autowire
public CarService(CarDao carDao2) {
this.carDao = carDao2
}
public CarService(@Autowired CarDao carDao) {
this.carDao2 = carDao;
}
@Autowired
public void setCarDao(CarDao carDao) {
this.carDao = carDao;
}
-
原理
@Autowired注解在进行依赖注入的过程中,会先利用无参构造器创建这个类,然后再进行后置处理,将标有@Autowired的属性进行赋值以达到注入的目的。
在为属性进行赋值之前,先会根据属性的类(Class)去容器中找是否存在,如果根据类找出对个Bean,就会根据Bean的名字去匹配。如果通过Bean的名字也找不到就会报
NoUniqueBeanDefinitionException
的错误,对于这种情况,我们可以通过如下注解来解决问题:-
@Primary
@Primary @Bean("carDao1") public CarDao carDao() { return new CarDao("car111"); } @Bean("carDao2") public CarDao carDao() { return new CarDao("car222"); }
加上@Primary这个注解后,在又多个同类型的Bean的时候,就会优先注入名字是carDao1的这个对象
-
@Qualifier
@Service public class CarService { @Qualifier("carDao2") @Autowired private CarDao carDao2; //... }
加上@Qualifier注解后,就回优先注入名字是carDao2的Bean
-
2、@Resource
-
概述
注解@Resource的功能与@Autowired注解的作用是一样的,也是为了实现依赖注入而出来的,用法也很类似。它不是Spring的注解,但是Spring却支持这个注解。因此@Resource不能配合Spring中@Primary等注解进行配合使用。
-
原理
优先根据名字在容器中查找对应的Bean,如果找不到才会根据类进行查找
3、@Profile
-
概述
有时候我们在进行依赖注入的时候可能会根据注入合适的组件,比如DataSource——有在开发时使用的是dev库、测试的时候使用的是test库和最后上线后的正式环境的库。倘若我们在每次切换DataSource的时候都去手动的改代码中的配置然后重新编译其实是非常不优雅的,我们更希望的是系统能够自己通过环境的改变然后自动地去切换对应的DataSource。为此,Spring为我们提供了
@Profile
注解来满足我们的需求,使得Spring能够根据环境来实现动态地依赖注入。 -
基本使用
我们先看看@Profile的源码
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(ProfileCondition.class) public @interface Profile { /** * The set of profiles for which the annotated component should be registered. */ String[] value(); }
通过注释不难看出,这个注解里面就一个value属性,那就是配置有@Profile注解的组件在什么时候被注册。
下面为了简化,就不写DataSource了,而是用两个简单的Bean来演示其用法:
/** * @author VernHe * @date 2021年08月28日 20:44 */ @Configuration public class ProfileConfiguration { @Profile("test") @Bean public ProfileBean testBean() { return new ProfileBean(); } @Profile("dev") @Bean public ProfileBean devBean() { return new ProfileBean(); } }
上面这段代码的功能也很简单,就是在test环境下,注入testBean,在dev环境下注入devBean。
接下来的问题就是如何切换环境了,这里有两种方式去配置环境:****
-
配置JVM参数
-Dspring.profiles.active = 环境名
-
编码方式配置
所谓的编码方式配置环境其实是在初始化ApplicationContext对象的时候去设置环境,说道初始化,我们先看看我们平时是如何初始化的:我们使用最多的就是调用的是它的有参构造方法
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ProfileConfiguration.class)
我们先来看一下它和无参构造方法的区别
public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this(); register(componentClasses); refresh(); }
其实有参构造方法就是在refresh()之前先调用了无参构造方法创建对象,然后进行了组件的注册,我们现在想配置环境,只需要在refresh()之前加上这样一步即可,下面是示例:
// 创建applicationContext AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); // 注册组件 applicationContext.register(ProfileConfiguration.class,MyConfiguration_01.class); // 设置环境变量 applicationContext.getEnvironment().addActiveProfile("dev"); // 刷新启动容器 applicationContext.refresh();
-
4、XXXAware系列接口
-
概述
首先谈谈这个XXXAware接口的作用,很多时候,我们在使用Spring时不仅仅是简单的进行一个Bean的注册和配置依赖这么简单,为了应对一些比较复杂的业务情景,我们还需要让Bean能够使用到Spring中的各种组件,这个时候我们就可以通过一系列的XXXXAware接口来使用Spring中的各种服务。下面仅举例个别Aware接口:
-
BeanNameAware
可以获取到Bean的名字
public interface BeanNameAware extends Aware { /** * Set the name of the bean in the bean factory that created this bean. * <p>Invoked after population of normal bean properties but before an * init callback such as {@link InitializingBean#afterPropertiesSet()} * or a custom init-method. * @param name the name of the bean in the factory. * Note that this name is the actual bean name used in the factory, which may * differ from the originally specified name: in particular for inner bean * names, the actual bean name might have been made unique through appending * "#..." suffixes. Use the {@link BeanFactoryUtils#originalBeanName(String)} * method to extract the original bean name (without suffix), if desired. */ void setBeanName(String name); }
实现这个接口之后,可以通过实现setBeanName(String name)方法获取到Bean的名字
-
ApplicationContextAware
可以获取到容器的ApplicationContext
public interface ApplicationContextAware extends Aware { /** * Set the ApplicationContext that this object runs in. * Normally this call will be used to initialize the object. * <p>Invoked after population of normal bean properties but before an init callback such * as {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()} * or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader}, * {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and * {@link MessageSourceAware}, if applicable. * @param applicationContext the ApplicationContext object to be used by this object * @throws ApplicationContextException in case of context initialization errors * @throws BeansException if thrown by application context methods * @see org.springframework.beans.factory.BeanInitializationException */ void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
-
EmbeddedValueResolverAware
获取到SpEL表达式的解析器
public interface EmbeddedValueResolverAware extends Aware { /** * Set the StringValueResolver to use for resolving embedded definition values. */ void setEmbeddedValueResolver(StringValueResolver resolver); }
-
其他
BeanFactoryAware:获取当前bean factory这也可以调用容器的服务 ApplicationContextAware: 当前的applicationContext, 这也可以调用容器的服务 MessageSourceAware:获得message source,这也可以获得文本信息 applicationEventPulisherAware:应用事件发布器,可以发布事件, ResourceLoaderAware: 获得资源加载器,可以获得外部资源文件的内容;
-
-
使用示例
/** * @author VernHe * @date 2021年08月28日 15:17 */ @Component public class MyAware implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware { private ApplicationContext applicationContext; /** * 可以拿到ApplicationContext * @param applicationContext * @throws BeansException */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; System.out.println(this.applicationContext); } /** * 可以拿到bean的名字 * @param name */ @Override public void setBeanName(String name) { System.out.println("Current Name: " + name); } /** * 字符串解析器:可以解析SPEL表达式 * @param resolver */ @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { // 使用SpEL解析器 System.out.println(resolver.resolveStringValue("你好啊${os.name}")); } }
-
原理
说道XXXAware接口的原理就不得不说一下一个Bean实例的生命周期了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QEf4Lw1Q-1630291742297)(C:\Users\Administrator\Desktop\Spring中Bean实例的生命周期.png)]
我们可以清楚的看到,在一个对象被实例化然后设置好属性之后会去检查是否有XXXAware接口并设置相关依赖
下面我们看看这是如何实现的:
在实现XXXAware接口后,我们在set方法上打上断点然后进Debug,不难发现org.springframework.context.support.ApplicationContextAwareProcessor这个类
class ApplicationContextAwareProcessor implements BeanPostProcessor { //...... }
它实现了BeanPostProcessor这个方法,我们来看看它里面的内容:
@Override @Nullable public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware || bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware || bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)){ return bean; } AccessControlContext acc = null; if (System.getSecurityManager() != null) { acc = this.applicationContext.getBeanFactory().getAccessControlContext(); } if (acc != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { invokeAwareInterfaces(bean); return null; }, acc); } else { invokeAwareInterfaces(bean); } return bean; } private void invokeAwareInterfaces(Object bean) { if (bean instanceof EnvironmentAware) { ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof EmbeddedValueResolverAware) { ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver); } if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); } if (bean instanceof ApplicationEventPublisherAware) { ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); } if (bean instanceof MessageSourceAware) { ((MessageSourceAware) bean).setMessageSource(this.applicationContext); } if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); } }
代码的逻辑其实并不难,无非就是判断Bean实现了哪种接口然后调用其对应的set方法。