【Spring】Spring5 常见的注解和使用【一】配置组件

Alan 363 2021-03-11
@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类也被注入了。


# Spring