動態代理常用的有兩種實現方式,一是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動態代理是通過"繼承"方式,繼承業務類,生成的動態代理類是業務類的子類,通過重寫業務方法進行代理;(效率較高,各大框架都使用這種方式)
