Java配置是Spring4.x推荐的配置方式,可以完全替代xml配置。
通过两个注解@Configuration和@Bean来实现。
1、@Configuration作用于类上,可以理解为一个xml配置文件。
2、@Bean作用于方法上,可以理解为xml配置中的<bean>。
@Configuration
@ComponentScan("com.xw.test")
public class SpringConfig {
@Bean
public UserDao getUserDao() {
return new UserDaoImpl();
}
}
@Bean注解会自动把方法的名字设置为bean的名字,如上面的就会设置为getUserDao
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserDao userDao1 = (UserDao) context.getBean("getUserDao");
System.out.println(userDao1);
context.destroy();
}
@ComponentScan("com.xw.test")的作用:扫描包
在使用注解的方式时,必须配置
<context:annotation-config/>
作用:向 Spring 容器注册这 4 个BeanPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
RequiredAnnotationBeanPostProcessor
使用@Autowired注解,就必须事先在Spring容器中声明AutowiredAnnotationBeanPostProcessor。
使用@Resource 、@PostConstruct、@PreDestroy等注解就必须声明CommonAnnotationBeanPostProcessor。
使用@PersistenceContext注解,就必须声明PersistenceAnnotationBeanPostProcessor。
使用@Required的注解,就必须声明RequiredAnnotationBeanPostProcessor。
但是:<context:component-scan base-package=XX.XX/>包含了自动注入上述processor的功能。所以就不用写了。
@Autowired和@Resource的区别:
使用@Autowired时,先在容器中查询类型的bean(byType)
1、如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据。
2、如果查询的结果不止一个,那么@Autowired会根据名称来查找。
名称:是指的如下userDao这个名称,或者自己通过注解@Qualifier("getUserDao")来指定名称查找。
@Autowired
private UserDao userDao;
3、如果根据名称查询的结果为空,那么会抛出异常。解决方法时,使用required=false。
所以在使用的时候我们一般是在实现类中加@Service、@Repository,名字是默认的,如
@Repository
public class UserDaoImpl implements UserDao
如上面这种,名称就是类首字母小写userDaoImpl。
注意:像上面的例子,如果有多个实现类UserDaoImpl UserDaoImpl2,根据上面@Autowired的规则,根据类型查找的结果不止一个,且根据名称userDao来查找肯定也不匹配(一个是userDaoImpl,一个是userDaoImpl2),此时就像上面第三步一样,抛出异常,注入失败。此时就可以用@Qualifier("userDaoImpl")来区分。
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.xw.test.UserDao' available: expected single matching bean but found 2: userDaoImpl,userDaoImpl2
使用@Resource时,是根据名字来查找,如下:
@Resource
private UserDao userDao;
1、先查找是否有名称为userDao的bean。
2、如果没有找到,则看是否有name属性(@Resource name=“”),有则根据name查找。3、如果还没有找到,则根据类型来查找,如果没有找到或者找到多个则抛出异常。
疑惑:
第一种情况:一个在类上@Repository,一个在标有@Configuration类的方法上。
@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao {
@Bean("userDaoImpl")
public UserDao getUserDao2() {
return new UserDaoImpl2();
}
信息: Overriding bean definition for bean 'userDaoImpl' with a different definition: replacing [Generic bean: class [com.xw.test.UserDaoImpl];
这样是可以共存的,会有个如上Overriding的提示而已。
第二种情况:两个都在类上@Repository
@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao {
@Repository("userDaoImpl")
public class UserDaoImpl2 implements UserDao {
Exception in thread "main" org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.xw.test.SpringConfig]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'userDaoImpl' for bean class [com.xw.test.UserDaoImpl2] conflicts with existing, non-compatible bean definition of same name and class [com.xw.test.UserDaoImpl]
此时初始化报错。
两种情况都是注入两个名字一样的不同属于不同类的实例对象,但结果不一样。猜测:和@Configuration有关,有时间再看下源码。
=====================2018-06-26更新=====================
最近一直在看Spring的源码,了解了启动大概的流程,想起这个问题,debug跟了一下:
1、ioc容器启动会刷新容器,refresh();
2、refresh();中调用invokeBeanFactoryPostProcessors(beanFactory);执行BeanFactory后置处理器
3、Spring有一个内部的BeanFactory:
internalConfigurationAnnotationProcessor,类型为ConfigurationClassPostProcessor。
4、而且是实现了PriorityOrdered,优先级很高的后置处理器
5、排序好之后执行 invokeBeanDefinitionRegistryPostProcessors。
6、执行ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法
7、执行方法 processConfigBeanDefinitions(registry);
8、解析每一个标有@Configuration的类
9、调用 parser.parse(candidates);
10、parse中一直会调到 doProcessConfigurationClass
11、会去@ComponentScan("")中的路径下扫描包
12、最终会调到ComponentScanAnnotationParser的doScan(String... basePackages)方法
13、AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
会处理一些其他逻辑,primary,Lazy等
14、之后会检查候选的 checkCandidate(beanName, candidate)
15、这里就是会出现上面那个报错的地方了,如果现有的bean定义中没有才会返回true,最后会throw new ConflictingBeanDefinitionException,就是上面出现的错误了。
以上是都在扫描包的时候冲突的处理逻辑,还有一个是在@Bean中定义一个名字一样的,不会报错,只会提示,因为@Bean不在扫描包中处理,而是在扫描包之后再处理的,所以@Bean的会覆盖扫描中的bean定义信息。如下:
1、会读取配置类中的bean定义信息,然后load其中的bean定义信息。
2、会解析配置类中的beanMethod,然后调用loadBeanDefinitionsForBeanMethod
3、先判断现有定义的信息中是否有这个定义信息,有就在判断是否允许定义信息覆盖,不允许就会报错,允许就判断角色,也只会提示,再之后就是我们出现覆盖信息的地方了。
所以最终会出现两种不同的情况是因为扫描包和类中定义bean是分开解析的,扫描包不允许重复,@Bean的形式允许重复(也可设置不允许)。
=====================2018-06-26更新=====================
读取外部参数:通过注解@PropertySource("classpath:test.properties")读取指定的配置。
使用参数:
常用的方式一:通过@Value
@Value("${name}")
private String name;
@Value("${age}")
private String age;
常用的方式二:通过Environment
@Autowired
private Environment enviroment;
enviroment.getProperty("name")即可获取。
借此温习记录下spring的注解。