1. Write in front
Although some mainstream frameworks now basically have ready-made Springboot starter packages for us to quickly integrate into our Springboot project, this will make us rely too much on this method and only use it, but we don't know how to implement the bottom layer, and we will be at a loss in case of problems. Therefore, writing a component by ourselves can make us better understand the basic routines of these components, and we can also have some ideas when we need to see the source code in case of problems.
Next, we will write a simple component based on Springboot, which can be used through the custom @ EnableXXX annotation. Then we only need to define an interface. The interface uses our custom annotation. We can automatically generate a proxy class for the interface and print out the execution parameters and method names of the methods in the interface.
2. Write a @ EnableXXX to import the used components
2.1 build an empty Maven project first
Then, spring boot start is introduced respectively. The pom file is as follows
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.joe</groupId> <artifactId>test-customize</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- springboot-starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.5.0</version> </dependency> </dependencies> </project>
2.2 write our startup annotations and annotations you want to scan
2.2.1 startup notes are as follows, similar to @ EnableMybaties or @ EnableFeign
package com.joe.customize.annotation; import com.joe.customize.register.CustomizeRegister; import org.springframework.context.annotation.Import; import java.lang.annotation.*; /** * @author joe * @date 2021/7/22 23:41 * @description * @csdn joe# */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented // Introduce the registration class of the registration bean @Import(CustomizeRegister.class) public @interface EnableCustomize { /** * Scanned base package */ String[] basePackages() default {}; }
Note: the above @ Import(CustomizeRegister.class) imports our custom class registrar, which will be described below
2.2.2 user defined scan annotation
Custom scan annotations are similar to @ Mapper or @ FeignClient
package com.joe.customize.annotation; import java.lang.annotation.*; /** * @author joe * @date 2021/7/22 23:39 * @description * @csdn joe# */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CustomizeAnnotation { }
2.3 writing custom factory classes
In the custom factory class, we will generate the proxy class we need for the scanned interface class that uses our @ customenannotation
package com.joe.customize.factory; import org.springframework.beans.factory.FactoryBean; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; /** * @author joe * @date 2021/7/23 0:49 * @description Custom bean factory to produce its own objects * @csdn joe# */ public class CustomizeFactoryBean implements FactoryBean<Object> { private Class<?> type; @Override public Object getObject() { // Here, the dynamic proxy of jdk is used to generate the proxy class. We can customize the logic of the proxy class, Object o = Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{this.type}, (Object proxy, Method method, Object[] args) -> { // Here we can customize the logic we want // For example, in mybatis, the mapper interface defined by us is converted into an executed sql statement // Another example is the request initiated by OpenFeign // Here, I simply change the result of method execution into method name and parameter list return method.getName() + ":" + Arrays.toString(args); }); return o; } @Override public Class<?> getObjectType() { return this.type; } public Class<?> getType() { return type; } public void setType(Class<?> type) { this.type = type; } }
2.4 focus on customizing our bean registrar
The bean registrar will implement the ImportBeanDefinitionRegistrar interface provided by spingboot. In the Registrar, we can generate our own classes and give them to Springboot to manage for us
package com.joe.customize.register; import com.joe.customize.annotation.CustomizeAnnotation; import com.joe.customize.annotation.EnableCustomize; import com.joe.customize.factory.CustomizeFactoryBean; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * @author joe * @date 2021/7/22 23:42 * @description * @csdn joe# */ public class CustomizeRegister implements ImportBeanDefinitionRegistrar { /** * Scan custom annotations and register as bean s * @param metadata * @param registry */ @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { /* Create scanner */ // false, does not contain the default filter. Now add your own filter ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false){ /** * Override to filter the scanned class * @param beanDefinition Scanned class definitions * @return */ @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { // Class metadata AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); // Is a separate class and is not an annotation if (annotationMetadata.isIndependent()) { if (!annotationMetadata.isAnnotation()){ // Class meeting requirements return true; } } // Default does not meet requirements return false; } }; // Configure the annotation name to be scanned AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(CustomizeAnnotation.class); scanner.addIncludeFilter(annotationTypeFilter); // Get the basic package configuration in the annotation Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(EnableCustomize.class.getName()); String[] basePackages = (String[])annotationAttributes.get("basePackages"); // Scan annotated interfaces Set<BeanDefinition> allCandidateComponents = new HashSet<>(); if (basePackages.length > 0){ for (String basePackage : basePackages) { allCandidateComponents.addAll(scanner.findCandidateComponents(basePackage)); } } // Generate a proxy object for the scanned interface if (!CollectionUtils.isEmpty(allCandidateComponents)){ for (BeanDefinition candidateComponents : allCandidateComponents) { // First judge whether the scanned interface is an interface. Maybe the annotation is written on the class, or it can be scanned AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateComponents; AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@CustomizeAnnotation Annotations can only be used on interfaces"); // Create our custom factory instance CustomizeFactoryBean customizeFactoryBean = new CustomizeFactoryBean(); // Set type String className = annotationMetadata.getClassName(); Class clazz = ClassUtils.resolveClassName(className, null); customizeFactoryBean.setType(clazz); // Bean objects are defined through bean definition constructors BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder // Here, we call our custom bean factory to create bean objects .genericBeanDefinition(clazz, customizeFactoryBean::getObject); // Set the automatic injection mode to automatic injection by type beanDefinitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); // Set lazy loading beanDefinitionBuilder.setLazyInit(true); AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition(); // Alias of bean String beanName = className.substring(className.lastIndexOf(".") + 1) + "Customize"; String[] beanNames = new String[]{beanName}; // Register to the Spring container BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, beanNames); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); } } } }
Done..
2.5 general process sorting
- First, add our customized @ EnableCustomize annotation on the startup class of springboot, and our bean registration class customeregister will be introduced into the @ EnableCustomize annotation through @ Import
- In the customeregister, we will scan the interface marked with our @ customenannotation annotation, and then create a custom proxy class through our custom customefactorybean
- The custom proxy class generates proxy objects through the dynamic proxy of jdk. In the HandlerMethod of creating proxy objects, we can customize logic to achieve the effect that mybats or Feign only need to write interfaces without implementing classes
3 test
3.1 make the components we write into a jar package
3.2 create a springboot starter web project
slightly
The pom file introduces our custom component package
3.3 write an interface and use our custom annotations without implementing classes
package com.joe.customize.service; import com.joe.customize.annotation.CustomizeAnnotation; /** * @author joe * @date 2021/7/23 0:19 * @description * @csdn joe# */ @CustomizeAnnotation public interface CustomizeService { public String test01(String arg1,String arg2); }
3.4 write a controller and use a custom interface
package com.joe.customize.conrtoller; import com.joe.customize.service.CustomizeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author joe * @date 2021/7/23 1:13 * @description * @csdn joe# */ @RestController @RequestMapping("/test") public class TestController { @Autowired private CustomizeService customizeService; @RequestMapping("/test01") public String test01(){ return customizeService.test01("a","b"); } }
3.5 add a custom @ enablecustome annotation to the startup class
package com.joe.customize; import com.joe.customize.annotation.EnableCustomize; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @EnableCustomize(basePackages = "com.joe.customize") @SpringBootApplication public class CustomizeApplication { public static void main(String[] args) { SpringApplication.run(CustomizeApplication.class, args); } }