接口代理之仿mybatis-spring

前言

实现类似于mybatis那样,通过动态代理方式,达到接口方法映射到自定义的服务逻辑实现,以及bean的注册和使用

代码地址

https://gitee.com/BlueDriver/code-demo/tree/master/demo/dynamic-proxy-mapper

相关类定义

Mapper类定义

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

Mapper类的代理实现定义

public class MapperProxy implements InvocationHandler {
    /**
     * 日志对象
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(MapperProxy.class);

    /**
     * applicationContext,用于获取业务处理时获取bean
     */
    private ApplicationContext applicationContext;

    public MapperProxy(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * 代理方法处理逻辑
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        LOGGER.info("方法[{}]代理实现", method.getName());

        if (Object.class.equals(method.getDeclaringClass())) {
            // 是Object类的方法,调用原有实现
            return method.invoke(this, args);
        }

        // 自定义的实现逻辑,任由发挥

        return "代理处理结果:" + method.getName();
    }
}

Mapper类对应的bean

public class MapperFactoryBean<T> implements FactoryBean<T> {
    /**
     * 接口类
     */
    private Class<T> mapperInterClass;
    /**
     * applicationContext
     */
    private ApplicationContext applicationContext;

    public MapperFactoryBean(Class<T> mapperInterClass, ApplicationContext applicationContext) {
        this.mapperInterClass = mapperInterClass;
        this.applicationContext = applicationContext;
    }

    @Override
    @SuppressWarnings("unchecked")
    public T getObject() {
        return (T) Proxy.newProxyInstance(
                this.getClass().getClassLoader(),
                new Class[]{mapperInterClass},
                new MapperProxy(applicationContext));
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterClass;
    }

    /**
     * Is the object managed by this factory a singleton? That is,
     * will {@link #getObject()} always return the same object
     * (a reference that can be cached)?
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}

工具类,用于从指定的包下查找含Mapper注解的接口类

public class ClassFindUtils {
    /**
     * 日志对象
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(ClassFindUtils.class);
    /**
     * (类文件)资源文件通配符
     */
    private static final String RESOURCE_PATTERN = "/**/*.class";


    /**
     * 根据 包路径+注解 查询类
     *
     * @param annotationClass 注解类
     * @param <A>             注解类泛型
     * @param basePackages    包路径(例:com.example.demo)
     * @return 类集合
     * 参考实现:ClassPathScanningCandidateComponentProvider#scanCandidateComponents
     */
    public static <A extends Annotation> Set<Class<?>> findClass(Class<A> annotationClass,
                                                                 String... basePackages) {
        Set<Class<?>> classSet = new HashSet<>();

        for (String basePackage : basePackages) {
            classSet.addAll(findClass(annotationClass, basePackage));
        }

        return classSet;
    }

    /**
     * 根据 包路径+注解 查询类
     *
     * @param annotationClass 注解类
     * @param <A>             注解类泛型
     * @param basePackage     包路径(例:com.example.demo)
     * @return 类集合
     * ClassPathScanningCandidateComponentProvider#scanCandidateComponents
     */
    public static <A extends Annotation> Set<Class<?>> findClass(Class<A> annotationClass,
                                                                 String basePackage) {
        Set<Class<?>> classSet = new HashSet<>();

        try {
            // spring工具类,可以获取指定路径下的全部类
            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

            String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    ClassUtils.convertClassNameToResourcePath(basePackage) + RESOURCE_PATTERN;

            Resource[] resources = resourcePatternResolver.getResources(pattern);

            // MetadataReader 的工厂类
            MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
            for (Resource resource : resources) {
                // 用于读取类信息
                MetadataReader reader = readerFactory.getMetadataReader(resource);
                // 扫描到的class
                String classname = reader.getClassMetadata().getClassName();
                Class<?> clazz = Class.forName(classname);
                // 判断是否有指定主解
                A annotation = clazz.getAnnotation(annotationClass);
                if (annotation != null) {
                    classSet.add(clazz);
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            LOGGER.error("扫描指定包[{}]下含注解[{}]的类异常", basePackage, annotationClass.getName());
            LOGGER.error("异常信息", e);
        }

        return classSet;
    }
}

将Mapper类注册为bean的处理

@Component
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, ApplicationContextAware {
    /**
     * 日志对象
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(MapperScannerConfigurer.class);

    /**
     * mapper类包路径
     */
    private String mapperPackage;
    /**
     * applicationContext
     */
    private ApplicationContext applicationContext;


    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 包路径
        String[] basePackages = StringUtils.tokenizeToStringArray(mapperPackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

        // 在包路径下查找含指定注解的类
        Set<Class<?>> classSet = ClassFindUtils.findClass(Mapper.class, basePackages);

        for (Class<?> clazz : classSet) {
            // 将找到的含Mapper注解的类进行bean定义和注册
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();

            beanDefinition.setBeanClass(MapperFactoryBean.class);
            beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);

            // 设置构造参数(对应 MapperFactoryBean 的构造器)
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(clazz);
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(applicationContext);

            // bean名称采取类名首字母小写的形式
            String beanName = Introspector.decapitalize(ClassUtils.getShortName(clazz));

            BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, beanName);
            BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
            LOGGER.info("为[{}]注册名称为[{}]的bean", clazz.getName(), beanName);
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // do nothing
    }

    /**
     * 从环境配置中读取mapper包路径
     */
    @Override
    public void setEnvironment(Environment environment) {
        mapperPackage = environment.getProperty("code.demo.mapper.package", "code.demo");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

测试

定义一个含Mapper注解的接口

@Mapper
public interface UserDao {
    String getNameById(int id);

    String getAddressById(int id);
}

单测类

@SpringBootTest(classes = ServiceStarter.class)
public class MapperTest {
    @Autowired
    private UserDao userDao;

    @Test
    public void testLogin() {
        String name = userDao.getNameById(1);
        System.err.println(name);

        String address = userDao.getAddressById(2);
        System.err.println(address);
    }
}

结果:

2022-07-10 18:29:27.844  INFO 19840 --- [           main] c.d.d.proxy.mapper.binding.MapperProxy   : 方法[getNameById]代理实现
代理处理结果:getNameById
2022-07-10 18:29:27.846  INFO 19840 --- [           main] c.d.d.proxy.mapper.binding.MapperProxy   : 方法[getAddressById]代理实现
代理处理结果:getAddressById

代码地址

https://gitee.com/BlueDriver/code-demo/tree/master/demo/dynamic-proxy-mapper

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值