mybatis之动态代理的应用
在前文(https://www.cnblogs.com/NYfor2018/p/9093472.html)我们知道了,Mybatis的使用需要用到Mapper映射文件,一个是映射接口,另一个是映射XML文件(此处不详谈映射文件XML),在应用中我们可以感觉到,映射接口似乎对接着XML文件中的实现命令,可是我们在运行程序是时候调用的往往是Mapper接口,而不是一个包含逻辑的实现类。很显然Mapper产生了代理类。
首先,什么是代理模式?代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。(取自百度百科:https://baike.baidu.com/item/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/8374046?fr=aladdin )
举个栗子,如图:
我们租房的时候一般是去找中介,而不是直接去找包租婆。放在代理模式这里来说,就是,我们在访问真实的对象的时候,往往不是直接去访问真实对象,而是通过代理对象,来对真实对象进行访问。
为什么要使用代理模式?通过代理,一方面可以控制如何访问真正的服务对象,提供额外的服务。另外一方面有机会通过重写一些类来满足特定的需要。就像是,有时候租客反映的一些问题,中介可以直接解决而不需要麻烦到包租婆。
一般来说,动态代理分为两种:一种是JDK反射机制提供的代理;另一种是CGLB代理。在JDK提供的代理,我们必须要提供接口;而在CGLIB中则不需要提供接口。
在学习动态代理之前,先了解一下反射的基础。
反射简单应用
import java.lang.reflect.Method;
public class ReflectService {
public void sayHello(String name) {
System.out.println("hello"+name);
}
public static void main(String[] args) throws Exception, IllegalAccessException, ClassNotFoundException {
//通过反射创建ReflectService对象
Object service = Class.forName(ReflectService.class.getName()).newInstance();
//获取服务方法
Method method = service.getClass().getMethod("sayHello", String.class);
//反射调用方法
method.invoke(service, "zhangsan");
}
}
① 先根据类名,来创建实例对象,所以就找了ReflectService类的类名。
② 然后再根据实例对象来找回它的方法,参数是方法名及其参数。
③ 利用反射机制来调用ReflectService类的sayHello方法。
JDK动态代理
JDK的动态代理,是由JDK的java.lang.reflect.*包提供支持的,我们需要完成以下步骤:
① 编写服务类和接口,服务类是真正的服务提供者,而JDK代理中接口是必要的。
② 编写代理类,提供绑定和代理方法。
首先,先编写动态代理的接口:
public interface HelloService {
public void sayHello(String name);
}
接着,写一个这个接口的实现类:
public class HelloServiceImpl implements HelloService{
@Override
public void sayHello(String name) {
System.out.println("hello "+name);
}
}
然后,写一个代理类,提供真实对象的绑定和代理方法。代理类的要求是实现InvocationHandler接口的代理方法,当一个对象被绑定后,执行其方法的时候就会进入到代理方法里。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class HelloServiceProxy implements InvocationHandler{
/**
* 真实的服务对象
*/
private Object target;
/**
*
* @param target
* @return 绑定委托对象并返回一个代理类
*/
public Object bind(Object target) {
this.target = target;
//取得代理对象
//public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
//loader服务对象的类加载器,interfaces是加载服务对象的接口,h是执行这个代理类的方法的执行者
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
/**
* 通过代理对象调用方法首先进入这个方法
* invoke方法的参数分别是:proxy是代理对象,method是被调用的方法,args是方法的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.err.println("#########我是JDK动态代理##########");
Object result = null;
//反射方法前调用
System.err.println("我准备说hello");
result = method.invoke(target, args);
//反射方法后调用
System.err.println("我说过hello了");
return result;
}
}
最后,编写一个测试类:
public class HelloServiceMain {
public static void main(String[] args) {
HelloServiceProxy HelloHandler = new HelloServiceProxy();
HelloService proxy = (HelloService)HelloHandler.bind(new HelloServiceImpl());
proxy.sayHello("张三");
}
}
运行出来的结果是:
整个过程可以这样理解:
先创建一个代理类对象a,然后用代理类对象绑定真正提供服务对象b,返回一个接口b+,再用这个接口b+来进行服务。
如果我们把proxy.sayHello(“张三”);注释掉:
我们会发现控制台什么东西都没有输出。
所以我们可以知道,代理类的invoke方法,只有在代理的真正提供服务的对象被调用的时候,才会起作用。
所以这时候我们可以这样理解:
挑选的人相当于访问者,相亲交流平台相当于代理,被挑选的人相当于真正提供服务的对象。当挑选的人在向相亲交流平台要求得到被挑选的人的信息,实际上,提供信息的人不是平台,是被挑选的人。挑选的人在对被挑选的人打招呼,而不是对相亲交流平台打招呼。而且,相亲交流平台不只是为一个独特的挑选的人提供被挑选的人的信息,只要有忍看上被挑选的人,相亲交流平台就可以提供ta的信息。
CGLIB动态代理
此处仅突出CGLIB动态代理跟JDK动态代理在代理类上的区别:
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class HelloServiceCgLib implements MethodInterceptor{
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
//回调方法
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.err.println("##########我是CGLIB的动态代理##########");
//反射方法前调用
System.err.println("我准备说hello");
Object returnObj = proxy.invokeSuper(obj, args);
//反射方法后调用
System.err.println("我说过hello了");
return returnObj;
}
}
可以看到CGLIB的代理类是实现MethodInterceptor的代理方法。在mybatis中通常在延迟加载的时候才会用到CGLIB的动态代理。
来源:oschina
链接:https://my.oschina.net/u/4355830/blog/3954315