SpringBoot custom starter enhanced - scan custom annotations

1. Description

The last article wrote a simple version of SpringBoot custom starter. The core principle is that during SpringBoot startup, it will get meta-inf / spring. Inf in all jar packages under the classpath Factories file, go to org. Org springframework. boot. autoconfigure. Enableautoconfiguration configuration item to judge whether the configured class meets the conditions of automatic assembly.

If the auto configuration class takes effect, you can do a lot of things. The integration of many third-party jar packages is based on this point, such as the integration of Mybatis and Spring.

Let's simulate a scenario, customize an annotation @ Scorpios, and let SpringBoot scan the annotated classes into the Spring container during startup to simulate the extension of third-party applications.

Its implementation ideas and code refer to the Spring source code

2. Steps

  • Introduce corresponding dependencies
  • Write custom annotation: @ Scorpios annotation
  • Write automatic Configuration class: @ Configuration annotation and @ Import annotation
  • Write a specific scan implementation class: CustomEnhanceRegister
  • In resources / meta-inf / spring Configure custom auto assembly classes in factories

The knowledge points in this article involve the use of @ Import annotation and ImportBeanDefinitionRegistrar interface. Before reading, you can refer to the following articles to familiarize yourself with the knowledge points:

There are several ways to register components in the Spring container:

https://blog.csdn.net/zxd1435513775/article/details/100625879

Spring source code series (8) -- how Mybatis is integrated into spring source code analysis

https://blog.csdn.net/zxd1435513775/article/details/121180974

3. Code implementation

directory structure

3.1 pom file

Finally, you should type the customized starter project into a jar for other projects to reference

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

3.2 automatic configuration

For this auto configuration class, configure it in meta-inf / spring In the factories file. Once this autoconfiguration class takes effect, you can do a lot of things. The next Enhanced Edition will be written here.

@Slf4j
@Configuration
@Import(CustomEnhanceRegister.class)
@ConditionalOnClass(CustomEnhanceService.class) // Only when there is a specified class in the class path can the configuration be changed effectively
public class CustomEnhanceAutoConfiguration {

}

3.3 user defined annotation

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scorpios {

    boolean registerBean() default false;

}

3.4 CustomEnhanceRegister class

This class completes the scanning and registration of custom annotations.

The EnvironmentAware interface is implemented here to get the environment variable information of the system.

We are in the configuration file application The configuration items configured in properties will finally be placed in this Environment. Refer to the following article:

Environment variable (Env) and system Property (Property) use

https://blog.csdn.net/zxd1435513775/article/details/103039651

@Slf4j
public class CustomEnhanceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    // Implement the EnvironmentAware interface to get the environment variable information of the system
    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        // Scan the specified package, which can be passed in manually
        String basePackages = "com.scorpios.customenhance";

        List<Class<?>> candidates = scanPackages(basePackages);
        if (candidates.isEmpty()) {
            log.info("Scan the specified package and find no qualified class "(basePackages.toString());
            return;
        }
        // Register scanned classes
        registerBeanDefinitions(candidates, registry);
    }


    private List<Class<?>> scanPackages(String basePackages) {
        List<Class<?>> candidates = new ArrayList<Class<?>>();
        try {
            candidates.addAll(findCandidateClasses(basePackages));
        } catch (IOException e) {
            log.error("Exception occurred while scanning the specified package "(basePackages);
        }
        return candidates;
    }

    // Returns the qualified class under the specified package
    private List<Class<?>> findCandidateClasses(String basePackage) throws IOException {
        List<Class<?>> candidates = new ArrayList<Class<?>>();
        // classpath*:com/scorpios/**/*.class
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                convertPath(basePackage) + '/' + this.DEFAULT_RESOURCE_PATTERN;
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        MetadataReaderFactory readerFactory = new SimpleMetadataReaderFactory(resourceLoader);
        Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader).getResources(packageSearchPath);
        for (Resource resource : resources) {
            MetadataReader reader = readerFactory.getMetadataReader(resource);
            // Filter which classes meet the criteria. Here you can judge the custom annotation @ Scorpios
            if (match(reader.getClassMetadata())) {
                Class<?> candidateClass = transform(reader.getClassMetadata().getClassName());
                if (candidateClass != null) {
                    candidates.add(candidateClass);
                    log.debug("Scan to qualified classes:" + candidateClass.getName());
                }
            }
        }
        return candidates;
    }


    private void registerBeanDefinitions(List<Class<?>> internalClasses, BeanDefinitionRegistry registry) {
        for (Class<?> clazz : internalClasses) {
            String beanName = ClassUtils.getShortNameAsProperty(clazz);
            RootBeanDefinition rbd = new RootBeanDefinition(clazz);
            registry.registerBeanDefinition(beanName, rbd);
            if (registerSpringBean(clazz)) {
                registry.registerBeanDefinition(beanName, new RootBeanDefinition(clazz));
            }
        }
    }


    private String convertPath(String path) {
        return StringUtils.replace(path, ".", "/");
    }

    // Return Class according to Class name
    private Class<?> transform(String className) {
        Class<?> clazz = null;
        try {
            clazz = ClassUtils.forName(className, this.getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
            log.info("The specified class was not found", className);
        }
        return clazz;
    }


    protected boolean match(ClassMetadata metadata) {
        Class<?> clazz = transformToClass(metadata.getClassName());
        if (clazz == null || !clazz.isAnnotationPresent(Scorpios.class)) {
            return false;
        }
        Scorpios scorpios = clazz.getAnnotation(Scorpios.class);
        if (scorpios.registerBean() && isAnnotatedBySpring(clazz)) {
            throw new IllegalStateException("class{" + clazz.getName() + "}Already identified Spring Component annotation");
        }
        // Filter abstract classes, interfaces, annotations, enumerations, inner classes and anonymous classes
        return !metadata.isAbstract() && !clazz.isInterface() && !clazz.isAnnotation() && !clazz.isEnum()
                && !clazz.isMemberClass() && !clazz.getName().contains("$");
    }

    private Class<?> transformToClass(String className) {
        Class<?> clazz = null;
        try {
            clazz = ClassUtils.forName(className, this.getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
            log.info("The specified class was not found", className);
        }
        return clazz;
    }


    private boolean isAnnotatedBySpring(Class<?> clazz) {
        return clazz.isAnnotationPresent(Component.class) || clazz.isAnnotationPresent(Configuration.class)
                || clazz.isAnnotationPresent(Service.class) || clazz.isAnnotationPresent(Repository.class)
                || clazz.isAnnotationPresent(Controller.class);
    }

    private boolean registerSpringBean(Class<?> beanClass) {
        return beanClass.getAnnotation(Scorpios.class).registerBean();
    }
}

3.4 specific business types

Use custom annotations

@Slf4j
@Scorpios
public class CustomEnhanceServiceImpl implements CustomEnhanceService {

    @Override
    public void enhance() {
        log.info("CustomEnhanceServiceImpl...enhance.....");
    }
}

3.5 spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.scorpios.customenhance.autoconfigure.CustomEnhanceAutoConfiguration

4. Test

Create a new project, introduce the above jar package, and automatically inject CustomEnhanceService into the Controller.

@RestController
public class IndexController {

    @Autowired
    CustomEnhanceService customEnhanceService;

    @RequestMapping("/index")
    public String index(){
        customEnhanceService.enhance();
        return "index 8001...";
    }
}

Startup log:

Access interface address: http://localhost:8001/index

The CustomEnhanceService modified by the custom annotation has been called.

Is it possible to integrate third-party applications..... Done!

The specific code address is as follows:

Code address: https://github.com/Hofanking/springboot-custom-starter-example

Keywords: Spring Spring Boot

Added by jawinn on Thu, 13 Jan 2022 13:40:22 +0200