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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM