Spring注解-组件注册(一)
Bean
- 我们之前使用xml时, 需要使用<bean>标签
- 配置
<bean class="test.bean.Person" id="person"> <property name="age" value="18"></property> <property name="name" value="zhangsan"></property> </bean>
- 使用
ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("beans.xml"); Person bean = cac.getBean("person", Person.class); System.out.println(bean); cac.close();
- 使用注解配置
- 需要我们手写配置类, 就相当于以前的配置文件.
- 配置类上加入@Configuration注解, 告诉Spring这是一个配置类.
- 在里面使用@Bean注解: 给容器注册一个额Bean, 类型为返回值类型, id默认是方法名.
- 其中, value属性可以修改id
@Configuration public class MainConfig { // 给容器中注册一个Bean; 类型为返回值的类型, id默认是方法名 @Bean(value = "person01") public Person person() { return new Person("lisi", 20); } }
- 其中, value属性可以修改id
- 使用
- 使用AnnotationConfigApplicationContext, 包含配置类类型.
public class MainTest { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class); Person bean2 = ac.getBean(Person.class); Person bean3 = ac.getBean(Person.class); System.out.println(bean2 == bean3); ac.close(); } }
- 使用AnnotationConfigApplicationContext, 包含配置类类型.
- @Scope注解
- @Bean默认是单实例的, 我们可以使用@Scope注解来调整作用域
- scope
- prototype: 多实例 -> IOC容器启动不会调用方法创建对象, 而是每次获取的时候调用方法创建对象.
- singleton: 单实例(默认值) -> IOC容器启动就会调用方法创建对象放到容器中. 以后获取直接从从容器中拿.
- request: 同一次请求创建一个实例
- sesson: 同一个session创建一个实例(后两个都web环境)
- 懒加载 -> 只针对于单实例
- 单实例原本是在容器启动的时候就创建对象的.
- 懒加载: 容器启动后, 先不创建对象, 第一次使用(获取)Bean的时候创建对象, 并初始化.
- 用@Lazy注解
@Configuration @ComponentScan(value = "test") public class MainConfig { // 给容器中注册一个Bean; 类型为返回值的类型, id默认是方法名 @Bean(value = "person01") @Lazy @Scope("singleton") public Person person() { return new Person("lisi", 20); } }
包扫描
- xml方式
- 使用<context:component-scan>
<!-- 包扫描: 只要标注了@Component, @Service, @Repository, @Controller, 就会被自动扫描加进容器 --> <context:component-scan base-package="test"></context:component-scan>
- 注解方式
- 使用@ComponentScan注解, 这是一个可重复注解, 也就是我们可以在一个类上写多次实现多种配置规则
- value属性填写要扫描的包.
- excludeFilters = Filter[]: 指定扫描的时候按照什么规则排除哪些组件
- includeFilters = Filter[]: 指定扫描的时候包含哪些组件
- useDefaultFilters属性: 是否使用默认配置规则, 默认值为true, 使用includeFilters时需配置成false.
@ComponentScan(value = "test", excludeFilters = { @Filter(type=FilterType.ANNOTATION, classes = {Controller.class, Service.class}) }) @ComponentScan(value = "test", includeFilters = { @Filter(type=FilterType.ANNOTATION, classes = {Repository.class}) })
- 测试
- AnnotationConfigApplicationContext有一个getBeanDefinitionNames()获取加载进Spring的组件.
- @Filter注解
- FilterType.ANNOTATION: 按照注解
- classes属性中填组件的类型.
- FilterType.ASSIGNABLE_TYPE: 按照给定的类型
- classes中填写具体类的类型
@Filter(type = FilterType.ASSIGNABLE_TYPE, classes= {BookService.class})
- classes中填写具体类的类型
-
- FilterType.ASPECTJ: 使用ASPECTJ表达式
- FilterType.REGEX: 使用正则表达式
- FilterType.CUSTOM: 使用自定义规则
- 自定义TypeFilter接口的实现类
public class MyTypeFilter implements TypeFilter { /** * metadataReader: 读取到当前正在扫描的类的信息 * metadataReaderFactory: 可以获取到其他任何类信息的. * * 返回false是一个也不匹配. */ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { //获取当前类注解的信息 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); //获取当前正在扫描的类的类信息 ClassMetadata classMetadata = metadataReader.getClassMetadata(); String className = classMetadata.getClassName(); System.out.println(className); //获取当前类资源(类的路径等信息) Resource resource = metadataReader.getResource(); //类名中有"er"就会被扫描进容器. if(className.contains("er")) { return true; } return false; } }
- 使用
@Filter(type=FilterType.CUSTOM, classes = {MyTypeFilter.class})
- 自定义TypeFilter接口的实现类
@Conditional注解
- 作用: 按照一定条件进行判断, 满足条件才给容器中注册bean.
- @Conditional(): 中填写Condition数组.
- Condition是个接口, 所以我们要写实现类放入@Conditional中.
- MaleCondition
public class MaleCondition implements Condition { /** * ConditionContext: 判断条件能使用的上下文(环境) * AnnotatedTypeMetadata: 注释信息 * 返回true则允许注册. */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //是否为男 //1.能获取到ioc使用的beanfactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //2.获取类加载器 ClassLoader classLoader = context.getClassLoader(); //3.获取当前环境信息 Environment environment = context.getEnvironment(); //获取到bean定义的注册类 BeanDefinitionRegistry registry = context.getRegistry(); String property = environment.getProperty("os.name"); //当前系统是否为Windows if(property.contains("Windows")) { return true; } return false; } }
- FemaleCondition
public class FemaleCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String property = environment.getProperty("os.name"); if(property.contains("Linux")) { return true; } return false; } }
- 使用
@Conditional({ MaleCondition.class }) @Bean("ming") public Person person001() { return new Person("xiaoming", 16); } @Conditional({ FemaleCondition.class }) @Bean("hong") public Person person002() { return new Person("xiaohong", 15); }
- 不仅仅能放在方法上, 也能放在类上
- 只有满足了当前条件, 这个类中配置的所有bean注册才能生效.
@Import注解
- 给容器中注册组件
- 包扫描+组件注解标注(@Controller等...)
- 但有局限, 只能注册我们自己写的类.
- @Bean
- 导入的第三方包里面的组件
- @Import
- 快速给容器中导入一个组件
- @Import(要导入到容器中的组件), 容器中就会自动注册这个组件, id默认是全类名
@Configuration @ComponentScan(value = "test") @Import({Color.class, Red.class}) //导入组件, id默认是组件的全类名 public class MainConfig { //... }
- ImportSelector
- 自定义逻辑, 返回要导入的组件, 这是一个接口, 我们要在实现类中操作.
//自定义逻辑, 返回需要导入的组件 public class MyImportSelector implements ImportSelector { /** * 返回值就是导入到容器中的组件的全类名 * AnnotationMetadata: 当前标注@Import的注解类的所有注解信息. */ @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { // 方法不能返回null值, 可以返回一个空数组 return new String[] {"test.bean.Blue", "test.bean.Yellow"}; } }
- 当然, 我们需要把实现类写入@Import中.
@Configuration @ComponentScan(value = "test") @Import({Color.class, Red.class, MyImportSelector.class}) //导入组件, id默认是组件的全类名 public class MainConfig { //... }
- 自定义逻辑, 返回要导入的组件, 这是一个接口, 我们要在实现类中操作.
- ImportBeanDefinitionRegistrar
- 手工注册bean到容器中, 需要实现ImportBeanDefinitionRegistrar接口.
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { /** * BeanDefinitionRegistry: BeanDefinition注册类, 把所有需要添加到容器中的bean, * 调用BeanDefinitionRegistry.registerBeanDefinition手工注册进来 */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //是否包含"red"这个组件 boolean definition = registry.containsBeanDefinition("red"); boolean definition2 = registry.containsBeanDefinition("blue"); if(definition && definition2) { //指定Bean信息: (Bean的类型, Scope等) RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class); //指定bean名 registry.registerBeanDefinition("rainBow", beanDefinition); } } }
- 当然, 我们需要把实现类写入@Import中.
@Configuration @ComponentScan(value = "test") @Import({MyImportSelector.class, MyImportBeanDefinitionRegistrar.class}) //导入组件, id默认是组件的全类名 public class MainConfig { //... }
- 手工注册bean到容器中, 需要实现ImportBeanDefinitionRegistrar接口.
FactoryBean(工厂Bean)
- 实现FactoryBean接口, 让工厂帮我们创建对象.
//创建一个Spring定义的FactoryBean public class ColorFactoryBean implements FactoryBean<Color> { // 返回一个Color对象, 这个对象会添加到容器中 @Override public Color getObject() throws Exception { // TODO Auto-generated method stub return new Color(); } @Override public Class<?> getObjectType() { // TODO Auto-generated method stub return Color.class; } // 是否单例 @Override public boolean isSingleton() { return false; } }
- 装配
@Bean public ColorFactoryBean colorFactoryBean() { return new ColorFactoryBean(); }
- 测试
- 虽说装配的是ColorFactoryBean, 但我们实际获取的却是其泛型中的实体对象.
@Test public void test() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class); Object bean = ac.getBean("colorFactoryBean"); System.out.println(bean); Object bean2 = ac.getBean("&colorFactoryBean"); System.out.println(bean2); }
- 但我们在id前加上&符, 就能获取工厂Bean本身.
- 虽说装配的是ColorFactoryBean, 但我们实际获取的却是其泛型中的实体对象.