Spring注解开发不完全の指南V1.0
一、组件注册
@Configuration+@Bean给容器中注册组件
@Bean:给容器中注册一个Bean,类型为返回值的类型,id默认是用方法名。@Bean注解可以设置别名,当@Bean中设置得别名和方法名不同时,注解中的别名优先作为id。
@Configurable
public class MainConfig {
@Bean("person-sp")
public Person person(){
return new Person("隔壁老王",5);
}
}
测试:
@Test
public void test01(){
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfig.class);
Person bean=context.getBean(Person.class);
System.out.println(bean);
String[] names=context.getBeanNamesForType(Person.class);
for(String name:names)
System.out.println(name);
}
输出:
Person{name='隔壁老王', age=5}
person-sp
@ComponentScan自动扫描+指定扫描规则
测试:
@Test
public void test02(){
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfig.class);
//获取容器中的组件名称
String[] names=context.getBeanDefinitionNames();
for(String name:names)
System.out.println(name);
}
输出:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookController
bookDao
bookService
person-sp
ComponentScan
接口中有这样两个方法,在包扫描的时候只扫描想要的几个类或者排除掉不想扫描的类:
//扫描时包括哪些
Filter[] includeFilters() default {};
//扫描时排除哪些
Filter[] excludeFilters() default {};
可以看到返回值都是Filter类型的数组,根据FilterType的不同,我们可以选择类型也可以选择正则表达式或者注解等作为过滤的条件。
@ComponentScan(value="com.demo",excludeFilters={
@ComponentScan.Filter(type= FilterType.ANNOTATION,classes={Controller.class, Service.class})
})
输出:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookDao
person-sp
如果将excludeFilters
改成includeFilters
,再次运行会发现并不起作用,回想通过xml配置时,如果想只扫描某几个包,那么需要将useDefault
设置为false,因为默认是排除哪些包,同样的在注解中,我们也需要设置useDefaultFilters
为false。
@ComponentScan(value="com.demo",includeFilters={
@ComponentScan.Filter(type= FilterType.ANNOTATION,classes={Controller.class, Service.class})
},useDefaultFilters = false)
自定义TypeFilter指定过滤规则
FilterType.ANNOTATION:按照注解
FilterType.ASSIGNABLE_TYPE:按照指定的类型
@ComponentScan(value="com.demo",includeFilters={
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,classes = BookService.class),
@ComponentScan.Filter(type= FilterType.ANNOTATION,classes={Controller.class})
},useDefaultFilters = false)
FilterType.CUSTOM:自定义规则
@ComponentScan(value="com.demo",includeFilters={
@ComponentScan.Filter(type =FilterType.CUSTOM,classes = MyTypeFilter.class),
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,classes = BookService.class),
@ComponentScan.Filter(type= FilterType.ANNOTATION,classes={Controller.class})
},useDefaultFilters = false)
由于自定义的这个过滤规则返回的都是false,也就是说没有一个是匹配的,所以并没有打印bookDao等信息。如果我们把下面代码中的注解去掉,那么打印的结果又会不同了!
package com.demo.config;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.core.io.Resource;
import java.io.IOException;
public class MyTypeFilter implements TypeFilter {
/**
* @Date: 2020/4/26 17:47
* @Param metadataReader:读取到的当前正在扫描的类的信息
* @Param metadataReaderFactory:可以获取到其它任何的类信息
* @return: boolean
**/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {
//获取当前类注解的信息
AnnotationMetadata data=metadataReader.getAnnotationMetadata();
//获取当前正在扫描的类的类信息
ClassMetadata classMetadata=metadataReader.getClassMetadata();
//获取当前类资源(类的路径)
Resource resource=metadataReader.getResource();
String className = classMetadata.getClassName();
System.out.println("======"+className+"======");
//如果包含er那么可以被扫描进去
//if(className.contains("er"))
//return true;
return false;
}
}
输出:
======com.demo.MainTest======
======com.demo.bean.Person======
======com.demo.config.MyTypeFilter======
======com.demo.controller.BookController======
======com.demo.dao.BookDao======
======com.demo.service.BookService======
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
person-sp
FilterType.REGEX:使用正则表达式
FilterType.ASPECTJ:使用ASPECTJ表达式
@Scope:设置组件作用域
获取到的Bean默认是单实例,也就是无论获取多少次,获取到的都是同一个。我们可以通过@Scope注解进行作用域的修改。
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE:prototype
* @see ConfigurableBeanFactory#SCOPE_SINGLETON:singleton
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST:request
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION:session
prototype是多实例的,ioc创建完成之后获取这个bean的时候才会进行创建该bean。默认是singleton单实例的,ioc容器启动就会调用方法创建对象然后放到ioc容器中。后两个都是在web环境中的,暂不介绍。
测试:
public void test03(){
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfig2.class);
Person bean=context.getBean(Person.class);
Person bean2=context.getBean(Person.class);
//单实例打印true,多实例为false
System.out.println(bean==bean2);
}
MainConfig2代码:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class MainConfig2 {
@Scope("prototype")
@Bean("person")
public Person person(){
return new Person("刘某人",18);
}
}
@Lazy:bean懒加载
这是针对于单实例bean来说的,容器启动时先不创建对象,当第一次使用Bean的时候再创建对象然后初始化。
@Conditional:按照条件注册bean
接下来我们在添加两个人Tom和Mike,假设Tom喜欢用Windows,Mike喜欢Linux系统,如果是Windows系统,容器中注册Tom,如果是Linux系统则注册Mike。
测试:
public void test04(){
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfig2.class);
String[] beanNamesForType = context.getBeanNamesForType(Person.class);
for(String name:beanNamesForType)
System.out.println(name);
}
相关代码:
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory=context.getBeanFactory();
//获取当前环境信息
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if(property.contains("linux"))
return true;
return false;
}
}
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取到ioc使用的beanfactory
ConfigurableListableBeanFactory beanFactory=context.getBeanFactory();
//获取类加载器
ClassLoader classLoader = context.getClassLoader();
//获取当前环境信息
Environment environment = context.getEnvironment();
//获取到当前bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
if(property.contains("Windows"))
return true;
return false;
}
}
在MainConfig2.java中添加:
@Conditional(WindowsCondition.class)
@Bean("Tom")
public Person person01(){
return new Person("Tom",34);
}
@Conditional(LinuxCondition.class)
@Bean("Mike")
public Person person02(){
return new Person("Mike",29);
}
输出:
person
Tom
@Conditional还可以标注在类上。
@Import:给容器中快速导入一个组件
给容器中注册组件的方式:
-
适合自己写的类:包扫描+组件注解(@Controller/@Service等)
-
适合导入的第三方包里的组件:@Bean
-
快速给容器中导入:@Import,id默认是全类名
-
使用Spring提供的FactoryBean
我们在bean中创建几个实体类(可以是个空类),我这里写的是Tools和Animal,然后在MainConfig2这个类中使用@Import注解,最后测试打印容器中的Bean名称。
@Import({Tools.class, Animal.class})
测试输出:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
... ...
mainConfig2
com.demo.bean.Tools
com.demo.bean.Animal
person
Tom
@Import:使用ImportSelector
ImportSelector接口是个导入选择器,返回需要导入的组建的全类名数组。
@Import({Tools.class, Animal.class,MyImportSelector.class})
MyImportSelector:
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
@Override
//返回指就是导入到容器中的组件的全类名
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.demo.bean.Hospital","com.demo.bean.Animal"};
}
}
测试输出:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
... ...
mainConfig2
com.demo.bean.Tools
com.demo.bean.Animal
com.demo.bean.Hospital
person
Tom
@Import:使用ImportBeanDefinitionRegistrar
ImportBeanDefinationRegistrar也是一个接口,可以实现手动注册bean到容器中。
@Import({Tools.class, Animal.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
自定义ImportBeanDefinationRegistrar:
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
/**
* @Description:
* @Param importingClassMetadata: 当前类的注解信息
* @Param registry: BeanDefination注册类
* 把所有需要添加到容器的bean,调用BeanDefinitionRegistry.registerBeanDefinition手动注册
* @return: void
**/
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//指定bean定义信息,如类型
RootBeanDefinition beanDefinition=new RootBeanDefinition(cinema.class);
//注册一个bean,指定bean名称
registry.registerBeanDefinition("cinema",beanDefinition);
}
}
测试输出:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
... ...
person
Tom
cinema
使用FactoryBean注册组件
现在创建一个关于颜色的工厂Bean,ColorFactoryBean。
import org.springframework.beans.factory.FactoryBean;
public class ColorFactoryBean implements FactoryBean<Color> {
@Override
//返回一个Color对象,这个对象会添加到容器中
public Color getObject() throws Exception {
System.out.println("ColorFactoryBean...getObject..");
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
不要忘记在MainConfig2.java中添加:
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
测试:
@Test
public void test05(){
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfig2.class);
//获取容器中的组件名称
String[] names=context.getBeanDefinitionNames();
for(String name:names)
System.out.println(name);
Object bean=context.getBean("colorFactoryBean");
System.out.println("bean的类型"+bean.getClass());
}
输出结果:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
... ...
com.demo.bean.Hospital
person
Tom
colorFactoryBean
cinema
ColorFactoryBean...getObject..
bean的类型class com.demo.bean.Color
工厂Bean获取的是调用getObject()创建的对象,是Color本身这个类型。如果想知道工厂Bean是什么类型的需要加前缀&。
Object bean=context.getBean("&colorFactoryBean");
System.out.println("bean的类型"+bean.getClass());
二、生命周期
- 指定初始化和销毁方法:@Bean
- 通过Bean实现InitializingBean和DisposableBean
- JSR250:@PostConstruct依赖注入完成后进行初始化(对方法进行标注);@PreDestroy,bean移除之前执行,通知进行销毁工作
- BeanPostProcessor:bean的后置处理器
@Bean指定初始化和销毁方法
之前通过xml方式开发的时候,我们可以通过指定init-method
和destroy-method
实现。那么如果想要通过注解要怎么做呢?
首先我们创建一个Car实体类(无参构造方法+init+destroy)和MainConfigOfLifeCycle.java配置类。通过在@Bean注解中指定initMethod、destroyMethod来实现指定初始化和销毁方法。
@Configuration
public class MainConfigOfLifeCycle {
@Bean(initMethod = "init",destroyMethod = "destroy")
public Car car(){
return new Car();
}
}
测试:
public void test01(){
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成");
annotationConfigApplicationContext.close();
}
输出:
Car Constructor...
Car init....
容器创建完成
Car destroy...
上面使用单实例演示的,大家可以通过@Scope设置prototype
再来看看多实例有什么不同。
在这里小结一下:
- 创建:单实例,在容器启动的时候创建对象;多实例,在每次获取的时候创建对象。
- 初始化:对象创建完成,并赋值后,调用初始化方法。
- 销毁:单实例是在容器关闭的时候,多实例不会管理这个bean,容器不会调用销毁方法。
/*多实例*/
public void test01(){
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成");
Object bean = annotationConfigApplicationContext.getBean(Car.class);
annotationConfigApplicationContext.close();
}
测试输出:
容器创建完成
Car Constructor...
Car init....
InitializingBean和DisposableBean
首先定义一个Cat类实现这两个接口,然后记得在MainConfigOfLifeCycle中注册该Bean。
public class Cat implements InitializingBean, DisposableBean {
public Cat(){
System.out.println("Cat Constructor...");
}
@Override
public void destroy() throws Exception {
System.out.println("Cat destroy...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Cat afterPropertiesSet...");
}
}
测试输出:
Cat Constructor...
Cat afterPropertiesSet...
容器创建完成
Cat destroy...
@PostConstruct和@PreDestroy
public class Dog {
public Dog(){
System.out.println("Dog constructor...");
}
@PostConstruct
public void init(){
System.out.println("Dog...@PostConstruct...");
}
@PreDestroy
public void destroy(){
System.out.println("Dog...@@PreDestroy...");
}
}
测试输出:
Cat Constructor...
Cat afterPropertiesSet...
Dog constructor...
Dog...@PostConstruct...
容器创建完成
Dog...@@PreDestroy...
Cat destroy...
@BeanPostProcessor:后置处理器
该接口有两个方法:
- postProcessBeforeInitialization:在初始化之前工作
- postProcessAfterInitialization:在初始化之后工作
定义一个后置处理器:MyBeanPostProcessor。
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization..."+bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization..."+bean);
return bean;
}
}
测试输出:
postProcessBeforeInitialization...com.demo.config.MainConfigOfLifeCycle$$EnhancerBySpringCGLIB$$23e854c1@f8c1ddd
postProcessAfterInitialization...com.demo.config.MainConfigOfLifeCycle$$EnhancerBySpringCGLIB$$23e854c1@f8c1ddd
Cat Constructor...
postProcessBeforeInitialization...com.demo.bean.Cat@3901d134
Cat afterPropertiesSet...
postProcessAfterInitialization...com.demo.bean.Cat@3901d134
Dog constructor...
postProcessBeforeInitialization...com.demo.bean.Dog@65d6b83b
Dog...@PostConstruct...
postProcessAfterInitialization...com.demo.bean.Dog@65d6b83b
容器创建完成
Dog...@@PreDestroy...
Cat destroy...
三、属性赋值
@Value赋值
-
基本数值:@Value(“张三”)
-
使用spEL#{}:@Value("#{20-5}")
-
${}:取出配置文件中的值
先来看前两种:
//Person.java
@Value("张三")
private String name;
@Value("#{20+5}")
private Integer age;
//MainConfigOfPropertyValues.java
@Bean
public Person person(){
return new Person();
}
测试:
@Test
public void test01(){
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfigOfPropertyValues.class);
Person bean=context.getBean(Person.class);
System.out.println(bean);
}
输出:
Person{name='张三', age=25}
@PropertySource加载外部配置文件
在Person中加入一个新属性:sex,它的值通过加载外部配置文件Person.properties来实现赋值。
以前是用xml配置时,我们需要写下列这样一行:
<context:property-placeholder location="classpath:person.properties">
现在这个属性的来源通过@PropertySource实现,添加在MainConfigOfPropertyValues类上面。
//读取外部配置文件中的键值对保存到运行环境变量中
@PropertySource(value={"classpath:/person.properties"})
使用$赋值:
@Value("${person.sex}")
private String sex;
输出:
Person{name='张三', age=25, sex='female'}
四、自动装配
@Autowired @Qualifier @Primary
如果一个组件需要用到另外一个组件,那么我们可以使用@Autowired注解进行中自动注入,该注解默认优先按照类型去容器中找对应的组件,如果有多个相同类型的组件,那么将属性的名称作为组件的id去容器中查找。我们可以使用@Qualifier(“bookDao”)指定需要装配的id,而不是使用属性名。
public class BookService {
@Autowired
private BookDao bookDao;
}
在BookController、BookService分别自动装配BookService和BookDao。
扫描包:
@ComponentScan({"com.demo.service","com.demo.controller","com.demo.dao"})
测试:
@Test
/*测试autowired*/
public void test06(){
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
BookService bean = context.getBean(BookService.class);
BookDao bean1 = context.getBean(BookDao.class);
System.out.println(bean1);
System.out.println(bean);
}
输出:
com.demo.dao.BookDao@6973bf95
BookService{bookDao=com.demo.dao.BookDao@6973bf95}
注意:自动装配默认已经将属性赋值好,如果没有赋值那么就会报错,但是可以通过使用@Autowired(required=false)
去解决。
@Primary可以在多个相同类型的Bean下使用,代表首选的Bean是哪个。
@Primary
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao=new BookDao();
bookDao.setLabel("2");
return bookDao;
}
测试输出:
BookService [bookDao=BookDao [label=2]]
@Resource @Inject
这两个注解是Java规范中的,@Autowired是Spring中的注解。
@Resource:默认按照组件名称进行装配,不支持@Primary功能和@Autowired(required=false)。
使用@Inject注解还需要添加下列依赖:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
该注解和@Autowired的功能相同,但是没有required=false
功能。
方法、构造器位置的自动装配
@Autowired可以标注在构造器、参数、方法和属性上,在上一小节中仅仅展示了标注属性。
新建一个Boss类,Car类型的变量car作为属性之一,我们添加setter和getter方法。
...
private Car car;
@Autowired
public void setCar(Car car){
this.car=car;
}
...
我们将@Autowired注解标注在了set方法上,那么Spring容器创建对象的时候就会调用该方法,完成赋值,方法使用的参数car、自定义类型的值会从ioc容器中获取,也就是说Boss类中的这个Car和ioc容器中的Car类型的Bean是同一个。
测试:
Boss boss=context.getBean(Boss.class);
System.out.println(boss);
Car car=context.getBean(Car.class);
System.out.println(car);
输出:
Boss [car=com.demo.bean.Car@4034c28c]
com.demo.beam.Car@4034c28c
标注在构造器上时,构造器要用的组件也是从容器中获取的。如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,因为效果和加上@Autowired是一样的。
@Autowired
public Boss(Car car){
this.car=car;
System.out.println("Boss的有参构造器。。。。");
}
放在参数位置:
public Boss(@Autowired Car car){
this.car=car;
System.out.println("Boss的有参构造器。。。。");
}