什么是代理

代理模式,目的就是为其他对象提供一个代理以控制对某个对象的访问,代理类为被代理者处理过滤消息,说白了就是对被代理者的方法进行增强。
看到这里,有没有感觉很熟悉?AOP,我们熟知的面向切面编程,不也是对方法增强,对切点进行处理过滤么。
其实AOP这种设计思想,他的精髓便是,在预编译和运行阶段使用动态代理实现的。

初体验

下面是我自己写的小例子。

//代理的接口
/**
 * @created with IDEA
 * @author: yonyong
 * @version: 1.0.0
 * @date: 2020/4/21
 * @time: 21:13
 **/
public interface person {
    void doSomething();
    void fun1();
    void fun2();
}

//被代理者/委托人
/**
 * @created with IDEA
 * @author: yonyong
 * @version: 1.0.0
 * @date: 2020/4/21
 * @time: 21:13
 **/
public class Student implements person{
    @Override
    public void doSomething() {
        System.out.println("dosomeThing");
    }

    @Override
    public void fun1() {
        System.out.println("fun1");
    }

    @Override
    public void fun2() {
        System.out.println("fun2");

    }
}
//实现InvocationHandler接口,加入切面逻辑
/**
 * @created with IDEA
 * @author: yonyong
 * @version: 1.0.0
 * @date: 2020/4/21
 * @time: 21:15
 **/
public class StudentProxyHandler implements InvocationHandler {

    Student target;

    public StudentProxyHandler(Student target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("pre");
        Object obj = method.invoke(target,args);
        System.out.println("aft");
        return obj;
    }
}
/**
 * @created with IDEA
 * @author: yonyong
 * @version: 1.0.0
 * @date: 2020/4/21
 * @time: 21:19
 **/
public class Main {
    public static void main(String[] args) {
        Student p=new Student();
        person person = (person) Proxy.newProxyInstance(p.getClass().getClassLoader(), p.getClass().getInterfaces(), new StudentProxyHandler(p));
        person.doSomething();
        System.out.println("______________________");
        person.fun1();
        System.out.println("______________________");
        person.fun2();
    }
}

运行代码,我们可以得到:

pre
dosomeThing
after
______________________
pre
dosomeThing
after
______________________
pre
dosomeThing
after

我们来通过打印的结果来实实在在的体会代理模式的优点及aop的特性:
如果有种业务场景,需要有多个方法有重复的代码块,或者相同的实现规则,如果不用aop我们可能要每个方法写一份规则,或者自定义个方法,每个方法调用来实现。首先这就已经使代码变得侵入性,也违反了java的封装重构原则。
而使用java动态代理,无论这个委托人有多少方法,他都会执行切面逻辑里的规则,这样很便于后期的代码维护,即便是后续加入新的方法,也无须考虑其他的,因为在切面里都已经做好了,这样代码的侵入性便降了很多。其实这和AOP的原理是一样的。

动态代理与mybatis

看到这里,我便觉得mybatis和j动态代理必定有着很紧密的联系。
但我们通过上面的例子,我们知道,想要用动态代理,必须要有个接口的实现类,否则代理接口便没什么意义。而mybatis只是接口+xml的形式,他是怎么被代理的呢?
还有一个疑问,我们知道Spring的注解如果放在service的接口层,而不是放在实现类,他会找不到这个bean,那为什么mapper层可以加注解呢?

首先我们通过查询资料知道,mybatis确实是有动态代理实现的。那我们带着这个疑问,去看mybatis的源码。

为了便于查看源码,我使用sqlsession的方式获取mapper。

LRoleMapper lRoleMapper = sqlSessionFactory.openSession().getMapper(LRoleMapper.class);
lRoleMapper.selectAll();

点进getMapper的实现类 SqlSessionManager
发现这里有sql的各个方法

Connection getConnection(){}
void commit() {}
rollback(){}
int insert(String statement){}
update(String statement){}
...

那我们在往下寻找,我看到了这个方法

 private class SqlSessionInterceptor implements InvocationHandler {
        public SqlSessionInterceptor() {
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            SqlSession sqlSession = (SqlSession)SqlSessionManager.this.localSqlSession.get();
            if (sqlSession != null) {
                try {
                    return method.invoke(sqlSession, args);
                } catch (Throwable var12) {
                    throw ExceptionUtil.unwrapThrowable(var12);
                }
            } else {
                SqlSession autoSqlSession = SqlSessionManager.this.openSession();

                Object var7;
                try {
                    Object result = method.invoke(autoSqlSession, args);
                    autoSqlSession.commit();
                    var7 = result;
                } catch (Throwable var13) {
                    autoSqlSession.rollback();
                    throw ExceptionUtil.unwrapThrowable(var13);
                } finally {
                    autoSqlSession.close();
                }

                return var7;
            }
        }
    }

看到这里,是不是眼前一亮,那我们再看这个类的构造方法

 private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionManager.SqlSessionInterceptor());
    }

那看到这里我们明白了,这个类代理的是SqlSessionFactory这个类,而它的切面逻辑,就是执行方法前,如果当前sqlsession存在sqlsession,就正常执行这个方法,如果不存在,就创建一个session,创建失败再回滚数据。
那这里的opensession,我直接贴源码了,无非就是从配置文件读取jdbc,连接验证后,获取会话之类,大家感兴趣可以一层一层往下扒。

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

我们继续回到getMapper方法,我们一层一层往下扒,最后我们可以看到这个类

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

再继续扒newInstance方法

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.binding;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
        } else {
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }
    }

    private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
        if (mapperMethod == null) {
            mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
            this.methodCache.put(method, mapperMethod);
        }

        return mapperMethod;
    }
}

那到这里我们可以得出,mybatis中使用了大量的java动态代理。
这个getMapper呢,到这里也就结束了,可以看到,SqlSession这个对象,代理的接口就是我们的mapper层dao,而这里的切面逻辑,如果当前声明的类是Object,直接执行方法,如果不是,执行另外的excute。这里我没有细看,初步理解是区分注解sql与mapper.xml 的sql。因为获取mapper的sql是通过反射得到的Class类。有兴趣的同学可以继续扒,我精力有限就先到这儿了。
到此为止,第一个问题迎刃而解,总结来说,其实我们注入的mapper,是动态代理产生的对象
那么为什么Mapper层加注解,spring也能获取到呢。
其实这个问题已经和java动态代理没什么关系了,在这里大概解释一下。
mybatis并不是spring的产品,而作为第三方的插件,我们都知道spring被称作胶水框架,而第三方就需要将自己的产品让spring管理。
换句话说,他们和spring自身的bean生命周期并不是同步的。

spring--------------------
class->扫描->新建实例->交给容器

mybatis ---------------------spring---
class -> 扫描-> 新建对象 -> 交给spring

同理,mybatis那就需要自己创建对象,把他交给spring。而我们平时的那些注解Mapperscan之类的,其实只是mybatis在标志这些接口,使用反射,获取这些类,实现一个ImportBeanDefinitionRegistrar接口,把自己产生的对象交给Spring。

posted on 2020-04-22 11:14  yonyong  阅读(655)  评论(3编辑  收藏  举报