期望目标

  1. 提供一个可以扫描指定包的注解
  2. 该包下,全部为接口类型
  3. 在spring boot环境中,可以正常注入扫描包内的全部接口
  4. 扫描包内的接口,使用代理模式实现,且,方法执行时,执行自定义代码段

思路解析

  1. 自定义一个注解
  2. 该注解需要具备一个数组参数,用于存储扫描包的路径
  3. 已知包内均为接口类型,也就是说,没有实现类,ioc注入必然报错,需要动态创建代理类
  4. 代理模式两种方案,jdk需要预先实现相关接口,不太方便,cglib可能会更方便一些,所以使用cglib去实现相关功能
  5. 该代理实现需要被ioc管理

代码实现

pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.2</version>
        </dependency>
       
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
   
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

设计CGLIB代理拦截器,进行方法拦截

package com.example.demo.bean;

import org.springframework.aop.support.AopUtils;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * cglib 代理模式核心方法拦截器
 * @author chunyang.leng
 * @date 2022-01-21 7:25 PM
 */
public class MyProxyInterceptor implements MethodInterceptor {

    /**
     * 执行方法拦截器
     * @param obj 被代理的目标对象实例对象,该对象为cglib代理实现,为自定义实现类
     * @param method 被代理接口正在执行的方法
     * @param args 被代理接口正在执行方法的参数
     * @param proxy 这是啥,我也不知道。。。。。。
     * @return 方法执行结果,如果方法无返回值,可以返回null
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        
        // 获取被代理目标的真实 class
        Class<?> targetClass = obj.getClass();
        boolean aopProxy = AopUtils.isAopProxy(obj);
        if (aopProxy) {
            targetClass = AopUtils.getTargetClass(obj);
        }
        String name = targetClass.getName();
        
        // 参考cglib代理实现命名规范
        String[] split = name.split("\\$\\$");

        System.out.println("class name =====>" + split[0]);

        System.out.println("method name =====>" + method.getName());
        return null;
    }
}

设计一个代理实现类 MyProxyBean

package com.example.demo.bean;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.cglib.proxy.Enhancer;

/**
 * @author chunyang.leng
 * @date 2022-01-21 3:46 PM
 */
public class MyProxyBean<T> implements FactoryBean<T> {

    /**
     * 被代理的接口
     */
    private Class<T> myInterfaceClass;

    /**
     * 构造函数
     * @param myInterfaceClass 被代理的接口类型,该参数,由注解解析器自动赋值
     */
    public MyProxyBean(Class<T> myInterfaceClass) {
        this.myInterfaceClass = myInterfaceClass;
    }

    /**
     * 返回该bean是否为单例类型
     * @return
     */
    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }

    /**
     * 当 ioc 容器提取对象时,调用此方法获取一个代理对象
     * @return
     * @throws Exception
     */
    @Override
    public T getObject() throws Exception {

        //用于创建代理对象的增强器,可以对目标对象进行扩展
        Enhancer enhancer = new Enhancer();
        //将目标对象设置为父类
        enhancer.setSuperclass(myInterfaceClass);
        //设置目标拦截器
        enhancer.setCallback(new MyProxyInterceptor());
        // 创建代理对象
        return (T)enhancer.create();

    }

    /**
     * 当 ioc 容器获取类型时,从此方法获取类型
     * @return
     */
    @Override
    public Class<T> getObjectType() {
        return myInterfaceClass;
    }
}

自定义路径扫描器 MyProxyScan

package com.example.demo.bean;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;

import java.io.IOException;
import java.util.Set;

/**
 * 自定义路径扫描器
 * @author chunyang.leng
 * @date 2022-01-21 4:23 PM
 */
public class MyProxyScan extends ClassPathBeanDefinitionScanner {

    /**
     * 创建一个自定义的路径扫描器
     * @param registry 需要传入 BeanDefinitionRegistry,该对象默认实现为 DefaultListableBeanFactory
     */
    public MyProxyScan(BeanDefinitionRegistry registry) {
        super(registry);
    }


    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        return super.doScan(basePackages);
    }

    /**
     * 修改判断实现逻辑,仅判断该类型是否是接口类型
     * @param metadataReader 元数据
     * @return true 是候选组件
     * @throws IOException
     */
    @Override
    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        return metadataReader.getClassMetadata().isInterface();
    }

    /**
     * 修改判断实现逻辑,仅判断该类型是否是接口类型
     * @param beanDefinition 元数据
     * @return true 是候选组件
     * @throws IOException
     */
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface();
    }
}

自定义注解解析器 MyProxyBeanImportSelector

package com.example.demo.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.CollectionUtils;

import java.util.Map;
import java.util.Set;

/**
 * MyProxy 注解解析器,用于实现自定义扫描和bean注册
 *
 * @author chunyang.leng
 * @date 2022-01-21 3:47 PM
 */
@Configuration
public class MyProxyBeanImportSelector implements ImportSelector, BeanFactoryAware {

    /**
     * 暂存的bean工厂对象,用于注册和扫描bean
     */
    private BeanFactory beanFactory;

    /**
     * 通过实现 BeanFactoryAware 接口,获取 BeanFactory 具体实现,用于bean扫描和注册
     *
     * @param beanFactory BeanFactory 的具体实现是 DefaultListableBeanFactory
     * @throws BeansException
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }



    /**
     * 实现具体的扫描流程和注册流程
     *
     * @param importingClassMetadata 注解元数据
     * @return 故意返回0,使用其它方式注册代理bean
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 转换为 DefaultListableBeanFactory 便于操作
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;

        // 获取自定义注解的具体数据信息
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(MyProxy.class.getName());

        if (!CollectionUtils.isEmpty(annotationAttributes)) {
            // 获取配置的扫描包路径
            String[] scanPackages = (String[]) annotationAttributes.get("scanPackages");

            // 自定义扫描器,获取 BeanDefinitionHolder
            MyProxyScan myProxyScan = new MyProxyScan(defaultListableBeanFactory);
            Set<BeanDefinitionHolder> beanDefinitionHolders = myProxyScan.doScan(scanPackages);

            // 循环处理每个被扫描到的bean对象,创建代理实现类
            for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {

                // 转换为抽象实现,便于操作具体对象
                AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanDefinitionHolder.getBeanDefinition();

                try {

                    // 获取当前被扫描到到接口到具体 class name
                    String beanClassName = beanDefinition.getBeanClassName();
                    // 装载 class
                    Class<?> targetInterface = Class.forName(beanClassName);

                    // 为该自定义的代理实现类,提供构造方法所需要的原始接口类型参数
                    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(targetInterface);


                    // 修改被扫描接口到具体实现类型,替换为自定义到代理实现类
                    beanDefinition.setBeanClassName(MyProxyBean.class.getName());

                    String beanName = beanDefinitionHolder.getBeanName();
                    System.out.println("已修改===>" + beanName);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
        // 故意返回空
        return new String[0];
    }


}

自定义注解 MyProxy

package com.example.demo.bean;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * @author chunyang.leng
 * @date 2022-01-21 3:55 PM
 */
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyProxyBeanImportSelector.class)
public @interface MyProxy {

    /**
     * scan package
     * @return
     */
    String[] value();
}

在启动器添加自定义注解

package com.example.demo;

import com.example.demo.bean.MyProxy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MyProxy(scanPackages = "com.example.demo.post")
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
        System.out.println("-------------------------------start done-----------------------------------------");
    }
}