explain
When you use mybatis or openFeign, you only define an interface class without an implementation class. You can inject the interface into the service and call the method return value.
An interface has no implementation class. Why can it be instantiated and handed over to spring management. How is mybatis and OpenFeign implemented?
Look at the source code of mybatis. A lot has been deleted and some key initialization steps have been retained.
Directly on the code, link (above gitee)
1. First customize the annotation for the SpringBootApplication startup class. The startup class is annotated with CkScan, and the annotation value is the package interfaces that need to be scanned. When springboot starts, it is found that Import imports the CkScannerRegistrar class in the annotation, and this class will be parsed. This step is to implement the entry. The CkScannerRegistrar class will be explained below
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({CkScannerRegistrar.class}) public @interface CkScan { String[] value() default {}; String[] basePackages() default {}; }
@org.springframework.boot.autoconfigure.SpringBootApplication @MapperScan("com.ck.datacenter.**.dao") @CkScan("com.ck.datacenter.itf") @EnableOpenApi public class SpringBootApplication { public static void main(String[] args) { SpringApplication application = new SpringApplication(SpringBootApplication.class); application.run(); } }
The CkScannerRegistrar class is implemented. When parsing this class, it is found that the importbeandefinitionregister interface of spring is implemented and the registerBeanDefinitions method is rewritten. This method will be called. The key point is the new CkClassPathScanner class, and doScan is called.
public class CkScannerRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { // Get the CkScan value of the SpringBootApplication custom annotation AnnotationAttributes attrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(CkScan.class.getName())); if (attrs != null) { List<String> basePackages = new ArrayList<>(); basePackages.addAll(Arrays.stream(attrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList())); basePackages.addAll(Arrays.stream(attrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList())); //Convert the interface into a BeanDefinition object and put it into spring //CkClassPathScanner is a custom scan class CkClassPathScanner classPathScanner = new CkClassPathScanner(beanDefinitionRegistry); classPathScanner.doScan(StringUtils.collectionToCommaDelimitedString(basePackages)); } } }
The implementation of CkClassPathScanner inherits the ClassPathBeanDefinitionScanner scanning class and rewrites the doScan in it. In the previous step, the doScan method has been called. Enter this method and call the parent class Super doScan (base packages) obtains all the qualified BeanDefinitionHolder objects (a wrapper class for the interface).
Scan filter conditions: the addIncludeFilter method in doScan can add filter conditions, and the isCandidateComponent method can also perform conditional filtering
After obtaining all BeanDefinitionHolder objects, calling processBeanDefinitions for processing is also the key.
public class CkClassPathScanner extends ClassPathBeanDefinitionScanner { public CkClassPathScanner(BeanDefinitionRegistry registry) { super(registry, false); } @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { // The filter is added as the interface class, and the interface contains the CkInterfaceAnnotation annotation return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent() && beanDefinition.getMetadata().hasAnnotation(CkInterfaceAnnotation.class.getName()); } @Override protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) { if (super.checkCandidate(beanName, beanDefinition)) { return true; } else { System.out.println("Skipping MapperFactoryBean with name '" + beanName + "' and '" + beanDefinition.getBeanClassName() + "' mapperInterface. Bean already defined with the same name!"); return false; } } @Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { // spring does not scan interfaces by default. Set it to true here and do not filter this.addIncludeFilter((metadataReader, metadataReaderFactory) -> true); Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { System.out.println("No scan found CkInterfaceAnnotation Annotation interface"); } else { this.processBeanDefinitions(beanDefinitions); } return beanDefinitions; } private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { // This section defines all interfaces with CkInterfaceAnnotation as beanDefinition objects // The bean object in beanDefinitions points to the proxy class of the interface // When the @ Autowired annotation is used to inject an interface, the interface proxy object is actually injected beanDefinitions.forEach((BeanDefinitionHolder holder) -> { GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); System.out.println("Interface name" + beanClassName); // Set the CkFactoryBean construction method parameters definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); definition.setBeanClass(CkFactoryBean.class); definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE); }); } }
The processBeanDefinitions method is implemented by looking directly at the annotation
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { // It can be understood that all interface classes are converted to beanDefinition objects, // beanName is the interface name. The actual instantiated object corresponding to the bean needs to be obtained from the getObject corresponding to the CkFactoryBean object // When the @ Autowired annotation is used to inject the interface, the interface proxy object is actually injected, that is, the getObject method in the CkFactoryBean class obtains the object beanDefinitions.forEach((BeanDefinitionHolder holder) -> { GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); System.out.println("Interface name" + beanClassName); // Parameters passed by the constructor when instantiating the CkFactoryBean class definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); definition.setBeanClass(CkFactoryBean.class); definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE); }); }
The CkFactoryBean class implements the FactoryBean interface of spring. When spring initializes the bean, it will call the getObject method to get the instance. We only need to return the proxy class of the interface in this method. When we call the interface in service, we actually call the CkInterfaceProxy proxy class that we write.
public class CkFactoryBean<T> implements FactoryBean<T> { private Class<T> ckInterface; public CkFactoryBean() { } public CkFactoryBean(Class<T> ckInterface) { this.ckInterface = ckInterface; } /** * bean Instantiate the object and point to the proxy class */ @Override public T getObject() throws Exception { // Returns the CkInterfaceProxy proxy proxy object return (T) Proxy.newProxyInstance(ckInterface.getClassLoader(), new Class[]{ckInterface}, new CkInterfaceProxy<>(ckInterface)); } /** * bean object type */ @Override public Class<T> getObjectType() { return this.ckInterface; } @Override public boolean isSingleton() { return true; } }
Implementation of CkInterfaceProxy proxy class. For example, when we use OpenFeign, we do processing in this step to obtain the annotation on the class and method, obtain the actual server from the registry through the annotation value on the class, and then splice the annotation path on the method to obtain the complete request path
public class CkInterfaceProxy<T> implements InvocationHandler, Serializable { private final Class<T> ckInterface; public CkInterfaceProxy(Class<T> ckInterface) { this.ckInterface = ckInterface; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (this.ckInterface.isAnnotationPresent(CkInterfaceAnnotation.class)) { // Read annotation on class CkInterfaceAnnotation interfaceAnnotation = this.ckInterface.getAnnotation(CkInterfaceAnnotation.class); System.out.println("Calling interface class name:" + interfaceAnnotation.value()); if (method.isAnnotationPresent(CkMethodAnnotation.class)) { // Annotation on read method CkMethodAnnotation methodAnnotation = method.getAnnotation(CkMethodAnnotation.class); System.out.println("Calling interface method name:" + methodAnnotation.value()); } } return null; } }
test
Add an interface class, and no class implements this interface
Inject the interface into the Controller and call the Controller interface method. The log is printed by the agent class