模拟mybatis动态生成Mapper实例的实现
动态代理常用的有两种实现方式,一是java自带的方式,一种是cglib提供的
mybatis使用cglib的动态代理生成mapper实例
这里模拟一下两种实现
常用的mybatis操作数据库的方式如下:
定义一下接口,里面的每个方式对应 *Mapper.xml(如bookMapper.xml)的每个sql
public interface BookMapper { int getCount(); }
使用时一般是
@Autowired private BookMapper bookMapper; public static void main(String[] args) { int count = bookMapper.getCount(); log.info("图片总数量:{}", count); }
下面开始代码实现(这里简化了从xml文件到可解析的sql的过程)
1.java 动态代理 方式
1.1简单定义sql上下文相关类
enum DaoOperation { Creat, Retrieve, Update, Delete } @AllArgsConstructor @Data class DaoContext { private DaoOperation operation; private String sql; } class DaoInfo { // 这个类的Map就可以通过检索所有 xml的方式来生成生成 static Map<String, DaoContext> map = ImmutableMap.<String, DaoContext>builder() .put("getCount", new DaoContext(DaoOperation.Retrieve, "select count(1) from book")) .build(); }
1.2动态代理
import com.google.common.collect.ImmutableMap; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Map; import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j;
@Slf4j class CustomerMapperMethodProxy implements InvocationHandler { public static Object getInstance(Class<?> cls) { CustomerMapperMethodProxy invocationHandler = new CustomerMapperMethodProxy(); Object newProxyInstance = Proxy.newProxyInstance(cls.getClassLoader(), new Class[]{cls}, invocationHandler); return newProxyInstance; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 当前method所属的class Class classz = method.getDeclaringClass(); if (classz.equals(Object.class)) { //调用的是Object的方法,如 toString(),equal()等 throw new RuntimeException("不是Mapper方法"); } // 获取方法名对应的信息. DaoContext daoContext = DaoInfo.map.get(method.getName()); if (daoContext == null) { throw new RuntimeException("没有实现该方法的sql语句"); } return execSql(daoContext); } private Object execSql(DaoContext daoContext) { // 这里可以引入DAO来真正执行sql log.info("正在执行sql语句,类型为:{},语句为:{}", daoContext.getOperation().toString(), daoContext.getSql()); return 1; } }
1.3 测试代码
/** * java 动态代理. */ @Slf4j public class DynamicProxyTest { /** * 动态生成的bookMapper实例 * 这个可以做成bean的方式给spring使用 */ private static BookMapper bookMapper = (BookMapper) CustomerMapperMethodProxy.getInstance(BookMapper.class); public static void main(String[] args) { int count = bookMapper.getCount(); log.info("图片总数量:{}", count); String s = bookMapper.toString(); log.info("toString:{}", s); } }
结果
10:51:50.893 [main] INFO com.g2.proxy.java.CustomerMapperMethodProxy - 正在执行sql语句,类型为:Retrieve,语句为:select count(1) from book 10:51:52.037 [main] INFO com.g2.proxy.java.DynamicProxyTest - 图片总数量:1 Disconnected from the target VM, address: '127.0.0.1:12200', transport: 'socket' Exception in thread "main" java.lang.RuntimeException: 没有实现该方法的sql语句 at com.g2.proxy.java.CustomerMapperMethodProxy.invoke(DynamicProxyTest.java:52) at com.sun.proxy.$Proxy0.toString(Unknown Source) at com.g2.proxy.java.DynamicProxyTest.main(DynamicProxyTest.java:32)
2.cglib实现
import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import lombok.extern.slf4j.Slf4j; /** * Created by zhangjy on 2020/1/19. */ @Slf4j public class CglibTest { private static BookMapper bookMapper = (BookMapper) SqlProxy.getInstance(BookMapper.class); public static void main(String[] args) { int count = bookMapper.getCount(); log.info("图片总数量:{}", count); } } @Slf4j class SqlProxy implements MethodInterceptor { public static Object getInstance(Class<?> clz) { MethodInterceptor target = new SqlProxy(); //创建加强器,用来创建动态代理类 Enhancer enhancer = new Enhancer(); //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类) enhancer.setSuperclass(clz); //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦 enhancer.setCallback(target); // 创建动态代理类对象并返回 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { Class classz = method.getDeclaringClass(); if (classz.equals(Object.class)) { //调用的是Object的方法,如 toString(),equal()等 throw new RuntimeException("不是Mapper方法"); } // 获取方法名对应的信息. DaoContext daoContext = DaoInfo.map.get(method.getName()); if (daoContext == null) { throw new RuntimeException("没有实现该方法的sql语句"); } return execSql(daoContext); } private Object execSql(DaoContext daoContext) { // 这里可以引入DAO来真正执行sql log.info("正在执行sql语句,类型为:{},语句为:{}", daoContext.getOperation(), daoContext.getSql()); return 1; } }
3.两种动态代理的区别
JDK动态代理是通过"组合"方式,将业务类当做动态生成的代理类的一个属性且实现业务类的各个方法,最终调用业务实现类的同名方法;
CGlib动态代理是通过"继承"方式,继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;(效率较高,各大框架都使用这种方式)