本文最后更新于:2022年5月10日 晚上
摘要:使用注解创建bean的四种方式、具体细节及拓展,涉及到的注解主要有:@Configuration、@Bean、@ComponentScan、@Scope、@Lazy、@Conditional、@Import,涉及到的借口主要有:Condition接口、TypeFilter接口、ImportSelector接口、ImportBeanDefinitionRegistrar接口、FactoryBean接口。
前言
Spring注解开发学习笔记。
Spring通过xml配置文件注注册组件
- 创建项目,导入spring-context依赖,使用lombok减少代码量,因此lombok依赖也导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.19</version> </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> <scope>provided</scope> </dependency> </dependencies>
|
- 定义一个bean:Person
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.shg.bean;
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;
@Data @NoArgsConstructor @AllArgsConstructor public class Person { private int age; private String name; }
|
- 创建xml配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.shg.bean.Person"> <property name="age" value="18"/> <property name="name" value="spring"/> </bean>
</beans>
|
- 从容器中获取bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.shg.test;
import com.shg.bean.Person; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class XMLTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); Person person = (Person) context.getBean("person"); System.out.println("person = " + person); } }
|
@Configuration和@Bean给容器中注册组件
- 创建配置类
- @Configuration 声明本类为配置类
- @Bean 给容器注册一个Bean;类型为返回值类型,id默认是用方法名作为id
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.shg.config;
import com.shg.bean.Person; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class MyConfig {
@Bean public Person person() { return new Person(20, "annotation"); }
@Bean(value = "p1") public Person person01() { return new Person(20, "annotation"); } }
|
- 从容器中获取bean
- getBeanNamesForType()方法可以根据类名获取所有bean的id
- 使用getBean方法获取bean实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.shg.test;
import com.shg.bean.Person; import com.shg.config.MyConfig; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Arrays;
public class AnnotationTest { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanNames = context.getBeanNamesForType(Person.class); Arrays.stream(beanNames) .map(name -> name + " = " + context.getBean(name)) .forEach(System.out::println); } }
|
@ComponentScan自动扫描组件
- 创建三个bean,分别使用注解@Repository、@Service、@Controller进行声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.shg.dao;
import org.springframework.stereotype.Repository;
@Repository public class UserDao { }
package com.shg.service;
import org.springframework.stereotype.Service;
@Service public class UserService { }
package com.shg.controller;
import org.springframework.stereotype.Controller;
@Controller public class UserController { }
|
- 创建配置类
- @ComponentScan注解的参数:
- value:指定要扫描的包
- excludeFilters = Filter[]:指定扫描的时候按照什么规则排除组件
- includeFilters = Filter[]:指定扫描的时候只需要哪些组件
- Filter[]数组中使用@Filter来具体指定按照什么规则
- type = FilterType.ANNOTATION:按照注解
- type = FilterType.ASSIGNABLE_TYPE:按照类型
- type = FilterType.ASPECTJ:使用ASPECTJ表达式
- type = FilterType.REGEX:使用正则指定
- type = FilterType.CUSTOM:自定义规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.shg.config;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller;
@Component
@ComponentScan(value = {"com.shg"}, includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}) }, useDefaultFilters = false) public class MyConfig1 { }
|
- 输出组件id
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.shg.test;
import com.shg.config.MyConfig1; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Arrays;
public class AnnotationTest1 { @Test public void test01() { ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig1.class); Arrays.stream(context.getBeanDefinitionNames()) .forEach(System.out::println);
} }
|
自定义Filter指定规则包含或排除组件
- 自定义一个Filter,实现TypeFilter接口,指定类名中包含User的类注册为bean
- metadataReader:读取到当前正在扫描的类的信息
- metadataReader.getAnnotationMetadata():获取当前类注解的信息
- metadataReader.getClassMetadata():获取当前正在扫描的类信息
- metadataReader.getResource():获取当前类的自愿信息(类路径)
- metadataReaderFactory:可以获取到其他任何类信息的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.shg.filter;
import org.springframework.core.io.Resource; 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 java.io.IOException;
public class MyFilter implements TypeFilter { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); ClassMetadata classMetadata = metadataReader.getClassMetadata(); Resource resource = metadataReader.getResource(); String className = classMetadata.getClassName(); System.out.println("className = " + className); return className.contains("User"); } }
|
- 在配置类中使用自定义的Filter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.shg.config;
import com.shg.filter.MyFilter; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Component;
@Component
@ComponentScan(value = {"com.shg"}, includeFilters = { @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyFilter.class}) }, useDefaultFilters = false) public class MyConfig1 { }
|
- 输出组件的id(略)
@Scope注解设置组件的作用域、@Lazy懒加载
- 创建配置类
- @Scope通过参数value设置组件的作用域,默认为singleton
- value的取值有:”singleton”、”prototype”、”request”、”session”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package com.shg.config;
import com.shg.bean.Person; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component;
@Configuration public class MyConfig2 { @Scope(value = "singleton") @Lazy @Bean(value = "p") public Person person() { return new Person(18, "scope"); }
@Scope(value = "prototype") @Bean(value = "p1") public Person person01() { return new Person(18, "scope"); } }
|
- 查看输出结果
- 根据id为p获取的bean是单实例的,单实例bean在Ioc容器初始化的时候创建
- 根据id为p1获取的bean是多实例的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.shg.test;
import com.shg.bean.Person; import com.shg.config.MyConfig1; import com.shg.config.MyConfig2; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Arrays;
public class AnnotationTest1 {
@Test public void testScope() { ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig2.class); Person p = (Person) context.getBean("p"); Person p1 = (Person) context.getBean("p"); System.out.println("(p == p1) = " + (p == p1)); Person p2 = (Person) context.getBean("p1"); Person p3 = (Person) context.getBean("p1"); System.out.println("(p2 == p3) = " + (p2 == p3)); } }
|
@Conditional按照条件注册bean
- 定义三个实现Condition接口的条件类,表示虚拟机运行的系统条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.shg.condition;
import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata;
public class WinCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name"); return osName != null && osName.contains("Windows"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.shg.condition;
import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata;
public class LinuxCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name"); return osName != null && osName.contains("Linux"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.shg.condition;
import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata;
public class MacCondition implements Condition {
@Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name"); return osName != null && osName.contains("Mac"); } }
|
- 创建配置类
- 使用@Conditional注解指定bean注册条件
- @Conditional可以放在类上也可以放在方法上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.shg.config;
import com.shg.bean.Person; import com.shg.condition.LinuxCondition; import com.shg.condition.MacCondition; import com.shg.condition.WinCondition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration;
@Configuration public class MyConfig3 { @Bean(value = "bill") @Conditional(value = WinCondition.class) public Person person() { return new Person(60, "bill"); }
@Bean(value = "linus") @Conditional(value = LinuxCondition.class) public Person person1() { return new Person(48, "linus"); }
@Bean(value = "jobs") @Conditional(value = MacCondition.class) public Person person2() { return new Person(48, "Jobs"); } }
|
- 输出结果
1 2 3 4 5 6 7 8 9 10 11
| @Test public void testConditional() { ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig3.class); Environment environment = context.getEnvironment(); System.out.println("environment.getProperty(\"os.name\") = " + environment.getProperty("os.name"));
String[] names = context.getBeanNamesForType(Person.class); Arrays.stream(names).forEach(System.out::println); }
|
@Import给容器注册组件
- 定义四个类Color、Blue、Yellow、RainBow
1 2 3 4 5 6 7 8 9
| package com.shg.bean;
public class Color { }
|
1 2 3 4 5 6 7 8 9
| package com.shg.bean;
public class Blue { }
|
1 2 3 4 5 6 7 8 9
| package com.shg.bean;
public class Yellow { }
|
- 自定义一个ImportSelector接口的实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.shg.condition;
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.shg.bean.Yellow", "com.shg.bean.Blue"}; } }
|
- 自定义一个ImportBeanDefinitionRegistrar接口的实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.shg.condition;
import com.shg.bean.RainBow; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; 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 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { boolean hasBlue = registry.containsBeanDefinition("com.shg.bean.Yellow"); boolean hasYellow = registry.containsBeanDefinition("com.shg.bean.Blue"); if (hasBlue && hasYellow) { RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class); registry.registerBeanDefinition("rainBow", beanDefinition); } } }
|
- 创建配置类
- @Import导入组件,value是一个数组,数组中的元素可以是:
- 一个普通类,id默认是组件的全类名
- ImportSelector接口的实现类,id默认是组件的全类名
- ImportBeanDefinitionRegistrar接口的实现类,id可以在实现类中指定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.shg.config;
import com.shg.bean.Color; import com.shg.condition.MyImportBeanDefinitionRegistrar; import com.shg.condition.MyImportSelector; import org.springframework.context.annotation.Import; import org.springframework.stereotype.Component;
@Component @Import(value = {Color.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class}) public class ImportConfig { }
|
- 输出组件的id
1 2 3 4 5 6 7 8
| @Test public void testImport() { ApplicationContext context = new AnnotationConfigApplicationContext(ImportConfig.class); String[] names = context.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } }
|
1 2 3 4 5 6 7 8 9
| org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory importConfig com.shg.bean.Color com.shg.bean.Yellow com.shg.bean.Blue rainBow
|
使用FactoryBean注册组件
- 创建一个FactoryBean接口的实现类,范型是要创建的bean的类型
- isSingleton()的返回值:true:这个bean是单实例,在容器中只保存一份; false:多实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.shg.bean;
import org.springframework.beans.factory.FactoryBean;
public class ColorFactoryBean implements FactoryBean<Color> { @Override public Color getObject() throws Exception { return new Color(); }
@Override public Class<?> getObjectType() { return Color.class; }
@Override public boolean isSingleton() { return true; } }
|
- 创建配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.shg.config;
import com.shg.bean.ColorFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class MyConfig4 { @Bean public ColorFactoryBean colorFactoryBean() { return new ColorFactoryBean(); } }
|
- 获取bean
- 根据id获取bean实例,默认是获取的是调用getObject创建的对象
- 如果要获取工厂bean实例,只需要在id前加上 & 前缀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Test public void testFactoryBean() { ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig4.class); String[] names = context.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } Object colorFactoryBean = context.getBean("colorFactoryBean"); System.out.println("colorFactoryBean = " + colorFactoryBean);
Object factoryBean = context.getBean("&colorFactoryBean"); System.out.println("factoryBean = " + factoryBean);
}
|
总结
使用注解注册Bean的方式
@Configuration + @Bean
@ComponentScan + @Component/@Controller/@Service/@Repository
- 注册bean的排除规则
- 使用@Scope注解设置bean的作用域
- 使用@Lazy注解懒加载单例bean
- 使用@Conditional注解设置bean的注册条件
@Import
@Configuration + @Bean + 实现FactoryBean接口
- isSingleton() 方法来设置bean是单例还是多例