Mybatis--11动态代理

动态代理

  • JDK自带的代理方式
    • JDK自带代理的代码演示
    • JDK自带代理模式的原理探究
  • CGLib动态代理
  • Mybatis中的代理模式详解

原生的方法调用直接通过SqlSession方法调用:提供了selectOne,selectList,insert,delete…方法。

 //原有方法调用形式
        /**
         * 查询操作:
         * 返回多个结果时,使用selectList
         * 返回的结果不管是单个还是多个,在resultType属性都是返回的Java对象全路径
         *
         * 返回单个结果对象使用selectOne
         */
		sqlSession.selectList("com.tulun.maventest.dao.StudentMapper1.getStudentByName", "%王%");

代理模式是Java中的一种设计模式:
Mybatis--11动态代理_第1张图片
公司里开发的一款软件产品,产品的开发是由开发人员开发,由销售人员去卖产品,产品由销售卖给顾客。

代理模式中代理类和委托类具有相同的接口。
代理类的主要职责就是为委托类预处理消息,过滤消息等。
代理类的对象本身并不是真正的实现服务,而是通过委托类的对象的相关方法,来提供特定的一些服务。
代理类和委托类之间存在关联关系,一个代理类的对象和一个委托类的对象相关联。
访问实际对象,是通过代理对象类方法的。

代理模式分为静态代理和动态代理:

静态代理是在程序编译阶段确定代理对象。
动态代理是在程序运行阶段确定代理对象。

动态代理是在运行时根据Java代码指示动态生成的,相比静态代理,优势在于方便的对代理类的函数进行统一的处理,而不用修改每个代理类的方法。

Java中提供的动态代理方式有两种:JDK自带的动态代理和CGLib实现的代理。

JDK自带的代理方式

JDK自带的代理方式需要实现invocationHandler接口,实现invoke的方法。

JDK自带代理的代码演示

先提供一个共有的接口和委托类实现

接口:

/**
 * 接口类
 * 定义委托类和代理类公共的方法
 */
public interface IUser {
    void talk();
}

委托类:

/**
 * 委托类
 * 实现了接口IUser中的talk方法
 */
public class User implements IUser{
    @Override
    public void talk() {
        System.out.println("doing User.talk");
    }
}

实现动态代理,首先创建一个实现了invocationHandler接口的辅助类:

/**
 * 代理的辅助类
 */
public class UserProxy implements InvocationHandler {
    private Object object;

    public UserProxy(Object o) {
        this.object = o;
    }

    /**
     * 实现动态代理,就需要实现InvocationHandler接口中的invoke方法,该方法有三个参数
     * @param proxy:动态代理生成的代理对象
     * @param method:调用的方法
     * @param args:表示该方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理类提供委托类提供的功能,也可以提供自己特有的功能
        System.out.println("doing UserProxy.invoke");
        //调用委托类提供的方法
        method.invoke(object,args);
        System.out.println("doing UserProxy.invoke end");
        return null;
    }

使用产生代理对象时,需要调用代理辅助类,调用委托方法

    public static void main(String[] args) {
        //产生代理对象
        IUser iUser = (IUser) Proxy.newProxyInstance(UserProxy.class.getClassLoader(), new Class[]{IUser.class}, new UserProxy(new User()));
        iUser.talk();
    }

日志打印:
Mybatis--11动态代理_第2张图片
通过日志的观察,当前的代理类调用talk方法,调用到代理辅助类UserProxy中的invoke方法,还调用到了委托类的talk实现。

JVM是如何自动实现invoke方法的调用呢?

JDK自带代理模式的原理探究

在代理类main方法调用中添加一个监控操作

//看看JDK生成的自动代理类
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

通过执行查看,JVM生成了一个$Proxy0.class字节码文件,源码如下:

public final class $Proxy0 extends Proxy implements IUser {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void talk() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.tulun.maventest.proxy.IUser").getMethod("talk");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

$Proxy0的定义中可知,确实实现了IUser接口。和代理模式下的代理是完全一致的
Mybatis--11动态代理_第3张图片

通过分析:当代理类实例中调用方法talk时,根据Java中的多态,调用了$Proxy0的talk方法,$Proxy0重写talk方法。

CGLib动态代理

代理提供了一种可扩展机制来控制被代理对象的访问,就是对象的访问做了一层封装。
当代理没有接口的类,此时Proxy和InvocationHandler机制不能使用了(JDK自带的代理模式的使用必须需要有接口的),此时可以使用CGLib库,采用底层字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截父类所有方法的调用,采用横切的逻辑。Spring AOP(横向切面)的技术就是采用动态代理。

CGLib的使用需要引入第三方库:

    <!--CGLib-->
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.2.2</version>
    </dependency>

父类:

public class CGLibSuper {
    public void doing(){
        System.out.println("CGLibSuper.doing");
    }
}

辅助类:

/**
 * 通过已有的类产生代理类时
 * 在当前辅助类需要实现MethodInterceptor接口
 */
public class CGLibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    public <T> T getProxy(Class<T> clazz){
        //设置需要创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        //通过字节码动态的创建子类实例
        return (T)enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLibProxy doing");
        //拦截到父类响应的方法
        Object o1 = methodProxy.invokeSuper(o, objects);
        System.out.println("CGLibProxy doing end");
        return o1;
    }
}

通过CGLib实现代理:

    public static void main(String[] args) {
        CGLibProxy cgLibProxy = new CGLibProxy();
        //动态产生的CGLibSuper的子类实例
        CGLibSuper proxy = cgLibProxy.getProxy(CGLibSuper.class);
        proxy.doing();
    }

执行结果:
Mybatis--11动态代理_第4张图片
通过执行结果可知,当前产生的代理调用相应的方法(doing),会被代理辅助类中的intercept方法拦截,在该方法中可以实现扩展,也能调用父类中的方法。

Mybatis中的代理模式详解

在这里插入图片描述
Mybatis中产生的代理是哪种方式?
JDK自带的方式:仅在其中实现了接口

mapper是如何添加进去的?
Mybatis--11动态代理_第5张图片
通过代码形式产生的会话工厂实例和读取配置形式是类似的,
通过代码形式中可以看到,Mapper接口是通过Configuration.addMapper()形式来添加,参数为接口的class。

类似于:

 	Configuration configuration = new Configuration();
   	configuration.addMapper(StudentMapper.class); //添加mapper

addMapper方法的实现:

    public <T> void addMapper(Class<T> type) {
        this.mapperRegistry.addMapper(type);
    }

mapper实际上被添加到了MapperRegistry类中,继续:

    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {//只添加接口
            if (this.hasMapper(type)) {//不允许接口重复添加
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));//将接口存放到HashMap中,重点
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }
            }
        }
    }

执行的configuration.addMapper()操作,最终是被放入到HashMap中,其名为knownMappers,knownMappers是MapperRegistry中的属性,它是一个HashMap对象,key为当前Class对象,value为MapperProxyFactory实例。

从getMapper入手分析:

sqlSession中调用getMapper()方法操作,实际调用到DefaultSqlSession中的实现。

DefaultSqlSession类:

    public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }

其中DefaultSqlSession没有逻辑,直接调用configuration中的getMapper方法,configuration是将配置信息存放住。

configuration类中的操作:

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

其中getMapper的实现是调用mapperRegistry中的方法,mapperRegistry是存放了一个HashMap的。

mapperRegistry类:

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);
            }
        }
    }

调用sqlSession.getMapper操作,最终会到上面的这个方法,它会根据接口在HashMap中找到对应的value值,是一个mapperProxyFactory的对象,然后通过调用该对象的newInstance方法,获取到代理对象。

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }

    protected T newInstance(MapperProxy<T> mapperProxy) {
    //JDK自带的代理方式生成的映射代理对象
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}
public class MapperProxy<T> implements InvocationHandler, Serializable {
//当前类实现了InvocationHandler接口

    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;
    }

	//实现invoke方法
    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;
    }
}

Mapper是如何注册的,Mapper接口是如何产生动态代理对象的?

Mapper接口在初始sqlSessionFactory的时候进行注册
Mapper接口注册到了mapperRegistry类的HashMap中,key是Mapper的class,value是当前的Mapper工厂
Mapper注册之后,可以从sqlSession中get获取对象
sqlSession.getMapper使用了JDK自带的动态代理,产生目标接口Mapper的代理对象
动态代理的代理辅助类MapperProxy

你可能感兴趣的:(Mybatis--11动态代理)