Spring中常用的注解

4.3.1 @ComponentScan和ComponentScans(bean批量注册)

  1. @ComponentScan用于批量注册bean,spring会按照这个注解的配置,递归扫描指定包中的所有类,将满足条件的类批量注册到spring容器中
  2. 可以通过value、basePackages、basePackageClasses 这几个参数来配置包的扫描范围
  3. 可以通过useDefaultFilters、includeFilters、excludeFilters这几个参数来配置类的过滤器,被过滤器处理之后剩下的类会被注册到容器中
  4. 指定包名的方式配置扫描范围存在隐患,包名被重命名之后,会导致扫描实现,所以一般我们在需要扫描的包中可以创建一个标记的接口或者类,作为basePackageClasses的值,通过这个来控制包的扫描范围
  5. @CompontScan注解会被ConfigurationClassPostProcessor类递归处理,最终得到所有需要注册的类
4.3.1.1 源码定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class) //@1
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};
    @AliasFor("value")
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
    String resourcePattern() default "**/*.class";
    boolean useDefaultFilters() default true;
    Filter[] includeFilters() default {};
    Filter[] excludeFilters() default {};
    boolean lazyInit() default false;
}

常用参数:

  1. value:指定需要扫描的包,如:com.javacode2018
  2. basePackages:作用同value;value和basePackages不能同时存在设置,可二选一
  3. basePackageClasses:指定一些类,spring容器会扫描这些类所在的包及其子包中的类
  4. nameGenerator:自定义bean名称生成器
  5. resourcePattern:需要扫描包中的那些资源,默认是:**/*.class,即会扫描指定包中所有的class文件
  6. useDefaultFilters:对扫描的类是否启用默认过滤器,默认为true
  7. includeFilters:过滤器:用来配置被扫描出来的那些类会被作为组件注册到容器中
  8. excludeFilters:过滤器,和includeFilters作用刚好相反,用来对扫描的类进行排除的,被排除的类不会被注册到容器中
  9. lazyInit:是否延迟初始化被注册的bean
  10. @Repeatable(ComponentScans.class),这个注解可以同时使用多个
4.3.1.2 工作过程:
  1. Spring会扫描指定的包,且会递归下面子包,得到一批类的数组
  2. 然后这些类会经过上面的各种过滤器,最后剩下的类会被注册到容器中
4.3.1.3 关键问题:
  1. 需要扫描哪些包?
    通过value、backPackages、basePackageClasses这3个参数来控制
  2. 过滤器有哪些?
    通过useDefaultFilters、includeFilters、excludeFilters这3个参数来控制过滤器
4.3.1.4 扫描规则:

默认情况下,任何参数都不设置的情况下会将@ComponentScan修饰的类所在的包作为扫描包。
默认情况下,useDefaultFilters=true,spring容器内部会使用默认过滤器,规则是:凡是类上有@Repository、@Service、@Controller、@Component这几个注解中的任何一个的,那么这个类就会被作为bean注册到spring容器中,所以默认情况下,只需在类上加上这几个注解中的任何一个,这些类就会自动交给spring容器来管理了。

4.3.1.5 案例1:任何参数未设置

分别在dao,controller,service包下创建类,用@Service,@Controller,@Repository注解标注
UserService

package com.zjhc.componentSacn.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {}

UserController

package com.zjhc.componentSacn.controller;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {}

UserDao

package com.zjhc.componentSacn.dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {}

UserModel

package com.zjhc.componentSacn;
import org.springframework.stereotype.Component;
@Component
public class UserModel {}

ScanBean

package com.zjhc.componentSacn;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class ScanBean {}

测试

@Test
public void test2(){
   ApplicationContext context = new AnnotationConfigApplicationContext(ScanBean.class);
   for (String beanName : context.getBeanDefinitionNames()) {
       System.out.println(beanName+"---->"+context.getBean(beanName));
   }
}

使用AnnotationConfigApplicationContext作为ioc容器,将ScanBean.class作为参数传入,默认会扫描ScanBean类所在的包中的所有类,类上有@Component、@Repository、@Service、@Controller任何一个注解的都会被注册到容器中
在这里插入图片描述

4.3.1.6 案例2:指定需要扫描的包

指定需要扫毛哪些包,可以通过value或者basePackage来配置,二者选其一,都配置运行会报错,下面我们通过value来配置

package com.zjhc.componentSacn;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan({"com.zjhc.componentSacn.controller,
                 com.zjhc.componentSacn.dao"})
public class ScanBean {}

测试结果
在这里插入图片描述

4.3.1.7 案例:basePackageClasses指定扫描范围

指定包名的方式扫描存在的一个隐患,若包被重名了,会导致扫描会失效,我们可以在需要扫描的包中定义一个标记的接口或者类,他们的唯一的作用是作为basePackageClasses的值,其他没有任何用途。
定义一个类或者接口

package com.zjhc.componentSacn.controller;
public class a {}
import com.zjhc.componentSacn.controller.a;
import org.springframework.context.annotation.ComponentScan;

//@ComponentScan({"com.zjhc.componentSacn.controller,com.zjhc.componentSacn.dao"})
@ComponentScan(basePackageClasses = a.class)
public class ScanBean {
}
scanBean---->com.zjhc.componentSacn.ScanBean@67e2d983
userController---->com.zjhc.componentSacn.controller.UserController@5d47c63f
4.3.1.8 includeFilters和excludeFilters的使用

是一个Filter类型的数组,多个Filter之间为或者关系,即满足任意一个就可以了,看一下Filter的代码:

@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {

    FilterType type() default FilterType.ANNOTATION;

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

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

    String[] pattern() default {};
}

主要参数:
type:过滤器的类型,是个枚举类型,5种类型
ANNOTATION:通过注解的方式来筛选候选者,即判断候选者是否有指定的注解
ASSIGNABLE_TYPE:通过指定的类型来筛选候选者,即判断候选者是否是指定的类型
ASPECTJ:ASPECTJ表达式方式,即判断候选者是否匹配ASPECTJ表达式
REGEX:正则表达式方式,即判断候选者的完整名称是否和正则表达式匹配
CUSTOM:用户自定义过滤器来筛选候选者,对候选者的筛选交给用户自己来判断
value:和参数classes效果一样,二选一
classes:3种情况如下
当type=FilterType.ANNOTATION时,通过classes参数可以指定一些注解,用来判断被扫描的类上是否有classes参数指定的注解
当type=FilterType.ASSIGNABLE_TYPE时,通过classes参数可以指定一些类型,用来判断被扫描的类是否是classes参数指定的类型
当type=FilterType.CUSTOM时,表示这个过滤器是用户自定义的,classes参数就是用来指定用户自定义的过滤器,自定义的过滤器需要实现org.springframework.core.type.filter.TypeFilter接口
pattern:2种情况如下
当type=FilterType.ASPECTJ时,通过pattern来指定需要匹配的ASPECTJ表达式的值
当type=FilterType.REGEX时,通过pattern来自正则表达式的值

4.3.1.8.1 扫描包含注解的类

我们自定义一个注解,让标注有这些注解的类自动注册到容器中

package com.zjhc.componentSacn.annotation;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
}

创建一个类,使用这个注解标注

package com.zjhc.componentSacn.annotation;
@MyAnno
public class Service1 {}

再来一个类,使用spring中的@Compontent标注

package com.zjhc.componentSacn.annotation;
import org.springframework.stereotype.Component;
@Component
public class Service2 {}

再来一个类,使用@CompontentScan标注

package com.zjhc.componentSacn;
@ComponentScan(includeFilters = {@ComponentScan.Filter(type= FilterType.ANNOTATION,classes = MyAnno.class)})
public class ScanBean2 {}

测试用例

 @Test
public void test3(){
    ApplicationContext context = new AnnotationConfigApplicationContext(ScanBean2.class);
    for (String beanName : context.getBeanDefinitionNames()) {
        System.out.println(beanName+"---->"+context.getBean(beanName));
    }
}

结果
在这里插入图片描述
问题:Service1上标注了@MyBean注解,被注册到容器了,但是没有标注@MyBean啊,怎么也被注册到容器了?
回答:@CompontentScan注解中的useDefaultFilters默认是true,表示会启用默认的过滤器,默认的过滤器会将标注有@Component、@Repository、@Service、@Controller这几个注解的类也注册到容器中。

修改扫描代码:
如果我们只想将标注有@MyBean注解的bean注册到容器,需要将默认过滤器关闭,即:useDefaultFilters=false

package com.zjhc.componentSacn;
@ComponentScan(useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type= FilterType.ANNOTATION,classes = MyAnno.class)})
public class ScanBean2 {}

再输出:
在这里插入图片描述

4.3.1.8.2 包含指定类型的类

被扫描的类满足IService.class.isAssignableFrom(被扫描的类)条件的都会被注册到spring容器中
@ComponentScan(
useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE,classes = IService.class)}
)
接口

package com.zjhc.componentSacn.annotation.componentBytype;
public interface IService {}

实现类

package com.zjhc.componentSacn.annotation.componentBytype;
public class ServiceA implements IService {}

package com.zjhc.componentSacn.annotation.componentBytype;
public class ServiceB implements IService {}

@CompontentScan标注的类

package com.zjhc.componentSacn.annotation.componentBytype;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@ComponentScan(useDefaultFilters = false,
includeFilters = {@ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE,
classes = IService.class)})
public class ScanBean3 {}

测试:

@Test
public void test4(){
   ApplicationContext context = new AnnotationConfigApplicationContext(ScanBean3.class);
   for (String beanName : context.getBeanDefinitionNames()) {
       System.out.println(beanName+"---->"+context.getBean(beanName));
   }
}

在这里插入图片描述

4.3.1.8.3 自定义Filter

步骤:
1.设置@Filter中type的类型为:FilterType.CUSTOM
2.自定义过滤器类,需要实现接口:org.springframework.core.type.filter.TypeFilter
3.设置@Filter中的classses为自定义的过滤器类型

TypeFilter这个接口的定义:
是一个函数式接口,包含一个match方法,方法返回boolean类型,有2个参数,都是接口类型的,下面介绍一下这2个接口

@FunctionalInterface
public interface TypeFilter {
    boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException;
}

MetadataReader接口:
类元数据读取器,可以读取一个类上的任意信息,如类上面的注解信息、类的磁盘路径信息、类的class对象的各种信息,spring进行了封装,提供了各种方便使用的方法。
MetadataReader接口的定义:

public interface MetadataReader {
    /**
     * 返回类文件的资源引用
     */
    Resource getResource();

    /**
     * 返回一个ClassMetadata对象,可以通过这个读想获取类的一些元数据信息,
     * 如类的class对象、是否是接口、是否有注解、是否是抽象类、父类名称、接口名称、
     * 内部包含的之类列表等等,可以去看一下源码
     */
    ClassMetadata getClassMetadata();

    /**
     * 获取类上所有的注解信息
     */
    AnnotationMetadata getAnnotationMetadata();
}

MetadataReaderFactory接口:
类元数据读取器工厂,可以通过这个类获取任意一个类的MetadataReader对象
MetadataReaderFactory接口定义:

public interface MetadataReaderFactory {
    /**
     * 返回给定类名的MetadataReader对象
     */
    MetadataReader getMetadataReader(String className) throws IOException;

    /**
     * 返回指定资源的MetadataReader对象
     */
    MetadataReader getMetadataReader(Resource resource) throws IOException;
}

案例:
需求:我们来个自定义的Filter,判断被扫描的类如果是IService接口类型的,就让其注册到容器中。
接口

package com.zjhc.componentSacn.annotation.componentBytype;
public interface IService {}

实现类

package com.zjhc.componentSacn.annotation.componentBytype;
public class ServiceA implements IService {}

package com.zjhc.componentSacn.annotation.componentBytype;
public class ServiceB implements IService {}

自定义的TypeFilter类:

package com.zjhc.componentSacn.annotation.componentByfilter;

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 {
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        Class clzz = null;
        try {
            //获取当前被扫描的类
            clzz = Class.forName(metadataReader.getClassMetadata().getClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //判断clzz是否为IService类型
        boolean result = IService.class.isAssignableFrom(clzz);
        return result;
    }
}

@CompontentScan标注的类

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@ComponentScan(useDefaultFilters = false,
includeFilters = {@ComponentScan.Filter(type= FilterType.CUSTOM,
classes = MyTypeFilter.class)})
public class ScanBean4 {
}
4.3.1.9 @ComponentScan重复使用
@ComponentScans({
        @ComponentScan(basePackageClasses = a.class),
        @ComponentScan(
                useDefaultFilters = false, //不启用默认过滤器
                includeFilters = {
                        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = IService.class)
                })})
public class ScanBean3 {
}

4.3.2 @Import使用:bean的批量注册

@Import可以用来批量导入需要注册的各种类,如普通的类、配置类,然后完成普通类和配置类中所有bean的注册。
@Import可以使用在任何类型上,通常情况下,类和注解上用的比较多。
value:一个Class数组,设置需要导入的类,可以是@Configuration标注的列,可以是ImportSelector接口或者ImportBeanDefinitionRegistrar接口类型的,或者需要导入的普通组件类。

源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    /**
     * {@link Configuration @Configuration}, {@link ImportSelector},
     * {@link ImportBeanDefinitionRegistrar}, 
     * or regular component classes to import.
     */
    Class<?>[] value();
}

@Import的value常见的有5种用法

  1. value为普通的类
@Import({Service1.class, Service2.class})
public class MainConfig1 {}
com.demo24.test1.Service1->com..demo24.test1.Service1@7e0b85f9
com.demo24.test1.Service2->com.demo24.test1.Service2@63355449

通过@Import导入的2个类,bean名称为完整的类名,可以使用@Compontent注解指定被导入类的bean名称

@Component("service1")
public class Service1 {}
service1->com.demo24.test1.Service1@45efd90f
  1. value为@Configuration标注的类
package com.javacode2018.lesson001.demo24.test2;
//模块1配置类
@Configuration
public class ConfigModule1 {
    @Bean
    public String module1() { return "我是模块1配置类!"; }
    }

package com.javacode2018.lesson001.demo24.test2;
//模块2配置类
@Configuration
public class ConfigModule2 {
    @Bean
    public String module2() { return "我是模块2配置类!"; }
}

package com.javacode2018.lesson001.demo24.test2;
//通过Import来汇总多个@Configuration标注的配置类
@Import({ConfigModule1.class, ConfigModule2.class}) //@1
public class MainConfig2 {}
mainConfig2->com.javacode2018.lesson001.demo24.test2.MainConfig2@ba2f4ec
com.javacode2018.lesson001.demo24.test2.ConfigModule1->com.javacode2018.lesson001.demo24.test2.ConfigModule1$$EnhancerBySpringCGLIB$$700e65cd@1c1bbc4e
module1->我是模块1配置类!
com.javacode2018.lesson001.demo24.test2.ConfigModule2->com.javacode2018.lesson001.demo24.test2.ConfigModule2$$EnhancerBySpringCGLIB$$a87108ee@55fe41ea
module2->我是模块2配置类!
  1. value为@CompontentScan标注的类

项目中分多个模块,每个模块有各自独立的包,我们在每个模块所在的包中配置一个@CompontentScan类,然后通过@Import来导入需要启用的模块
第一个组件的包及里面的类:

package com.javacode2018.lesson001.demo24.test3.module1;
@Component
public class Module1Service1 {}

package com.javacode2018.lesson001.demo24.test3.module1;
@Component
public class Module1Service2 {}

package com.javacode2018.lesson001.demo24.test3.module1;
//模块1的主键扫描
@ComponentScan
public class CompontentScanModule1 {}

第二个组件的包及里面的类:

package com.javacode2018.lesson001.demo24.test3.module2;
@Component
public class Module2Service1 {}

package com.javacode2018.lesson001.demo24.test3.module2;
@Component
public class Module2Service2 {}

package com.javacode2018.lesson001.demo24.test3.module2;
// 模块2的组件扫描
@ComponentScan
public class CompontentScanModule2 {}

总配置类:通过@Import导入每个模块中的组件扫描类

/**
 * 通过@Import导入多个@CompontentScan标注的配置类
 */
@Import({CompontentScanModule1.class, CompontentScanModule2.class}) //@1
public class MainConfig3 {}
  1. value为ImportBeanDefinitionRegistrar接口类型
1. 定义ImportBeanDefinitionRegistrar接口实现类,在registerBeanDefinitions方法中使用registry来注册bean
2. 使用@Import来导入步骤1中定义的类
3. 使用步骤2@Import标注的类作为AnnotationConfigApplicationContext构造参数创建spring容器
4. 使用AnnotationConfigApplicationContext操作bean

这个接口提供了通过spring容器api的方式直接向容器中注册bean

public interface ImportBeanDefinitionRegistrar {
 default void registerBeanDefinitions(
         AnnotationMetadata importingClassMetadata, 
         BeanDefinitionRegistry registry,
         BeanNameGenerator importBeanNameGenerator) {

     registerBeanDefinitions(importingClassMetadata, registry);
 }
 default void registerBeanDefinitions(
          AnnotationMetadata importingClassMetadata, 
          BeanDefinitionRegistry registry) {
 }
}

importingClassMetadata:AnnotationMetadata类型的,通过这个可以获取被@Import注解标注的类所有注解的信息。
registry:BeanDefinitionRegistry类型,是一个接口,内部提供了注册bean的各种方法。
importBeanNameGenerator:BeanNameGenerator类型,是一个接口,内部有一个方法,用来生成bean的名称。

BeanDefinitionRegistry接口:bean定义注册器,提供了bean注册的各种方法,基本上所有bean工厂都实现了这个接口,让bean工厂拥有bean注册的各种能力,AnnotationConfigApplicationContext类也实现了这个接口

public interface BeanDefinitionRegistry extends AliasRegistry {
    /**
     * 注册一个新的bean定义
     * beanName:bean的名称
     * beanDefinition:bean定义信息
     */
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException;

    /**
     * 通过bean名称移除已注册的bean
     * beanName:bean名称
     */
    void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    /**
     * 通过名称获取bean的定义信息
     * beanName:bean名称
     */
    BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    /**
     * 查看beanName是否注册过
     */
    boolean containsBeanDefinition(String beanName);
    /**
     * 获取已经定义(注册)的bean名称列表
     */
    String[] getBeanDefinitionNames();
    /**
     * 返回注册器中已注册的bean数量
     */
    int getBeanDefinitionCount();
    /**
     * 确定给定的bean名称或者别名是否已在此注册表中使用
     * beanName:可以是bean名称或者bean的别名
     */
    boolean isBeanNameInUse(String beanName);
}

BeanNameGenerator接口:bean名称生成器

public interface BeanNameGenerator {
    String generateBeanName(
    BeanDefinition definition,
     BeanDefinitionRegistry registry);
}

这个接口spring有三个内置的实现:
DefaultBeanNameGenerator:默认bean名称生成器,xml中bean未指定名称的时候,默认就会使用这个生成器,默认为:完整的类名#bean编号
AnnotationBeanNameGenerator:注解方式的bean名称生成器,比如通过@Component(bean名称)的方式指定bean名称,如果没有通过注解方式指定名称,默认会将完整的类名作为bean名称。
FullyQualifiedAnnotationBeanNameGenerator:将完整的类名作为bean的名称

BeanDefinition接口:bean定义信息,用来表示bean定义信息的接口,我们向容器中注册bean之前,会通过xml或者其他方式定义bean的各种配置信息,bean的所有配置信息都会被转换为一个BeanDefinition对象,然后通过容器中BeanDefinitionRegistry接口中的方法,将BeanDefinition注册到spring容器中,完成bean的注册操作。
案例:
Service1:

package com.javacode2018.lesson001.demo24.test4;
public class Service1 {}

Service2:需要注入Service1

package com.javacode2018.lesson001.demo24.test4;
public class Service2 {
    private Service1 service1;

    public Service1 getService1() { return service1;}
    public void setService1(Service1 service1) { this.service1 = service1; }
    @Override
    public String toString() {
        return "Service2{" + "service1=" + service1 +'}'; }
}

定义一个类实现ImportBeanDefinitionRegistrar接口,然后在里面实现上面2个类的注册

package com.javacode2018.lesson001.demo24.test4;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
 @Override
 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
     //定义一个bean:Service1
     BeanDefinition service1BeanDinition = BeanDefinitionBuilder.genericBeanDefinition(Service1.class).getBeanDefinition();
     //注册bean
     registry.registerBeanDefinition("service1", service1BeanDinition);

     //定义一个bean:Service2,通过addPropertyReference注入service1
     BeanDefinition service2BeanDinition = BeanDefinitionBuilder.genericBeanDefinition(Service2.class).
             addPropertyReference("service1", "service1").
             getBeanDefinition();
     //注册bean
     registry.registerBeanDefinition("service2", service2BeanDinition);
 }
}

定义配置类导入实现类

@Import({MyImportBeanDefinitionRegistrar.class})
public class ImportBean{}

测试:

@Test
public void test4() {
    //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ImportBean.class);
    //2.输出容器中定义的所有bean信息
    for (String beanName : context.getBeanDefinitionNames()) {
        System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
    }
}
  1. value为ImportSelector接口类型
7. 定义ImportSelector接口实现类,在selectImports返回需要导入的类的名称数组
8. 使用@Import来导入步骤1中定义的类
9. 使用步骤2@Import标注的类作为AnnotationConfigApplicationContext构造参数创建spring容器
10. 使用AnnotationConfigApplicationContext操作bean

ImportSelector接口:

public interface ImportSelector {
    /**
     * 返回需要导入的类名的数组,可以是任何普通类,配置类(@Configuration、@Bean、@CompontentScan等标注的类)
     * @importingClassMetadata:用来获取被@Import标注的类上面所有的注解信息
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}

案例:
普通类:Service1

package com.javacode2018.lesson001.demo24.test5;
public class Service1 {}

@Configuration标注的配置类:Module1Config

package com.javacode2018.lesson001.demo24.test5;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Module1Config {
    @Bean
    public String name() {
        return "公众号:路人甲java";
    }
    @Bean
    public String address() {
        return "上海市";
    }
}

自定义一个ImportSelector,然后返回上面2个类的名称

package com.javacode2018.lesson001.demo24.test5;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
    @Override
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{
                Service1.class.getName(),
                Module1Config.class.getName()
        };
    }
}

@Import标注的类,导入MyImportSelector

package com.javacode2018.lesson001.demo24.test5;
import com.lesson001.demo24.test4.MyImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.Import;
/**
 * 通过@Import导入MyImportSelector接口实现类
 */
@Import({MyImportSelector.class})
public class MainConfig5 {}

测试

@Test
public void test5() {
 //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
 //2.输出容器中定义的所有bean信息
 for (String beanName : context.getBeanDefinitionNames()) {
     System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
 }
}
  1. value为DeferredImportSelector接口类型

DeferredImportSelector是ImportSelector的子接口,既然是ImportSelector的子接口,所以也可以通过@Import进行导入。和ImportSelector不同地方有两点:延迟导入和指定导入的类的处理顺序

延迟导入
@Import的value包含了多个普通类、多个@Configuration标注的配置类、多个ImportSelector接口的实现类,多个ImportBeanDefinitionRegistrar接口的实现类,还有DeferredImportSelector接口实现类,此时spring处理这些被导入的类的时候,会将DeferredImportSelector类型的放在最后处理,会先处理其他被导入的类,其他类会按照value所在的前后顺序进行处理
案例:
来3个配置类,每个配置类中都通过@Bean定一个string类型的bean,内部输出一句文字。
Configuration1:

package com.javacode2018.lesson001.demo24.test7;
@Configuration
public class Configuration1 {
    @Bean
    public String name1() {
        System.out.println("name1");
        return "name1";
    }
}

Configuration2:

package com.javacode2018.lesson001.demo24.test7;
@Configuration
public class Configuration2 {
    @Bean
    public String name2() {
        System.out.println("name2");
        return "name2";
    }
}

Configuration3:

package com.javacode2018.lesson001.demo24.test7;
@Configuration
public class Configuration3 {
    @Bean
    public String name3() {
        System.out.println("name3");
        return "name3";
    }
}

一个ImportSelector实现类,导入Configuration1

package com.javacode2018.lesson001.demo24.test7;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class ImportSelector1 implements ImportSelector {
   @Override
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
       return new String[]{ Configuration1.class.getName()};
    }
}

一个DeferredImportSelector实现类,导入Configuration2

package com.javacode2018.lesson001.demo24.test7;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class DeferredImportSelector1 implements DeferredImportSelector {
   @Override
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
       return new String[]{Configuration2.class.getName()};
    }
}

一个总的配置类

package com.javacode2018.lesson001.demo24.test7;
import org.springframework.context.annotation.Import;
@Import({
        DeferredImportSelector1.class,
        Configuration3.class,
        ImportSelector1.class,
})
public class MainConfig7 {}

测试及输出:

@Test
public void test7() {
    //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig7.class);
}
name3
name1
name2

输出的结果结合一下@Import中被导入的3个类的顺序,可以看出DeferredImportSelector1是被最后处理的,其他2个是按照value中所在的先后顺序处理的。

指定导入的类的处理顺序
@Import中有多个DeferredImportSelector接口的实现类时候,可以指定他们的顺序,指定顺序常见2种方式:
实现Ordered接口的方式:

org.springframework.core.Ordered
public interface Ordered {
    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
    int getOrder();
}

实现Order注解的方式:

org.springframework.core.annotation.Order

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
    int value() default Ordered.LOWEST_PRECEDENCE;
}

案例:
2个配置类,内部都有一个@Bean标注的方法,用来注册一个bean
Configuration1:

package com.javacode2018.lesson001.demo24.test8;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Configuration1 {
    @Bean
    public String name1() {
        System.out.println("name1");
        return "name1";
    }
}

Configuration2:

package com.javacode2018.lesson001.demo24.test8;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Configuration2 {
    @Bean
    public String name2() {
        System.out.println("name2");
        return "name2";
    }
}

来2个DeferredImportSelector实现类,分别来导入上面2个配置文件,顺便通过Ordered接口指定一下顺序,DeferredImportSelector1的order为2,DeferredImportSelector2的order为1,order值越小优先级越高
DeferredImportSelector1:

package com.javacode2018.lesson001.demo24.test8;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;

public class DeferredImportSelector1 implements DeferredImportSelector, Ordered {
   @Override
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Configuration1.class.getName()};
    }
    @Override
    public int getOrder() {
        return 2;
    }
}

DeferredImportSelector2:

package com.javacode2018.lesson001.demo24.test8;
import com.javacode2018.lesson001.demo24.test7.Configuration2;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;

public class DeferredImportSelector2 implements DeferredImportSelector, Ordered {
   @Override
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Configuration2.class.getName()};
    }
    @Override
    public int getOrder() {
        return 1;
    }
}

来个总的配置类,引入上面两个ImportSelector
MainConfig8:

package com.javacode2018.lesson001.demo24.test8
import org.springframework.context.annotation.Import;
@Import({
        DeferredImportSelector1.class,
        DeferredImportSelector2.class,
})
public class MainConfig8 {}

测试输出:

@Test
public void test8() {
    //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig8.class);
}
name2
name1
4.3.2.1 案列

需求
凡是类名中包含service的,调用他们内部任何方法,我们希望调用之后能够输出这些方法的耗时
实现分析
我们可以通过代理来实现,bean实例创建的过程中,我们可以给这些bean生成一个代理,在代理中统计方法的耗时,这里面有2点:

  1. 创建一个代理类,通过代理来间接访问需要统计耗时的bean对象
  2. 拦截bean的创建,给bean实例生成代理生成代理

具体实现
Service1

package com.javacode2018.lesson001.demo24.test6;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
    public void m1() {
        System.out.println(this.getClass() + ".m1()");
    }
}

Service2

package com.javacode2018.lesson001.demo24.test6;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
    public void m1() {
        System.out.println(this.getClass() + ".m1()");
    }
}

创建统计耗时的代理类:用cglib来实现一个代理类

package com.javacode2018.lesson001.demo24.test6;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CostTimeProxy implements MethodInterceptor {
    //目标对象
    private Object target;

    public CostTimeProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long starTime = System.nanoTime();
        //调用被代理对象(即target)的方法,获取结果
        Object result = method.invoke(target, objects); //@1
        long endTime = System.nanoTime();
        System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
        return result;
    }

    /**
     * 创建任意类的代理对象
     *
     * @param target
     * @param <T>
     * @return
     */
    public static <T> T createProxy(T target) {
        CostTimeProxy costTimeProxy = new CostTimeProxy(target);
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(costTimeProxy);
        enhancer.setSuperclass(target.getClass());
        return (T) enhancer.create();
    }
}

拦截bean实例的创建,返回代理对象

package com.javacode2018.lesson001.demo24.test6;
import com.javacode2018.lesson001.demo23.test4.CostTimeProxy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;

public class MethodCostTimeProxyBeanPostProcessor implements BeanPostProcessor {
    @Nullable
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean.getClass().getName().toLowerCase().contains("service")) {
            return CostTimeProxy.createProxy(bean); //@1
        } else {
            return bean;
        }
    }
}

需要将MethodCostTimeProxyBeanPostProcessor注册到容器中才会起作用,下面我们通过@Import结合ImportSelector的方式来导入这个类,将其注册到容器中

package com.javacode2018.lesson001.demo24.test6;
import com.javacode2018.lesson001.demo23.test4.MethodCostTimeProxyBeanPostProcessor;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MethodCostTimeImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MethodCostTimeProxyBeanPostProcessor.class.getName()};
    }
}

来一个@Import来导入MethodCostTimeImportSelector,下面我们使用注解的方式,在注解上使用@Import

package com.javacode2018.lesson001.demo24.test6;
import com.javacode2018.lesson001.demo23.test4.MethodCostTimeImportSelector;
import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MethodCostTimeImportSelector.class)
public @interface EnableMethodCostTime {
}

来一个总的配置类

package com.javacode2018.lesson001.demo24.test6;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
@EnableMethodCostTime //@1
public class MainConfig6 {
}

上面使用了@CompontentScan注解,此时会将Servce1和Service2这两个类注册到容器中。
@1:此处使用了@EnableMethodCostTime注解,而@EnableMethodCostTime注解上使用了@Import(MethodCostTimeImportSelector.class),此时MethodCostTimeImportSelector类中的MethodCostTimeProxyBeanPostProcessor会被注册到容器,会拦截bean的创建,创建耗时代理对象。

测试:

@Test
public void test6() {
    //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig6.class);
    Service1 service1 = context.getBean(Service1.class);
    Service2 service2 = context.getBean(Service2.class);
    service1.m1();
    service2.m1();
}

结果:

class com.javacode2018.lesson001.demo24.test6.Service1.m1()
public void com.javacode2018.lesson001.demo24.test6.Service1.m1(),耗时(纳秒)74200
class com.javacode2018.lesson001.demo24.test6.Service2.m1()
public void com.javacode2018.lesson001.demo24.test6.Service2.m1(),耗时(纳秒)33800

如果我们不想开启方法耗时统计,只需要将MainConfig6上的@EnableMethodCostTime去掉就可以了
spring中有很多类似的注解,以@EnableXXX开头的注解,基本上都是通过上面这种方式实现的,如:

@EnableAspectJAutoProxy
@EnableCaching
@EnableAsync

4.3.3 @Import使用:bean的批量注册

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一位不知名民工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值