Mapper 接口动态代理实现原理

public class MyMapperProxy<T> implements InvocationHandler {

    private Class<T> mapperInterface;

    private SqlSession sqlSession;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 针对不同的 sql 类型,需要调用 sqlSession 的不同方法

      // 接口中的参数也有很多情况,这里只考虑没有参数的情况
      List<T> result = sqlSession.selectList(mapperInterface.getCanonicalName()
                                             + "." + method.getName());
      // 返回值也有很多情况,这里不做处理直接返回
      return result;
    }
}

从上面的代理类中可以看到,当调用一个接口的方法时,会先通过接口的全限定名称当前调用的方法名的组合得到一个方法 id,这个 id 的值就是映射 XML 中 namespace 和具体方法 id 的组合。

该代理类的单元测试方法如下所示:

import com.study.mybatis.config.SpringConfig;
import com.study.mybatis.entity.Country;
import com.study.mybatis.mapper.CountryMapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;

import java.lang.reflect.Proxy;
import java.util.List;

/**
* @author Zhang B H
* @create 2023-09-30 19:11
*/
@ActiveProfiles("qa")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DynamicProxyTest {

   @Autowired
   private SqlSessionFactory sqlSessionFactory;

   private SqlSession sqlSession;

   @Before
   public void setUp() throws Exception {
       sqlSession = sqlSessionFactory.openSession();
   }

   @Test
   public void test() {
       DynamicProxy.MyMapperProxy<CountryMapper> countryMapperProxyHandler = new DynamicProxy.MyMapperProxy<>();
       countryMapperProxyHandler.setMapperInterface(CountryMapper.class);
       countryMapperProxyHandler.setSqlSession(sqlSession);

       CountryMapper countryMapper = (CountryMapper) Proxy.newProxyInstance(
               this.getClass().getClassLoader(),
               new Class<?>[]{CountryMapper.class},
               countryMapperProxyHandler
       );
       List<Country> countries = countryMapper.selectAll();
       Assert.notEmpty(countries, "");
   }

   @After
   public void tearDown() throws Exception {
       sqlSession.close();
   }
}

Last updated