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