Spring注解驱动开发(2)——向容器注册Bean

目录

1使用@Bean注解

当要向容器注册的Bean来自第三方包时可以采用这种方式。

创建一个配置类,并在类中写一个返回指定bean对象的方法,在该方法上标注@Bean注解

@Configuration  //声明这是一个配置类
public class MainConfig1 {

    @Bean(value = "myPerson") //value指定bean的id
    public Person person(){   //bean的默认id为方法名
        return new Person("rainboy",20);
    }
}

bean的id默认为方法名,也可以通过@Bean注解的value属性进行指定

  • 测试代码
@Test
public void test3(){

    //指定配置类为MainConfig1
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig1.class);

    String[] names = context.getBeanDefinitionNames();

    //打印容器中所有bena的name
    for(String name:names){
        System.out.println(name);
    }
}
  • 测试结果

.2 使用@ComponentScan包扫描配合@Component等注解

和原来将包扫描信息定义在xml文件中不同,注解驱动开发时只需要在配置类上加上@ComponentScan注解,该注解有以下几个重要的属性

  • 指定要扫描的包及其子包
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};

由于是value属性,故实际使用时属性名可以省略。

  • 指定过滤掉哪些Bean(即这些bean不向容器注册)
/**
 * Specifies which types are not eligible for component scanning.
 * @see #resourcePattern
 */
Filter[] excludeFilters() default {};

其中Filter是定义在注解@ComponentScan内部的注解,它有以下几个重要属性

/**
 * The type of filter to use.
 * <p>Default is {@link FilterType#ANNOTATION}.
 * @see #classes
 * @see #pattern
 */
FilterType type() default FilterType.ANNOTATION;

@AliasFor("value")
Class<?>[] classes() default {};

type返回一个FilterType枚举,指定过滤规则,默认是按注解类型过滤。classes是指定过滤规则后要过滤的类型。举个例子,如果过滤规则是按注解类型过滤,classes值设置为{Controller.class,Service.class},则所有标注以上两个注解的Bean都不会被容器注册。其他过滤规则还有

  1. FilterType.ASSIGNABLE_TYPE:按类过滤
  2. FilterType.ASPECTJ:按ASPECTJ表达式过滤
  3. FilterType.REGEX:按正则表达式:过滤
  4. FilterType.CUSTOM:按自定义类型过滤
  • 指定要进行注册的bean
/**
 * Specifies which types are eligible for component scanning.
 * <p>Further narrows the set of candidate components from everything in {@link #basePackages}
 * to everything in the base packages that matches the given filter or filters.
 * <p>Note that these filters will be applied in addition to the default filters, if specified.
 * Any type under the specified base packages which matches a given filter will be included,
 * even if it does not match the default filters (i.e. is not annotated with {@code @Component}).
 * @see #resourcePattern()
 * @see #useDefaultFilters()
 */
Filter[] includeFilters() default {};

这个注解生效的前提是要将默认的过滤规则关闭,即将该属性设置为false

boolean useDefaultFilters() default true;

2.1 一个在包扫描时过滤指定的Bean的例子

分别在子包下创建BookController,BookService,BookDao

如果想要容器启动时不注册@Controller和@Service注解的Bean,可以进行如下设置

@Configuration

//扫描com.rainboy包及其子包,并过滤掉标注有@Controller和@Service注解的类
@ComponentScan(value = "com.rainboy", excludeFilters =
        {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class,Service.class})}
        )
public class MainConfig2 {

}
  • 测试代码
@Test
public void test4(){

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);

    String[] names = context.getBeanDefinitionNames();

    for(String name:names){
        System.out.println(name);
    }

}
  • 运行结果如下

2.2 按自定义规则过滤指定的Bean

在2.1的基础上将以Dao结尾的Bean过滤掉,首先自定义过滤类

  • 自定义过滤类
    该类实现TypeFilter接口,并重写match方法,注意结果返回true表示要过滤:不向容器注册。返回false表示不过滤。
/**
 * 自定义过滤器,在包扫描时忽略指定的Bean
 */
public class MyTypeFilter implements TypeFilter {

    /**
     * @param metadataReader        读取到的当前正在扫描的类的信息
     * @param metadataReaderFactory
     * @return
     * @throws IOException
     */
    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();

        if (className.endsWith("Dao")) {
            return true;   //以Dao结尾的Bean都过滤掉,这里返回true表示要过滤
        }
        return false;
    }
}

修改配置类的ComponentScan参数

@Configuration

//扫描com.rainboy包及其子包,并过滤掉标注有@Controller和@Service注解的类
@ComponentScan(value = "com.rainboy", excludeFilters =
        {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class,Service.class}),
                @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})

        }
        )
public class MainConfig2 {

}

最后测试结果如下

3.使用@Import注解

3.1 @Import的简单用法

使用@Import注解可以方便快速的向容器注册Bean。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

   /**
    * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
    * or regular component classes to import.
    */
   Class<?>[] value();

}

该注解只能加在类上。

  • 配置类
@Configuration
@Import(Person.class) //注册Person
public class MainConfig4 {

}
  • 测试代码
@Test
public void test6(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);

    String[] names = context.getBeanDefinitionNames();

    for(String name:names){
        System.out.println(name);
    }
}
  • 测试结果

可以看到Person以及被注册到了容器中,且id默认为类的全类名。

3.2 @Import高级用法@ImportSelector

ImportSelector是一个接口,需要实现该接口并重写方法,返回需要注册的类的全类名。

public interface ImportSelector {

   /**
    * Select and return the names of which class(es) should be imported based on
    * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
    */
   String[] selectImports(AnnotationMetadata importingClassMetadata);

}
  • 自定义ImportSelector
public class MyImportSelector implements ImportSelector {

    /**
     *
     * @param importingClassMetadata  标注有ImportSelector注解的类上的所有注解信息
     * @return
     */
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        return new String[]{
                "com.rianboy.bean.Student","com.rianboy.bean.User"
        };
    }
}
  • 配置类
@Configuration
@Import({Person.class,MyImportSelector.class})
public class MainConfig4 {

}
  • 测试运行结果

4. 使用Spring提供的FactoryBean

  • 自定义FactoryBean
public class UserFactoryBean implements FactoryBean<User> {
    
    //真正想容器注册的Bean
    public User getObject() throws Exception {
        System.out.println("UserFactoryBean....");
        return new User("rainboy", 20);
    }

    public Class<?> getObjectType() {
        return User.class;
    }

    public boolean isSingleton() {
        return true;
    }
}
  • 配置类
@Configuration
@Import({Person.class, MyImportSelector.class})
public class MainConfig4 {


    /**
     * 向容器注册UserFactoryBean
     * @return
     */
    @Bean("user") //id为user
    public UserFactoryBean userFactoryBean() {

        return new UserFactoryBean();
    }

}
  • 测试代码
@Test
public void test6(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);

    String[] names = context.getBeanDefinitionNames();

    for(String name:names){
        System.out.println(name);
    }

    System.out.println("----------");
    Object user = context.getBean("user");

    System.out.println(user.getClass());
}
  • 测试结果

可以看到,虽然在配置类中我们创建了一个UserFactoryBean,但是实际向容器注册,并且能从容器中取出的对象是User,也就是在我们从容器中获取id为"user"的bean时,UserFactoryBean的getObject()方法会被调用。
如果想获取UserFactoryBean本身,则可以在获取时指定id为“&user”。

context.getBean("&user")

5. 注册Bean时的一些有用注解

5.1. Bean的作用域@Scope及懒加载@Lazy

通过@Scope("prototype")指定,Bean的作用域主要有以下几种

  • singleton:单实例,ioc容器启动的时候就会创建单实例的bean并缓存起来,以后每次获取都是从缓存中拿。默认配置。
  • prototype:多实例,ioc容器启动时不会创建对象,在获取的时候才创建,并且每次获取都是创建一个新的对象。
  • request:同一次请求创建一个实例
  • session:同一个session创建一个实例

对于单实例bean,默认容器启动就会创建Bean。也可以通过@Lazy注解将Bean的创建过程延迟到第一次获取Bean时

@Lazy
@Bean(value = "myPerson") //value指定bean的id
public Person person(){   //bean的默认id为方法名
    return new Person("rainboy",20);
}

5.2. 条件注册@Conditional

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

   /**
    * All {@link Condition}s that must {@linkplain Condition#matches match}
    * in order for the component to be registered.
    */
   Class<? extends Condition>[] value();

}

该注解可以让Bean在满足一定条件后才向Spring容器注册

根据操作系统的类型注册Bean

  • 配置类
@Configuration
public class MainConfig3 {


    @Conditional(MacCondition.class)  //只有操作系统为mac时该bean才被注册
    @Bean("mac")
    public Person person1() {
        return new Person("mac", 20);
    }

    @Conditional(WindowCondition.class)   //只有操作系统为windows时该bean才被注册
    @Bean("windows")
    public Person person2() {
        return new Person("windows", 20);
    }
}

该注解需要自定义实现Condition接口的类,并重写其匹配逻辑。

  • Mac系统的匹配逻辑
/**
 * 判断操作系统是否是mac系统
 */
public class MacCondition implements Condition {

    /**
     * 当操作系统时mac系统时,匹配成功,该注解标注的bean才能注册成功
     * @param context  判断条件能使用的上下文环境
     * @param metadata 标注了Condition注解的注释信息
     * @return
     */
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        Environment environment = context.getEnvironment();

        return environment.getProperty("os.name").toLowerCase().contains("mac");
    }
}
  • Windows系统的匹配逻辑
/**
 * 判断操作系统是否是windows系统
 */
public class WindowCondition implements Condition {

    /**
     * 当操作系统是windows系统时,匹配成功,该注解标注的bean才会被容器注册
     * @param context
     * @param metadata
     * @return
     */
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        Environment environment = context.getEnvironment();

        return environment.getProperty("os.name").toLowerCase().contains("windows");

    }
}
  • 测试代码
@Test
public void test5() {

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);

    String[] names = context.getBeanNamesForType(Person.class);
    for (String name : names) {
        System.out.println(name);
    }
}
  • 运行结果

由于运行程序的机器为mbp,故而只有name为mac的bean会被注册进ioc容器中。

猜你喜欢

转载自www.cnblogs.com/rain-boy/p/9846240.html