@Configuration
把一个类作为IoC容器,它的某个方法头上如果注册了@Bean,就是作为这个spring容器中的Bean。
创建一个person类,有name和age两个字段:
@Data
public class Person {
private String name;
private int age;
public Person(){}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
写一个类加上@Configuration注解,然后写一个创建Person对象的方法,方法加上@Bean注解。
@Configuration
public class MyConfig {
@Bean
public Person person(){
return new Person("Alan",18);
}
}
编写测试类:
public class MyTest {
@Test
public void test(){
ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
Object person = app.getBean("person");
System.out.println(person);
}
}
运行结果:

可以发现Person类已被AnnotationConfigApplicationContext扫描到。
查看MyConfig类的person方法,可以看到返回的是一个new的person对象,这个时候可能会有一个疑惑,如果多次调用getBean方法,会不会每次获取到的person对象都是new出来的对象?
这里改下代码来看看,调用两次getBean方法,比较下person和person2
public class MyTest {
@Test
public void test(){
ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
Object person = app.getBean("person");
Object person2 = app.getBean("person");
System.out.println(person);
System.out.println(person == person2);
}
}
结果:

可以看到,结果为true,表示每次扫描到的都是同一个bean。这里虽然person方法返回的是一个new的对象,但是getBean方法调用的时候并不是直接调用的该person方法,而是使用原型模式将person方法作为模板调用。
@ComponentScan
在配置类上添加@ComponentScan注解。该注解默认会扫描该类所在包下的所有配置类,相当于之前的<context: component-scan>。
编写自己MyConfig2类,加上ComponentScan注解。
@Configuration
@ComponentScan(value = "com.alan.demo")
public class MyConfig2 {
}
编写测试文件:
public class MyTest {
@Test
public void test(){
ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig2.class);
String[] beanDefinitionNames = app.getBeanDefinitionNames();
String sss = Arrays.toString(beanDefinitionNames)
.replaceAll("\\[|\\]","")
.replaceAll(", ","\n");
System.out.println(sss);
}
}
运行后的结果:

会发现已经扫描到的我配置包下的所有类。
这个注解是支持灵活的配置的,首先需要将useDefaultFilters配置为false然后,可以配置type=FilterType.ANNOTATION,表示扫描有特定注解的类:
includeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Controller.class})}
配置FilterType.ASSIGNABLE_TYPE,表示扫描特定类名的类:
includeFilters = {@Filter(type = FilterType.ASSIGNABLE_TYPE, value = {MyController.class})}
甚至可以配置一个自定义的filter,在这里filter里面就可以实现只扫描自己需要的特定类。比如这里编写一个自定义filter,继承TypeFilter,重写match方法,在match方法里面,判断类名是否包含er,包含则扫描,不包含就不扫描。
/**
*
* @param metadataReader 获取当前正在操作得类得信息
* @param metadataReaderFactory 获取上下文中所有的信息
* @return
* @throws IOException
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//获取扫描到的类的信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
String className = classMetadata.getClassName();
System.out.println("-------" + className + "--------");
if(className.contains("er")){
return true;
}
return false;
}
}
在@ComponentScan注解中加上includeFilters和useDefaultFilters配置。
@Configuration
@ComponentScan(value = "com.alan.project",
includeFilters = {@Filter(type = FilterType.CUSTOM, value = {MyFilter.class})},
useDefaultFilters = false
)
public class MyConfig2 {
}
运行测试文件MyTest后的结果:

可以看到,只有包含er的类才能被扫描到。
@Scope
用来表示类的作用域,有prototype,singleton,request,session这四个配置。prototype表示多例,singleton表示单例,request表示一个请求中是单例,session表示一次会话中是单例。
还是使用@Configuration注解中的例子,在person方法上加上@Scope注解,设置为prototype,再看看getBean获取的两个person是否指向同一个地址。
@Configuration
public class MyConfig {
@Scope("prototype")
@Bean
public Person person(){
return new Person("Alan",18);
}
}
运行@Test测试方法后的结果:

可以看到,两次调用getBean获取到的person对象不是用一个对象。
注意,@Scope注解不配置默认是单例。
@Lazy
延时初始化,表示只有使用的时候才会加载。
继续沿用@Configuration注解中的例子,在MyConfig类中加上一行注释。
@Configuration
public class MyConfig {
@Bean
public Person person(){
System.out.println("将person加载到IoC容器中");
return new Person("Alan",18);
}
}
在test方法初始AnnotationConfigApplicationContext后,加上一行注释,通过注释来观察一下person类加载的顺序。运行test方法后的结果:

可以看到,在IoC容器初始化之前,person类就已经被加载到IoC容器中。
然后在person方法上加上@Lazy注解后,看看执行的结果:
@Configuration
public class MyConfig {
@Lazy
@Bean
public Person person(){
System.out.println("将person加载到IoC容器中");
return new Person("Alan",18);
}
}

可以看到是先初始化了IoC容器,然后加载了Person。
这里需要注意的是@Lazy注解只对单例Bean起作用,对多例Bean是无法起作用的。
@Conditional
表示按照一定的条件进行判断,满足一定的条件才会被加载到IoC容器中。
这里编写两个自定义的条件类WinCondition和LinuxCondition,分别实现Condition类,重写match方法,在方法中,判断环境的运行系统,WinCondition中是window系统返回true,LinuxCondition中是Linux系统返回true。
public class WinCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
System.out.println(osName);
if (osName.contains("Windows")) {
return true;
}
return false;
}
}
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
System.out.println(osName);
if (osName.contains("Linux")) {
return true;
}
return false;
}
}
在MyConfig类中编写两个方法分别使用WinCondition和LinuxCondition两个注解。
@Configuration
public class MyConfig {
@Conditional(WinCondition.class)
@Bean
public Person alan() {
System.out.println("将Alan加载到IoC容器中");
return new Person("Alan", 20);
}
@Conditional(LinuxCondition.class)
@Bean
public Person yilan() {
System.out.println("将Yilan加载到IoC容器中");
return new Person("Yilan", 18);
}
}
运行测试方法:

由于当前电脑环境是windows,如果加上了WinCondition注解Bean被加载到了Ioc容器中。
@Import
用来导入外部资源。
方法一:新创建一个Cat类,然后在MyConfig上加入注解@Import,配置Cat类。
public class Cat {
}
@Configuration
@Import(Cat.class)
public class MyConfig {
@Lazy
@Bean
public Person person(){
System.out.println("将person加载到IoC容器中");
return new Person("Alan",18);
}
}
运行测试文件后,看到结果:

Cat类已被加载进去了。
方法二:除了直接在标签内导入某个类,还可以使用自定义的selector实现导入多个类。新建两个要导入的类Company和Employee。
public class Company {
}
public class Employer {
}
这里写一个MyImportSelector实现ImportSelector,重写selectImports方法,方法返回的字符串数组改成要返回的类的全路径名。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.alan.project.entity.Company",
"com.alan.project.entity.Employer"};
}
}
注解上加入MyImportSelector
@Import({Cat.class, MyImportSelector.class})
Test文件:
public class MyTest {
@Test
public void test(){
ApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
String[] beanDefinitionNames = app.getBeanDefinitionNames();
String sss = Arrays.toString(beanDefinitionNames)
.replaceAll("\\[|\\]","")
.replaceAll(", ","\n");
System.out.println(sss);
}
}
运行结果:

可以看到MyImportSelector中返回的类也被加载到了容器中。
**方法三:**最后还有一种方法,使用自定义的实现了ImportBeanDefinitionRegistrar的类。
这里新建User类:
public class User {
}
自定义的ImportBeanDefinitionRegistrar类重写了registerBeanDefinitions方法。方法里面判断Company和Employer有没有被注入进BeanDefinition,如果注入进去了就注入User类。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//如果容器里面包含了Company和Member这两个类,就把User注入到IoC容器中
boolean companyBean = registry.containsBeanDefinition("com.alan.project.entity.Company");
boolean employerBean = registry.containsBeanDefinition("com.alan.project.entity.Employer");
if (companyBean && employerBean) {
BeanDefinition beanDefinition = new RootBeanDefinition(User.class);
registry.registerBeanDefinition("User", beanDefinition);
}
}
}
注解上加入MyImportBeanDefinitionRegistrar注解
@Import({Cat.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
运行test方法可以看见,user类也被注入了。
