什么是自动装配
自动装配就是让应用程序上下文为你找出依赖项的过程。说的通俗一点,就是Spring会在上下文中自动查找,并自动给bean装配与其关联的属性!
spring中实现自动装配的方式有两种,一种是通过xml文件、另一种是通过注解。
这里讲的是通过注解@Autowired实现的,xml的我也不会~
上面是网上对自动装配的理解,换做我来说的话,大概是这么捞的…
@Autowired的作用就是将在容器中的对象找出来给你 作用类似 new一个对象获取一个对象 当然默认单实例每次拿到都是同一个
@Autowired的小总结
@Autowired的装配总结:
1.默认根据组件类型去容器里面获取 等同于annotationcontext.getBean(Person.class);
2.如果根据类型找到多个bean
比如你person本身标注了@component然后又在配置了@bean注入了一个person02,
他就会根据id去匹配容器中的对象 id默认是@Autowired Person person22 默认id是person22(我原本以为是类型首字母小写呢)
3.可以使用@Qualifier("person222")去强制限定id为这个person222 标注方式
@Qualifier("person222")
@Autowired Person person22
4.默认@Autowired使用了但是没找到对应的bean会报错 可以使用
@Autowired(required=false)使得不一定必须,没有就拉倒
5.标注@Primary,作用是如果程序需要这个类型的bean,优先使用这个使用方式
@Primary
@Bean(value = "person222")
public Person myPerson(){
return new Person("haha",20,"广东");
}
总结起来:@Autowired优先级就是这样的
直接根据类去获取,找到一个就直接装配
找到超过一个的时候:
1.@Qualifier("person222")--->
2.@Primary标注的 -->
3.根据组件的id装配(默认id和人为设置)
除了@Autowired之外还有两个注解也可以实现自动装配的功能,他们之间的区别
JSR250规范@resource
功能和@Autowired接近 但是没有@primary和required=false的功能
JSR330规范@inject
功能和@Autowired更接近,
但是需要导依赖(导包)有@primary功能没有required=false的功能
并且:这两个规范是java定义的自动装配,
脱离了spring还能够在别的ioc容器使用,而
@Autowired是spring的规范,脱离了spring就无法使用。
<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
@inject的依赖
@Autowired还可以标注在方法、参数、构造器上,分别有什么作用
标注在方法上
@Component
public class Father {
Son son;
public Father() {
}
@Override
public String toString() {
return "Father{" +
"son=" + son +
'}';
}
public Son getSon() {
return son;
}
@Autowired
public void setSon(Son son) {
this.son = son;
}
}
标注在构造函数上
@Component
public class Father {
Son son;
@Autowired
public Father(Son son) {
this.son=son;
}
@Override
public String toString() {
return "Father{" +
"son=" + son +
'}';
}
public Son getSon() {
return son;
}
public void setSon(Son son) {
this.son = son;
}
}
标注在参数上
@Bean
public Father father(@Autowired Son son){
Father father = new Father();
father.setSon(son);
return father;
}
测试类
AnnotationConfigApplicationContext as = as();
Father father = as.getBean(Father.class);
Son son = as.getBean(Son.class);
System.out.println(father);
System.out.println(son);
结果都是一样的:
Father{son=com.hjj.Bean.Son@15761df8}
com.hjj.Bean.Son@15761df8
这里说明,都是从容器里面获取的 标注了@Autowired
而且可以省略
小总结:我也不清楚...意思是在各种地方使用这个注解,
都是从ioc容器获取bean对象,然后很多都可以省略 莫得了
Aware接口的作用
aware接口的作用就是传递一些spring底层的组件到bean里面供开发者使用
业务按需要实现特定的Aware接口,spring容器会主动找到该bean,
然后调用特定的方法,将特定的参数传递给bean;
比如下面这个Father类 实现了几个aware接口,重写了他们的方法 每个方法都在构造之后初始化之前调用
因为他们是通过这个bean的后置处理器去调用的
下面是bean以及测试结果
@Component
public class Father implements EmbeddedValueResolverAware, BeanNameAware, ApplicationContextAware {
Son son;
ApplicationContext applicationContext;
public Father(ApplicationContext applicationContext) {
this.applicationContext=applicationContext;
}
@Override
public String toString() {
return "Father{" +
"son=" + son +
", applicationContext=" + applicationContext +
'}';
}
public Son getSon() {
return son;
}
public void setSon(Son son) {
this.son = son;
}
@Override
public void setBeanName(String s) {
System.out.println("当前bean的名字"+s);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("获取到的ioc容器对象"+applicationContext);
}
@Override
public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
String s = stringValueResolver.resolveStringValue("你好${os.name}我是#{25*4},我住在${person.address}");
System.out.println("解析的字符串"+s);
}
}
@Test
public void test01(){
AnnotationConfigApplicationContext as = as();
Father father = as.getBean(Father.class);
Son son = as.getBean(Son.class);
System.out.println(father);
System.out.println(son);
}
测试结果
测试结果:
当前bean的名字father
解析的字符串你好Windows 10我是100,我住在广东省
获取到的ioc容器对象org.springframework.context.annotation.AnnotationConfigApplicationContext@19e1023e: startup date [Tue Jun 02 13:43:42 CST 2020]; root of context hierarchy
Father{son=null, applicationContext=org.springframework.context.annotation.AnnotationConfigApplicationContext@19e1023e: startup date [Tue Jun 02 13:43:42 CST 2020]; root of context hierarchy}
com.hjj.Bean.Son@4c9f8c13
上面三个aware的接口作用,EmbeddedValueResolverAware, BeanNameAware, ApplicationContextAware
EmbeddedValueResolverAware是初始化前将一个字符串值解析器传入bean内
BeanNameAware在bean初始化前将bean在容器的id(名字)传进bean
ApplicationContextAware在bean初始化前将ioc容器对象传进bean里面
所以可以看出来 aware接口的作用就是传递一些spring底层的组件到bean里面供开发者使用
另外:EmbeddedValueResolverAware可以解析什么,可以使用${}获取环境变量,包括配置文件的key-value
因为前面说了吧 配置文件的键值对是加载到环境变量里面的,所以能获取环境变量就能获取配置文件
但是首先配置文件得先加载进容器里,你可以使用
@PropertySource(value = {"classpath:/person.properties"})在配置类上标注
或者xml的方式加载配置文件
<context:property-placeholder location="classpath:person.properties"/>
我们拿ApplicationContextAware作为例子 看看bean的后置处理器是如何将对象传给bean的
try {
/********构造bean并且赋值之类的********/
this.populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
/**********初始化****************/
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
}
这里就是前面讲bean的生命周期函数的时候后置处理器那里分析过的内容 先构造 然后初始化
初始化里面还有嵌套的流程
后置处理器的预处理-->调用初始化方法--->后置处理器的后处理
if (mbd == null || !mbd.isSynthetic()) {
/*******************后置处理器的预处理**************/
wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
}
try {
/*****************调用初始化方法******************/
this.invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable var6) {
throw new BeanCreationException(mbd != null ? mbd.getResourceDescription() : null, beanName, "Invocation of init method failed", var6);
}
if (mbd == null || !mbd.isSynthetic()) {
/***************后置处理器的后处理****************/
wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
先循环迭代遍历所有的后置处理器的预处理部分
Iterator var4 = this.getBeanPostProcessors().iterator();
do {
if (!var4.hasNext()) {
return result;
}
BeanPostProcessor beanProcessor = (BeanPostProcessor)var4.next();
/*********在这个方法里面调用aware接口判断的 方法**************/
result = beanProcessor.postProcessBeforeInitialization(result, beanName);
} while(result != null);
/***********这里做aware接口的判断*************/
this.invokeAwareInterfaces(bean);
/**********这里判断bean实现了哪个aware接口然后传相应的组件********/
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
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是实现的哪个接口 就将哪个接口要传的参数传过去 比如这里是执行这句
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware)bean).setApplicationContext(this.applicationContext);
}
简洁版
上面的方法栈调用分析的看得麻烦就看这个流程总结吧:(我压根觉得这不配叫源码分析- -所以叫他方法栈调用顺序分析)
1.this.populateBean(beanName, mbd, instanceWrapper);构造bean对象
2.exposedObject = this.initializeBean(beanName, exposedObject, mbd);初始化对象
点进2里面看
2.1wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
2.2 this.invokeInitMethods(beanName, wrappedBean, mbd);
2.3wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
2.1后置处理器的预处理--》2.2调用初始化方法--》后置处理器的后处理
点进2.1 applyBeanPostProcessorsBeforeInitialization里面看
2.1.1一个迭代器遍历(视频的版本是for循环 一样的效果)遍历执行后置处理器的预处理
result = beanProcessor.postProcessBeforeInitialization(result, beanName);
再点进2.1.1看看遍历的时候后置处理器的预处理逻辑
postProcessBeforeInitialization里面是调用了这个方法
this.invokeAwareInterfaces(bean);
再点进去看看里面做了很多这种判断
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware)bean).setApplicationContext(this.applicationContext);
}
如果这个bean实现了ApplicationContextAware就将applicationContext传到bean里面
哦吼 完事了
然后我其实自己也没怎么理解(甚至不知道这些东西的使用场景,感觉学了跟没学一样,希望哪个路过的大佬救救我!!感觉这样学没什么进步)
然后我其实自己也没怎么理解(甚至不知道这些东西的使用场景,感觉学了跟没学一样,希望哪个路过的大佬救救我!!感觉这样学没什么进步)
@profile的使用
给bean或者配置类上标注一个标志 可以说是一个环境…比如说开发环境、测试环境、生产环境
配置类:里面注入了三个数据源,然后用@profile设置不同的标志
@Configuration
public class MyProfileConfig {
@Profile("test")
@Bean
public DataSource dataSourceTest() throws PropertyVetoException {
ComboPooledDataSource dataSource=new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
return dataSource;
}
@Profile("Dev")
@Bean
public DataSource dataSourceDev() throws PropertyVetoException {
ComboPooledDataSource dataSource=new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test01");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
return dataSource;
}
@Profile("Pro")
@Bean
public DataSource dataSourcePro() throws PropertyVetoException {
ComboPooledDataSource dataSource=new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/testttt");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
return dataSource;
}
}
测试类版本1:创建ioc容器的时候构造函数使用带参数的,然后运行的时候修改jvm参数:
-Dspring.profiles.active=test
测试类1:
public class ProfileTest {
public AnnotationConfigApplicationContext as(){
AnnotationConfigApplicationContext annotationcontext = new AnnotationConfigApplicationContext(MyProfileConfig.class);
return annotationcontext;
}
@Test
public void test01(){
AnnotationConfigApplicationContext as = as();
String[] beanNamesForType = as.getBeanNamesForType(DataSource.class);
for(String s:beanNamesForType){
System.out.println(s);
}
}
}
测试类版本2:创建ioc容器使用无参构造 然后再去设置它的环境参数
public class ProfileTest {
public AnnotationConfigApplicationContext as(){
/*使用无参构造...*/
AnnotationConfigApplicationContext annotationcontext = new AnnotationConfigApplicationContext();
/*设置jvm参数*/
annotationcontext.getEnvironment().setActiveProfiles("Dev","test");
/*设置配置类*/
annotationcontext.register(MyProfileConfig.class);
/*刷新容器*/
annotationcontext.refresh();
return annotationcontext;
}
@Test
public void test01(){
AnnotationConfigApplicationContext as = as();
String[] beanNamesForType = as.getBeanNamesForType(DataSource.class);
for(String s:beanNamesForType){
System.out.println(s);
}
}
}
这里当时没有复制测试结果,反正意思就是使用@profile来使得某些bean或者标注在配置类上,这个配置类这个bean只有在是这个环境(有点类似条件判断,但是只是判断运行的时候jvm的某一个参数,@conditional可以判断更多的条件吧)
用来切换生产环境和测试环境等等