`
234390216
  • 浏览: 10232970 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
博客专栏
A5ee55b9-a463-3d09-9c78-0c0cf33198cd
Oracle基础
浏览量:462623
Ad26f909-6440-35a9-b4e9-9aea825bd38e
springMVC介绍
浏览量:1775516
Ce363057-ae4d-3ee1-bb46-e7b51a722a4b
Mybatis简介
浏览量:1398358
Bdeb91ad-cf8a-3fe9-942a-3710073b4000
Spring整合JMS
浏览量:395022
5cbbde67-7cd5-313c-95c2-4185389601e7
Ehcache简介
浏览量:679983
Cc1c0708-ccc2-3d20-ba47-d40e04440682
Cas简介
浏览量:530892
51592fc3-854c-34f4-9eff-cb82d993ab3a
Spring Securi...
浏览量:1183946
23e1c30e-ef8c-3702-aa3c-e83277ffca91
Spring基础知识
浏览量:467921
4af1c81c-eb9d-365f-b759-07685a32156e
Spring Aop介绍
浏览量:151394
2f926891-9e7a-3ce2-a074-3acb2aaf2584
JAXB简介
浏览量:68153
社区版块
存档分类
最新评论

Spring(32)——ImportSelector介绍

阅读更多

ImportSelector介绍

@Configuration标注的Class上可以使用@Import引入其它的配置类,其实它还可以引入org.springframework.context.annotation.ImportSelector实现类。ImportSelector接口只定义了一个selectImports(),用于指定需要注册为bean的Class名称。当在@Configuration标注的Class上使用@Import引入了一个ImportSelector实现类后,会把实现类中返回的Class名称都定义为bean。来看一个简单的示例,假设现在有一个接口HelloService,需要把所有它的实现类都定义为bean,而且它的实现类上是没有加Spring默认会扫描的注解的,比如@Component@Service等。

public interface HelloService {

    void doSomething();
    
}

public class HelloServiceA implements HelloService {

    @Override
    public void doSomething() {
        System.out.println("Hello A");
    }

}

public class HelloServiceB implements HelloService {

    @Override
    public void doSomething() {
        System.out.println("Hello B");
    }

}

现定义了一个ImportSelector实现类HelloImportSelector,直接指定了需要把HelloService接口的实现类HelloServiceA和HelloServiceB定义为bean。

public class HelloImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {HelloServiceA.class.getName(), HelloServiceB.class.getName()};
    }

}

然后定义了@Configuration配置类HelloConfiguration,指定了@Import的是HelloImportSelector。

@Configuration
@Import(HelloImportSelector.class)
public class HelloConfiguration {

}

这样当加载配置类HelloConfiguration的时候会一并把HelloServiceA和HelloServiceB注册为Spring bean。可以进行如下简单测试:

@ContextConfiguration(classes=HelloConfiguration.class)
@RunWith(SpringRunner.class)
public class HelloImportSelectorTest {

    @Autowired
    private List<HelloService> helloServices;
    
    @Test
    public void test() {
        this.helloServices.forEach(HelloService::doSomething);
    }
    
}

看到这里可能你会觉得其实它也没什么用,因为整一个ImportSelector实现类那么麻烦,还不如直接在HelloConfiguration中定义bean或者import。在不引入ImportSelector的情况下,下面的两种方式都可以达到相同的效果。

@Configuration
@Import({HelloServiceA.class, HelloServiceB.class})
public class HelloConfiguration {
    
}
@Configuration
public class HelloConfiguration {

    @Bean
    public HelloServiceA helloServiceA() {
        return new HelloServiceA();
    }
    
    @Bean
    public HelloServiceB helloServiceB() {
        return new HelloServiceB();
    }
    
}

如果直接是固定的bean定义,那完全可以用上面的方式代替,但如果需要动态的带有逻辑性的定义bean,则使用ImportSelector还是很有用处的。因为在它的selectImports()你可以实现各种获取bean Class的逻辑,通过其参数AnnotationMetadata importingClassMetadata可以获取到@Import标注的Class的各种信息,包括其Class名称,实现的接口名称、父类名称、添加的其它注解等信息,通过这些额外的信息可以辅助我们选择需要定义为Spring bean的Class名称。现假设我们在HelloConfiguration上使用了@ComponentScan进行bean定义扫描,我们期望HelloImportSelector也可以扫描@ComponentScan指定的Package下HelloService实现类并把它们定义为bean,则HelloImportSelector和HelloConfiguration可以改为如下这样:

@Configuration
@ComponentScan("com.elim.spring.core.importselector")
@Import(HelloImportSelector.class)
public class HelloConfiguration {

    
}
public class HelloImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
        String[] basePackages = (String[]) annotationAttributes.get("basePackages");
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        TypeFilter helloServiceFilter = new AssignableTypeFilter(HelloService.class);
        scanner.addIncludeFilter(helloServiceFilter);
        Set<String> classes = new HashSet<>();
        for (String basePackage : basePackages) {
            scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));
        }
        return classes.toArray(new String[classes.size()]);
    }

}

可以看到在HelloImportSelector的实现中获取了HelloConfiguration类上标注的@ComponentScan的basePackages属性值,并使用ClassPathScanningCandidateComponentProvider进行了扫描。可能有的时候你不希望依赖于配置类上的@ComponentScan,而期望直接扫描配置类所在的包。此时可以通过importingClassMetadata.getClassName()获取配置类的Class名称,进而获取其package名称。

public class HelloImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        String packageName = null;
        try {
            packageName = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        String[] basePackages = new String[] {packageName};
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        TypeFilter helloServiceFilter = new AssignableTypeFilter(HelloService.class);
        scanner.addIncludeFilter(helloServiceFilter);
        Set<String> classes = new HashSet<>();
        for (String basePackage : basePackages) {
            scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));
        }
        return classes.toArray(new String[classes.size()]);
    }

}

更通用一点的做法可能你还是期望扫描的package跟@Configuration上的@ComponentScan的basePackages保持一致或者在没有指定@ComponentScan时扫描配置类所在的package。@ComponentScan的basePackages如果没有指定,默认是把配置类当前所在的package当做basePackage。所以为了满足这些需求,我们的HelloImportSelector可以定义为如下这样:

public class HelloImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        String[] basePackages = null;
        if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
            basePackages = (String[]) annotationAttributes.get("basePackages");
        }
        if (basePackages == null || basePackages.length == 0) {//ComponentScan的basePackages默认为空数组
            String basePackage = null;
            try {
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            basePackages = new String[] {basePackage};
        }
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        TypeFilter helloServiceFilter = new AssignableTypeFilter(HelloService.class);
        scanner.addIncludeFilter(helloServiceFilter);
        Set<String> classes = new HashSet<>();
        for (String basePackage : basePackages) {
            scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));
        }
        return classes.toArray(new String[classes.size()]);
    }

}

为ImportSelector定义特定的注解

当我们觉得在@Configuration配置类上使用@Import(HelloImportSelector.class)太麻烦,或者是需要在ImportSelector实现类中使用一些特定的配置时就可以考虑为ImportSelector实现类定义一个特定的注解,在该注解上使用@Import(HelloImportSelector.class)。如下针对上面的HelloImportSelector定义了一个@HelloServiceScan注解,用于扫描HelloService实现类。

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

}

此时,我们的HelloConfiguration类可以改为如下这样,效果跟之前的一样的。

@Configuration
@HelloServiceScan
public class HelloConfiguration {

}

有了自定义注解后,就可以定义自定义注解的属性,以供在扫描bean时进行一些特殊的配置。比如可以把扫描的路径定义到自定义的注解中,而不必依赖于@ComponentScan。下面的代码中就为@HelloServiceScan自定义了属性basePackages和value,它俩互为别名。

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

    @AliasFor("value")
    String[] basePackages() default {};
    
    @AliasFor("basePackages")
    String[] value() default {};
    
}

这样HelloImportSelector在进行bean扫描时可以通过@HelloServiceScan的basePackages属性获取需要扫描的basePackage。

public class HelloImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(HelloServiceScan.class.getName());
        String[] basePackages = (String[]) annotationAttributes.get("basePackages");
        if (basePackages == null || basePackages.length == 0) {//HelloServiceScan的basePackages默认为空数组
            String basePackage = null;
            try {
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            basePackages = new String[] {basePackage};
        }
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        TypeFilter helloServiceFilter = new AssignableTypeFilter(HelloService.class);
        scanner.addIncludeFilter(helloServiceFilter);
        Set<String> classes = new HashSet<>();
        for (String basePackage : basePackages) {
            scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));
        }
        return classes.toArray(new String[classes.size()]);
    }

}

@Configuration配置类上就可以为@HelloServiceScan指定额外的basePackages属性了。

@Configuration
@HelloServiceScan("com.elim.spring.core.importselector")
public class HelloConfiguration {

}

(注:本文基于Spring 5.0.7所写)

0
0
分享到:
评论

相关推荐

    Spring————面试题库

    Spring是一个开源的Java平台,它是Java应用程序开发的一个综合和广泛的基础支持平台。Spring框架的目的是帮助Java开发者解决在开发应用程序时遇到的基础性问题,让开发者能够专注于业务逻辑的开发,而不必担心底层...

    Spring系列——MVC框架整合.md

    Spring系列——MVC框架整合.md

    SpringCloud——分布式配置中心(Spring Cloud Config)

    在微服务架构中,Spring Cloud Config 是一个强大的分布式配置中心,它允许开发人员将应用程序的配置存储在远程仓库中,并且可以在运行时动态地管理和更新这些配置,无需重启应用。这个特性对于大型分布式系统来说...

    Spring Mvc——第一个应用程序

    **Spring MVC —— 第一个应用程序** Spring MVC 是 Spring 框架的一个模块,主要用于构建 Web 应用程序。它提供了一种模型-视图-控制器(MVC)架构,简化了开发过程,使得开发者可以专注于业务逻辑而不必过于关心...

    征服Spring AOP—— Schema

    本文将深入探讨“Spring AOP——Schema”,这是Spring AOP的一种配置方式,通过XML schema定义切面和通知。 首先,我们需要理解AOP的基本概念。面向切面编程是一种编程范式,旨在提高软件的模块化程度,将关注点...

    Quartz Spring整合——附带webservice Demo

    在"Quartz Spring整合——附带webservice Demo"的项目中,我们可以看到如何将这两个强大的工具结合在一起。这个Demo可能包含了一个使用Quartz调度器来触发Web服务调用的示例。Web服务(Webservice)是一种基于标准的...

    SpringCloud——Zookeeper(注册中心)

    SpringCloud为Zookeeper提供了一个名为`spring-cloud-starter-zookeeper`的启动器,允许开发者轻松地将Zookeeper集成到SpringBoot应用中。首先,我们需要在项目中引入依赖: ```xml &lt;groupId&gt;org.springframework...

    SpringCloud——声明性REST客户端(Feign)

    【SpringCloud——声明性REST客户端(Feign)】 在分布式微服务架构中,服务之间的通信是至关重要的。Spring Cloud提供了一种优雅的方式,通过Feign客户端来实现这一目标。Feign是一个声明式的Web服务客户端,它...

    Spring特性——事件驱动模型

    本篇文章将深入探讨Spring框架的一个重要特性——事件驱动模型。通过理解这一特性,开发者可以更好地利用Spring来实现应用间的通信和协调,提高系统的灵活性和可扩展性。 事件驱动模型是一种设计模式,它基于发布/...

    SpringCloud——分布式跟踪(Sleuth)

    Spring Cloud Sleuth是Spring Cloud生态系统的一部分,它实现了分布式追踪的标准——OpenTracing和Zipkin。通过集成Sleuth,开发者可以在不修改代码的情况下,轻松地在微服务架构中实现请求的全链路追踪。 二、核心...

    Spring框架的简单实现

    我们从一个简单的容器开始,一步步的重构,最后实现一个基本的Spring框架的雏形,为了帮助我们更加深入的理解Spring的IoC...【SSH进阶之路】一步步重构容器实现Spring框架——彻底封装,实现简单灵活的Spring框架(十一)

    SpringCloud——客户端负载平衡器(Ribbon)

    Ribbon是一个客户端负载均衡器,它可以很好地控制HTTP和TCP客户端的行为。

    ProSpring——Spring专业开发指南

    《ProSpring——Spring专业开发指南》是一本深入探讨Spring框架的专业书籍,旨在帮助开发者全面理解和掌握Spring的核心概念、功能及最佳实践。通过阅读本书,你可以深入理解Spring如何为Java应用程序提供强大的依赖...

    Spring开发指南——中文版

    《Spring开发指南——中文版》是由夏昕编著的一本针对Spring框架的中文教程,旨在帮助开发者更好地理解和应用Spring框架。Spring是Java平台上的一个核心框架,广泛应用于企业级应用开发,提供了一种全面的编程和配置...

    Java EE 框架整合开发⼊⻔到实战——Spring+Spring MVC+MyBatis(微课版)课后习题答案.pdf

    这份文档名为《Java EE 框架整合开发入门到实战——Spring+Spring MVC+MyBatis(微课版)课后习题答案.pdf》,它显然是关于Java EE中流行的三个框架整合使用的教程。这三个框架分别是Spring、Spring MVC和MyBatis,...

    小读spring ioc源码(一)——整体介绍

    《Spring IOC源码解析(一)——整体介绍》 在深入理解Spring框架的过程中,源码分析是不可或缺的一环。本文将对Spring的IOC(Inversion of Control,控制反转)容器的源码进行初步探讨,旨在帮助读者从整体上把握...

Global site tag (gtag.js) - Google Analytics