spring 配置类

Spring 使用 @Configuration 注解来替代之前的 springContext.xml 文件:

  • @Configuration:注解在类上,声明一个配置类,等同于之前的一个 spring-context.xml 配置文件
  • @ComponentScans、@ComponentScan:自动装配,用于声明要扫描的包路径 ```java @Configuration // 声明一个配置类,等同于spring-context.xml配置文件 public class MyConfig{ // ….. }

public class Main { public static void main(String[] args) { // 通过配置类创建Spring容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class); context.close(); } }

  1. @Configuration 注解 proxyBeanMethods 属性的作用:
  2. - 类组件之间没有依赖关系使用 Lite 模式加速容器启动过程,减少判断,即 proxyBeanMethods=false
  3. - 类组件之间有依赖关系使用 Full 模式,方法会被调用得到之前的单实例组件,即 proxyBeanMethods=true,默认
  4. ```java
  5. // 创建两个pojo,其中Schoo的属性中含有User类
  6. @Data
  7. @Accessors(chain = true)
  8. public class User {
  9. private String name;
  10. private Integer age;
  11. }
  12. @Data
  13. @Accessors(chain = true)
  14. public class School {
  15. private String name;
  16. private User user;
  17. }
  18. // 创建Spring配置类
  19. @Configuration(proxyBeanMethods = true) //声明配置类
  20. public class DemoConfig {
  21. @Bean
  22. public User user(){
  23. return new User().setName("李四").setAge(18);
  24. }
  25. @Bean
  26. public School school(){
  27. // 组件School依赖了User组件
  28. // 使用方法名
  29. return new School().setUser(user()).setName("校长");
  30. }
  31. }
  32. // 测试:
  33. public static void main(String[] args) {
  34. AnnotationConfigApplicationContext context =
  35. new AnnotationConfigApplicationContext(DemoConfig.class);
  36. // 测试一:测试从容器中获取的是否是同一对象
  37. User user = context.getBean("user", User.class);
  38. User user1 = context.getBean("user", User.class);
  39. System.out.println(user == user1); //始终返回true
  40. // 测试二:测试组件中注入的是否是容器中的Bean
  41. School school = context.getBean("school", School.class);
  42. //当proxyBeanMethods设置为true时,返回true,当proxyBeanMethods设置为false时,返回false
  43. // 说明默认情况下spring会对@Bean标注的方法进行代理,使得组件存在依赖时,依赖获取的组件为容器中的单例对象
  44. // 当proxyBeanMethods设置为false时,spring不会对@Bean标注的方法进行代理,导致组件存在依赖时,创建的是不同对象,且该对象不被容器管理
  45. System.out.println(school.getUser() == user);
  46. context.close();
  47. }

**

组件注册

Spring 支持多种方式注册组件:

  1. 包扫描 + 组件标注注解(@Controller、@Service、@Repository、@Component):一般开发过程均使用此种方式
  2. 使用 @Bean 声明:一般用于引入第三方 jar 包,进行第三方 jar 的整合
  3. 使用 @Import 导入:快速给容器中导入一个组件
  4. 使用 Spring 提供的 FactoryBean (工厂Bean)

组件属性

组件 Bean 的单例与多例:默认是单例,通过 @Scope 注解来进行设置

  • @Scope 注解的取值:
    • ConfigurableBeanFactory#SCOPE_PROTOTYPE:prototype 多实例
    • ConfigurableBeanFactory#SCOPE_SINGLETON:singleton 单实例
    • org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST:request 同一次请求创建一个实例
    • org.springframework.web.context.WebApplicationContext#SCOPE_SESSION:session 同一个 session 创建一个实例
  • Spring 中单例默认是随着 Spring 工厂创建时;如果需要单例在使用时才创建,需要使用 @Lazy 注解
  • Spring 中多例在 Spring 工厂时不创建,而是在使用时才创建

组件的选择性导入

组件的选择性导入:@Conditional 注解

  • @Conditional( { Condition.class } ):按照一定的条件进行判断,满足条件给容器中注册 Bean,条件类需要实现 Condition 接口
  • @Conditional 注解可以注解在配置类或者方法上

    • 注解在配置类上时,表示只有满足当前条件,该配置类中的所有配置才会生效
    • 注解在方法上时,表示只有满足当前条件,该方法声明的 Bean 才会被导入 ```java // 自定义一个条件类:表示只有当前操作系统是Windows时才导入该配置类 public class WindowsCondition implements Condition { /*

      • @param context 判断条件能使用的上下文环境
      • @param metadata 注释信息
      • @return */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 判断是否为Windows系统 // 1.能够获取到ioc使用的beanFactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); // 2.获取到类加载器 ClassLoader classLoader = context.getClassLoader(); // 3.获取到资源加载器 ResourceLoader resourceLoader = context.getResourceLoader(); // 4.获取当前环境信息 Environment environment = context.getEnvironment(); // 5.获取到bean定义的注册类 BeanDefinitionRegistry registry = context.getRegistry();

        // 可以判断容器中bean的注册情况,也可以给容器中注册bean // 可以在JVM运行参数中使用-Dos.name=linux来控制该参数 String property = environment.getProperty(“os.name”); if (property != null && property.contains(“Windows”)){

        1. return true;

        } return false; } }

// 使用该条件 @Configuration @Conditional({WindowsCondition.class}) public class MyConfig{ // …. }

  1. <a name="E0JWo"></a>
  2. #### 包扫描+组件标注注解
  3. 组件标注注解:
  4. - @Component:通用
  5. - @Repository、@Service、@Controller:用在业务层
  6. 组件扫描注解:注解在配置类(@Configuration注解的类)上,用于指定扫描路径和扫描条件,相当于配置文件中的 <context:component-scan>
  7. - @ComponentScan:可重复注解
  8. - @ComponentScans:支持在里面写入多个 @ComponentScan 注解,@ComponentScan 重复的载体
  9. ```java
  10. @Configuration // 声明配置类
  11. @ComponentScans(value = {@ComponentScan(...), @ComponentScan(...)}) // 支持指定多个扫描路径的@ComponentScan
  12. public class MyConfig {
  13. // ....
  14. }

@ComponentScan 注解的常用参数:

  • basePackages / value:字符串数组,指定包扫描路径,可以是单个路径或者多个路径
  • basePackageClasses:指定具体扫描的类,支持单个或多个 class
  • nameGenerator:指定 bean 名称即 id 的生成器,默认是 BeanNameGenerator
  • scopeResolver:处理检测到的 Bean 的 scope 范围
  • scopedProxy:是否为指定到的组件生成代理
  • useDefaultFilters:是否对 @Component、@Repository、@Service、@Controller 注解的类开启检测,默认为 true
  • includeFilters:指定扫描时需要扫描哪些需要的组件,支持如下的 FilterType
    • FilterType.ANNOTATION:按照注解进行筛选,默认
    • FilterType.ASSIGNABLE_TYPE:按照给定的类型
    • FilterType.ASPECTJ:使用 AspectJ 表达式
    • FilterType.REGEX:使用正则表达式
    • FilterType.CUSTOM:自定义类型,必须为 TypeFilter 的实现类
  • excludeFilters:排除某些扫描到的类
  • lazyInit:扫描到的类都开启懒加载,默认不开启 ```java // 扫描包 @ComponentScan({“com.example.service” ,”com.example.entity”})

// 指定具体的类 @ComponentScan(basePackageClasses = {User.class ,UserService.class})

// 扫描自定义的注解@MyAnnotation @ComponentScan( basePackages=”com.example”, includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {MyAnnotation.class}) } )

// 自定义扫描规则:需要实现 TypeFilter 接口 public class MyFilter implements TypeFilter{ /*

  1. * @param metadataReader 读取到的当前正在扫描类的信息
  2. * @param metadataReaderFactory 可以获取其他任何类信息
  3. * @return
  4. * @throws IOException
  5. */
  6. @Override
  7. public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
  8. // 获取当前类注解的信息
  9. AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
  10. // 获取当前正在扫描的类的类信息
  11. ClassMetadata classMetadata = metadataReader.getClassMetadata();
  12. // 获取当前类资源信息(类的路径)
  13. Resource resource = metadataReader.getResource();
  14. // 获取到类名
  15. String className = classMetadata.getClassName();
  16. return false;
  17. }

}

  1. <a name="5vHG2"></a>
  2. #### 使用 @Bean 声明
  3. 在配置类中的方法上添加 @Bean 注解,可以声明一个 Bean,一般用于第三方 jar 包的整合:
  4. - @Bean 注解声明的 Bean 的 id:
  5. - 当 @Bean 注解不加参数时,默认以**方法名**作为 id
  6. - 当 @Bean(name ="xx") 中有定义时,则采用定义中的名字
  7. - @Bean 注解声明的 Bean 默认是单实例,如果需要创建多实例的 Bean ,则需要在方法上添加 @Scope 注解,@Scope 注解支持如下取值:
  8. - ConfigurableBeanFactory#SCOPE_PROTOTYPE:prototype 多实例
  9. - ConfigurableBeanFactory#SCOPE_SINGLETON:singleton 单实例
  10. - org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST:request 同一次请求创建一个实例
  11. - org.springframework.web.context.WebApplicationContext#SCOPE_SESSION:session 同一个 session 创建一个实例
  12. - 单实例的 Bean 默认在Spring工厂创建时进行创建,多实例的 Bean 会在使用时才会进行创建
  13. ```java
  14. @Configuration
  15. public class MyConfig{
  16. @Bean // id 为 user
  17. public User user(){
  18. return new User();
  19. }
  20. @Lazy // 懒加载
  21. @Bean(name = "pro") // id为pro
  22. public Product product(){
  23. return new Product();
  24. }
  25. @Bean
  26. @Scope("prototype") // 创建多实例的Bean
  27. public Student student(){
  28. return new Student();
  29. }
  30. }

使用 @Import 进行注册

@Import 支持如下情况的组件导入:

  • @Import( User.class ):直接导入某个类,其导入的 Bean 的 id 为该类的全限定名,如 com.example.entity.User
  • @Import( ImportSelector.class ):ImportSelector的实现类有一个 selectImports() 方法,返回的是需要导入到容器中的全类名数组
  • @Import( ImportBeanDefinitionRegistrar.class ):ImportBeanDefinitionRegistrar 的实现类中有一个 registerBeanDefinitions 方法,支持在里面进行 Bean 的注册

1、@Import({ User.class }):直接导入类,一般用于导入另外的配置类

  1. // 实体类
  2. @Data(staticConstructor = "of") // 静态创建
  3. @Accessors(chain = true) // 支持链式调用
  4. public class User {
  5. private String name;
  6. private Integer age;
  7. }
  8. // 要被导入的配置类,该类可以不用 @Configuration 声明
  9. public class SubConfig{
  10. @Bean
  11. public User user(){
  12. return User.of().setName("Bob").setAge(19);
  13. }
  14. }
  15. // 配置类
  16. @Configuration
  17. @Import({ SubConfig.class })
  18. public class Config1 {
  19. }
  20. // 测试
  21. public class Config1Test {
  22. // 通过配置类创建Spring工厂
  23. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config1.class);
  24. @Test
  25. public void test1() {
  26. String[] names = context.getBeanDefinitionNames();
  27. for (String name : names) {
  28. // 输出:
  29. // com.example.config.SubConfig,即使用 @Import 进行加载会使用类全限定名作为id
  30. // user:说明子配置类中配置生效了
  31. System.out.println(name);
  32. }
  33. }
  34. @Test
  35. public void test2() {
  36. // 通过id获取bean
  37. User bean = context.getBean("user", User.class);
  38. // 输出:User(name=Bob, age=19)
  39. System.out.println(bean);
  40. User user = context.getBean(User.class);
  41. System.out.println(user);
  42. }
  43. }

2、@Import( ImportSelector.class ):ImportSelector的实现类有一个 selectImports() 方法,返回的是需要导入到容器中的全类名数组

  • 通过这种方式导入的类,其 id 为其全限定类名 ```java /**
  • @param importingClassMetadata 当前标注@Import注解类的所有注解信息
  • @return 返回值为需要导入到容器中的全类名数组 */ public class MySelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    1. // 可以返回空数组,不能返回null
    2. // 支持导入配置类,且配置类不需要标注@Configuration
    3. return new String[]{"com.example.config.Config2"};
    } }

// 在配置类上使用 @Configuration @Import({MySelector.class}) public class Config3 { }

  1. 3@Import( ImportBeanDefinitionRegistrar.class ):ImportBeanDefinitionRegistrar 的实现类中有一个 registerBeanDefinitions 方法,支持在里面进行 Bean 的注册,手动注册
  2. ```java
  3. // ImportBeanDefinitionRegistrar实现类
  4. public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  5. /**
  6. * @param importingClassMetadata 当前类的注册信息
  7. * @param registry BeanDefinition注册类,把所有需要添加到容器中的 Bean 通过 registry.registerBeanDefinition() 注册进去
  8. */
  9. @Override
  10. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  11. // 如果容器中不存在user这个Bean,则注册一个
  12. if (!registry.containsBeanDefinition("user")) {
  13. // Bean的定义信息
  14. RootBeanDefinition beanDefinition = new RootBeanDefinition(User.class);
  15. // user:需要注册的Bean的id
  16. // beanDefinition:BeanDefinition的实现类,用来定义一个Bean
  17. registry.registerBeanDefinition("user", beanDefinition);
  18. }
  19. }
  20. }
  21. // 在配置类上进行导入
  22. @Configuration
  23. @Import({MyImportBeanDefinitionRegistrar.class})
  24. public class MyConfig{}

使用 Spring 提供的 FactoryBean

FactoryBean 接口的方法:

  1. public interface FactoryBean<T> {
  2. String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
  3. // 返回创建的Bean
  4. @Nullable
  5. T getObject() throws Exception;
  6. // 返回创建的Bean的类型
  7. @Nullable
  8. Class<?> getObjectType();
  9. // 是否是单例,true 单例,false 多例
  10. default boolean isSingleton() {
  11. return true;
  12. }
  13. }

使用:

  • 通过工厂Bean的id获取到的是工厂Bean中getObject返回的对象
  • 通过工厂Bean的&id获取到的是工厂类本身 ```java // 创建一个FactoryBean的实现类 public class MyFactoryBean implements FactoryBean { @Override public User getObject() throws Exception {

    1. return User.of().setAge(18).setName("Hello");

    }

    @Override public Class<?> getObjectType() {

    1. return User.class;

    } }

// 配置类中进行配置 @Configuration public class MyConfig {

  1. @Bean
  2. public MyFactoryBean myFactoryBean(){
  3. return new MyFactoryBean();
  4. }

}

// 测试,通过id获取相应的Bean public class DemoTest { private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);

  1. @Test
  2. public void test() {
  3. // 通过工厂Bean的id获取的是调用getObject()方法创建的对象:class com.example.entity.User
  4. Object myFactoryBean = context.getBean("myFactoryBean");
  5. System.out.println(myFactoryBean.getClass());
  6. // 如果需要获取工厂bean本身,可以使用&id获取:class com.example.common.MyFactoryBean
  7. Object bean = context.getBean("&myFactoryBean");
  8. System.out.println(bean.getClass());
  9. }

} ```