Spring注解驱动开发(1)之组件注册

Spring注解驱动开发(1)之组件注册

微信搜索 zze_coding 或扫描 👉 二维码关注我的微信公众号获取更多资源推送:

下面示例用到的依赖如下:

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.12.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
</dependency>

@Configuration

@Configuration 标注的类就是一个 Spring 配置类,配置类的作用就相当于之前我们使用的 Spring 配置文件。

示例

1、创建配置类,通过 @Configuration 注解标注一个类为配置类:

import org.springframework.context.annotation.Configuration;

/**
 * 配置类
 */
@Configuration
public class MainConfig {
}

2、通过读取注解配置类来创建 IoC 容器:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);

@ComponentScan

@ComponentScan 注解的作用相当于在 Spring 配置文件中配置 <context:component-scan> 标签,扫描 bean 到 IoC 容器。

示例

1、创建测试 Service 类:

// com.springanno.service.UserService
import org.springframework.stereotype.Service;

@Service
public class UserService {
}

2、使用 @ComponentScan 注解:

// com.springanno.config.MainConfig
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * 配置类
 */
@Configuration
@ComponentScan("com.springanno")
public class MainConfig {
}

3、测试:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
UserService bean = applicationContext.getBean(UserService.class);
System.out.println(bean);
/*
com.springanno.service.UserService@77e9807f
 */

相关属性

ComponentScan.Filter[] excludeFilters() default {} :排除扫描匹配到的类。例:

excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}, // 不扫描指定注解标注的类

ComponentScan.Filter[] includeFilters() default {} :仅扫描匹配到的类,要生效需同时指定 useDefaultFilters = false。例:

includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Service.class})}, // 仅扫描指定注解标注的类
useDefaultFilters = false

@ComponentScan.Filter 用来指定过滤规则:

@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Service.class}), // 过滤指定注解标注的类
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {UserService.class}), // 过滤给定类型的类
@ComponentScan.Filter(type = FilterType.ASPECTJ,pattern = {"com.springanno.service.*Service"}), // 通过 ASPECTJ 表达式过滤指定类
@ComponentScan.Filter(type = FilterType.REGEX, pattern ={".*.*Service"}), // 通过正则过滤指定类

还可自定义过滤规则,先自定义一个 TypeFilter 类:

// com.springanno.config.MyTypeFilter
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 MyTypeFilter implements TypeFilter {
    /**
     * @param metadataReader 正在扫描的类的信息
     * @param metadataReaderFactory 可获取其它类信息
     * @return 如果返回 true ,说明当前扫描的类匹配成功
     */
    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);
        return true;
    }
}

使用如下:

@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) // 自定义规则过滤

@Bean

@Bean 注解的作用相当于在 Spring 配置文件中配置 <bean> 标签。

示例 1:直接返回要注册的 bean

1、在配置类中使用 @Bean 注解注册一个 User 实例到 IoC 容器:

import com.springanno.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig {
    /**
     * 默认情况下方法名为 bean 的 id
     * 返回值为加入到 IOC 容器的实例
     * 可通过 @Bean 注解的 value 属性指定 bean 的 id
     *      
     *  <bean id="user1" class="com.springanno.pojo.User">
     *    <property name="name" value="张三"/>
     *    <property name="age" value="20"/>
     *  </bean>
     */
    @Bean(value = "user1")
    public User user(){
        return new User("张三", 20);
    }
}

2、测试:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Object user1 = applicationContext.getBean("user1");
System.out.println(user1);
/*
User{name='张三', age=20}
 */

示例 2:返回 FactoryBean

1、创建如下 FactoryBean 类:

// com.springanno.config.UserFactoryBean
import com.springanno.pojo.User;
import org.springframework.beans.factory.FactoryBean;

public class UserFactoryBean implements FactoryBean<User> {
    /**
     * 返回的对象将会添加到容器
     */
    public User getObject() throws Exception {
        return new User("ice",22);
    }

    /**
     * 对象类型
     */
    public Class<?> getObjectType() {
        return User.class;
    }

    /**
     * 是否单例
     */
    public boolean isSingleton() {
        return true;
    }
}

2、使用 @Bean 注解注册 FactoryBean 实例到 IoC 容器:

// com.springanno.config.MainConfig
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig {
    @Bean
    public UserFactoryBean userFactoryBean(){
        return new UserFactoryBean();
    }
}

3、测试:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println(beanDefinitionName);
}
Object user = applicationContext.getBean("userFactoryBean");
System.out.println(user);
/*
mainConfig
userFactoryBean
User{name='ice', age=22}
 */
// 可以看到,我们获取的是 userFactoryBean,但实际上返回的是 userFactoryBean 对应实例的 getObject 方法的返回值
// 如果我们的确要获取 userFactoryBean 对应的实例,可通过 &id 这种方式获取:
UserFactoryBean userFactoryBean = (UserFactoryBean) applicationContext.getBean("&userFactoryBean");
System.out.println(userFactoryBean);
/*
com.springanno.config.UserFactoryBean@4461c7e3
 */

@Scope

@Scope 注解作用相当于在 <bean> 标签上的 scope 属性。

示例

1、使用 @Bean 注解注册 Bean 的同时使用 @Scope 注解:

// com.springanno.config.MainConfig
import com.springanno.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class MainConfig {
    /**
     * @Scope
     *  value 属性有如下可选值:
     *      singleton(默认): 单例。IOC 容器启动时就会调用方法创建对象放到容器,之后每次使用都是直接从容器中取。
     *      prototype : 多例。只有要使用该对象时才会调用该方法创建对象。
     *      request (web 环境): 一次请求。
     *      session (web 环境): 一次会话。
     */
    @Scope("prototype")
    @Bean
    public User user(){
        return new User("张三", 20);
    }
}

2、测试:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Object user1 = applicationContext.getBean("user");
Object user2 = applicationContext.getBean("user");
System.out.println(user1==user2);
/*
false
 */

@Lazy

@Lazy 注解作用相当于在 <bean> 标签上配置属性 lazy-init="true",针对单实例 bean,控制容器启动时不创建对象,第一次获取该 bean 时才创建对象。。

示例

1、使用 @Bean 注册 bean 的同时使用 @Lazy 注解,标识改 bean 是一个懒加载的实例:

// com.springanno.config.MainConfig
import com.springanno.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

@Configuration
public class MainConfig {
    @Lazy
    @Bean
    public User user(){
        System.out.println("创建了");
        return new User("张三", 20);
    }
}

2、测试:

懒加载:ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
/*
[无任何输出]
*/

@Conditional

按照指定的条件进行判断,满足条件才在容器中注册 bean。

示例

当前操作系统为 Windows 时,我们注册一个 id 为 Windows 的 bean,当前系统为 Linux 时,注册一个 id 为 linux 的 bean

1、创建 Condition 类:

// com.springanno.condition.WindowsCondition
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * 判断是否是 Windows 系统
 */
public class WindowsCondition implements Condition {
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        String osName = environment.getProperty("os.name").toLowerCase();
        return osName.contains("windows");
    }
}
// com.springanno.condition.LinuxCondition
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * 判断是否是 Linux 系统
 */
public class LinuxCondition implements Condition {

    /**
     * @param conditionContext      判断条件能使用的上下文
     * @param annotatedTypeMetadata 注解信息
     */
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 获取到容器使用的 BeanFactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        // 获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        // 获取当前环境信息
        Environment environment = conditionContext.getEnvironment();
        // 获取 bean 定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();

        String osName = environment.getProperty("os.name").toLowerCase();
        return osName.contains("linux");
    }
}

2、注册 bean 时使用 @Condition 注解指定自定义的 Condition 类:

// com.springanno.config.MainConfig
import com.springanno.condition.LinuxCondition;
import com.springanno.condition.WindowsCondition;
import com.springanno.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig {
    @Conditional({WindowsCondition.class})
    @Bean
    public User windows() {
        return new User("windows", 20);
    }

    @Conditional({LinuxCondition.class})
    @Bean
    public User linux() {
        return new User("linux", 3);
    }
}

3、测试:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
String[] beanNames = applicationContext.getBeanDefinitionNames();
for (String beanName : beanNames) {
    System.out.println(beanName);
}
/*
mainConfig
windows
*/
// 当前是在 windows 下,所以可以看到只注册了 windows bean

@Import

@Import 的作用也是注册指定 bean 到 IoC 容器,只不过它相对来说更偏重于批量注册。它提供了下面几种注册 bean 到 IoC 容器的方式。

示例 1:直接注册指定 bean 到容器

1、指定要注册 bean 的类型:

// com.springanno.config.MainConfig
/**
 * @Import 可以直接将注册指定 bean 到容器中,id 为 bean 的类全路径名
 */
@Import({User.class})
public class MainConfig {
}

2、测试:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println(beanDefinitionName);
}

/*
mainConfig
com.springanno.pojo.User
 */

示例 2:通过 ImportSelector 批量注册

1、定义 ImportSelector 类,在方法中返回要注册类的全路径名:

// com.springanno.config.MyImportSelector
import com.springanno.pojo.User;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        List<String> classNameList = new ArrayList<String>();
        classNameList.add(User.class.getName());
        return StringUtils.toStringArray(classNameList);
}

2、在 @Import 注解中指定 ImportSelector 类:

// com.springanno.config.MainConfig
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
/**
 * MyImportSelector.selectImports() 方法返回的类的全路径列表,
 * @Import 将会把这些全路径对应的类都注册到容器,id 为类的全路径名
 */
@Import({MyImportSelector.class})
public class MainConfig {
}

3、测试:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println(beanDefinitionName);
}
/*
mainConfig
com.springanno.pojo.User
 */

示例 3:通过 ImportBeanDefinitionRegistrar 手动定义 bean 信息注册 bean

1、创建如下 ImportBeanDefinitionRegistrar 类:

// com.springanno.config.MyImportBeanDefinitionRegistrar
import com.springanno.pojo.User;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     *
     * @param annotationMetadata 当前类注解信息
     * @param beanDefinitionRegistry BeanDefinition 注册类
     *
     */
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        // 判断 IOC 容器中是否已经注册了 user
        boolean isContainsUser = beanDefinitionRegistry.containsBeanDefinition("user");
        // 可以通过 beanDefinitionRegistry.registerBeanDefinition() 方法注册所有需要添加到容器中的 bean
        if(!isContainsUser){
            RootBeanDefinition beanDefinition = new RootBeanDefinition(User.class.getName());
            // id=user
            beanDefinitionRegistry.registerBeanDefinition("user", beanDefinition);
        }
    }
}

2、在 @Import 注解中指定 ImportBeanDefinitionRegistrar 类:

// com.springanno.config.MainConfig
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
/**
* @Import 会执行 MyImportBeanDefinitionRegistrar.registerBeanDefinitions(),在该方法中完成 bean 的注册
 */
@Import({MyImportBeanDefinitionRegistrar.class})
public class MainConfig {
}

3、测试:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println(beanDefinitionName);
}
/*
mainConfig
user
 */

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.zze.xyz/archives/spring-anno-1.html

Buy me a cup of coffee ☕.