MyBatis入门-Mapper接口动态代理

MyBatis中Mapper接口动态代理实现原理

本篇内容:

  • 了解到为什么Mapper接口没有实现类却能够被正常调用

实践-无参数

自定义代理类,该对象有sqlSession和mapper两个属性,sqlSession执行mapper中的方法交给代理方法来实现。

InvocationHandler:当程序使用反射为指定接口生成动态代理对象,这个动态代理对象的实现类实现了一个或多个接口。动态代理对象就需要实现一个或多个接口里定义的所有方法。当执行动态代理对象里的方法时,实际上会替换成调用InvocationHandler对象的invoke方法。

/**
 * 使用Java动态代理方式创建一个代理类
 * @author: [email protected] 2021/3/18  14:26
 */
public class MyMapperProxy<T> implements InvocationHandler {
    
    
    private static final Logger LOGGER = LoggerFactory.getLogger(MyMapperProxy.class);

    /**
     * mapper对象
     * 被代理的主要对象
     */
    private Class<T> mapperInterface;
    /**
     * sqlSession
     * 用来执行mapper中的接口
     */
    private SqlSession sqlSession;


    public MyMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
    
    
        this.mapperInterface = mapperInterface;
        this.sqlSession = sqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        System.out.println("====================进入代理方法==========================");
        // 针对不同的sql类型,需要调用sqlSession不同的方法
        // 接口方法中的参数也有很多情况,这里只考虑没有参数的情况
        List<T> list = sqlSession.selectList(mapperInterface.getName() + "." + method.getName());
        System.out.println("====================代理方法执行完毕==========================");
        return list;
    }
}

测试类:

public class MyMapperProxyTest extends BaseMapperTest {
    
    

    @Test
    public void invoke() {
    
    
        SqlSession sqlSession = getSqlSession();

        MyMapperProxy<CountryMapper> proxy = new MyMapperProxy<CountryMapper>(
                CountryMapper.class,
                sqlSession
        );
        CountryMapper countryMapper = (CountryMapper) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[]{
    
    CountryMapper.class},
                proxy);
        countryMapper.selectAll();
    }
}

测试结果

====================进入代理方法==========================
Thu Mar 18 14:44:52 CST 2021 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
==>  Preparing: select id,countryname,countrycode from country 
==> Parameters: 
<==    Columns: id, countryname, countrycode
<==        Row: 1, 中国, CN
<==        Row: 2, 美国, US
<==        Row: 3, 俄罗斯, RU
<==        Row: 4, 英国, GB
<==        Row: 5, 法国, FR
<==      Total: 5
====================代理方法执行完毕==========================

总结:从MyMapperProxy类中的invoke方法可知,先通过接口的全限定名称和当前调用的方法名组合得到一个方法id,这个id值映射到xml中namespace和具体方法id的组合。所以可以在代理方法中使用sqlSession以命名空间的方式调用方法。

这种方式没有对具体类进行代理,而是通过代理转化成了对其他代码的调用。

实践-有参数

代理类:修改sqlSession执行接口的方法

public class MyMapperProxy2<T> implements InvocationHandler {
    
    
    private static final Logger LOGGER = LoggerFactory.getLogger(MyMapperProxy2.class);

    /**
     * mapper对象
     * 被代理的主要对象
     */
    private Class<T> mapperInterface;
    /**
     * sqlSession
     * 用来执行mapper中的接口
     */
    private SqlSession sqlSession;


    public MyMapperProxy2(Class<T> mapperInterface, SqlSession sqlSession) {
    
    
        this.mapperInterface = mapperInterface;
        this.sqlSession = sqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        System.out.println("====================进入代理方法==========================");
        T mapper = sqlSession.getMapper(mapperInterface);
        Object invoke = method.invoke(mapper, args);
        System.out.println("====================代理方法执行完毕==========================");
        return invoke;
    }
}

测试类

public class MyMapperProxy2Test extends BaseMapperTest {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(MyMapperProxy2Test.class);

    @Test
    public void invoke() {
    
    
        SqlSession sqlSession = getSqlSession();

        MyMapperProxy2<SysUserMapper> mapperProxy2 = new MyMapperProxy2<>(SysUserMapper.class, sqlSession);

        SysUserMapper sysUserMapper = (SysUserMapper) Proxy.newProxyInstance(SysUserMapper.class.getClassLoader(), new Class[]{
    
    SysUserMapper.class}, mapperProxy2);

        SysUser sysUser = sysUserMapper.queryById(1L);

        LOGGER.info("用户信息:{}",sysUser.toString());

        List<SysRole> sysRoles = sysUserMapper.selectRoleByUserIdAndRoleEnabled(1L, 1);

        LOGGER.info("角色信息:{}",sysRoles.toString());
    }
}

测试结果:

====================进入代理方法==========================
==>  Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from mybatis.sys_user where id = ? 
==> Parameters: 1(Long)
<==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<==        Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<==      Total: 1
====================代理方法执行完毕==========================
====================进入代理方法==========================
==>  Preparing: SELECT r.id, r.role_name, r.enabled, r.create_by, r.create_time FROM `sys_user` u INNER JOIN sys_user_role ur ON u.id = ur.user_id INNER JOIN sys_role r ON r.id = ur.role_id WHERE u.id = ? AND r.enabled = ? 
==> Parameters: 1(Long), 1(Integer)
<==    Columns: id, role_name, enabled, create_by, create_time
<==        Row: 1, 管理员, 1, 1, 2021-01-28 21:58:32.0
<==        Row: 2, 普通用户, 1, 1, 2021-01-28 21:58:32.0
<==      Total: 2
====================代理方法执行完毕==========================

猜你喜欢

转载自blog.csdn.net/weixin_43169156/article/details/114980873