重温一下代理模式、以及自己在理解代理模式中存在的误区。
记得在大学的时候学校spring的时候,spring的好多特性都是依赖于JDK的动态代理。后来慢慢写代码就出现了至今都流行的代码结构。
controller->service->dao这三层,一层一层调用。在controller调用service层的时候,具体业务的实现就是serviceImpl类里面的具体实现。
误区一:serviceImpl就是service的动态代理的代理类,service是代理类(真的是很长一段时间,我都这么认为),其实serviceImpl才是被代理类,$Proxy数字.class才是代理类,主要死没有搞懂代理模式的含义以及背景
解决误区的措施:在XXXController里@Autowired XXXService,整个项目启动的时候,spring加载XXXController bean的时候,由于里面注入了Service,所以要先实例化Service,这里打断点发现,如果在Service中没有加类似于@Transcationl注解的时候,注入的就是普通Servcie的实现类,因为@Transcationl等一些注解是通过JDK动态代理实现的,因为当有了这些注解的时候,我要对当前的ServiceImpl进行事务等切面处理,所以这时候要生成ServiceImpl的代理类。在spring注入类的时候,具体逻辑可参考以下方法,可以在这里打断点开始debug,一步一步的就可以看到spring是如何创建代理类的。并且可以看到为什么@Service注解设置名字的要小写。
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary
误区一注意1:这里是ServiceImpl的代理类,而不是Service的代理类(这一点至关重要)
误区一注意2:当多个Serviceimpl都实现了Service,那么注入的时候如何告知spring注入哪个呢?可以使用@Qualifier注解指定名字即可
代理模式的背景简介:
如果我先要从JVM A里调用JVM B里的某一个方法B我该如何调用?这种背景下出现了代理模式
最早期的RMI(HeadFirst中关于代理模式有说)的解决方案如下,以下代理模式为远程代理
1、启动一个JVM A和JVM B都能访问的公共仓库
2、将要被调用JVM B启动的时候将自己接口的实现类注册到一个公共的仓库里面(Naming.rebind("ServiceB",ServiceImpl)方法)
3、在JVM A中去公共仓库里找到要调用的方法的类(Naming.lookup("ServiceB")),获得了ServiceImpl的实例,JVM A中用以下方式接收。通过Java的多态,实例化接口的具体实现类
ServiceB service = Naming.lookup("ServiceB");
//上面返回的是接口的具体实现,就相当于
ServiceB service = new ServiceBImpl();
4、通过拿到的service的实例化对象,调用对应的方法即可。
是不是有点类似于现在微服务的注册中心的味道?
代理模式为虚拟代理
如果我的目的要取出一个对象,但是这个对象构造很耗时间,那么在构造完毕之前我会告诉调用这个对象的那一方此时我的状态是什么,当我构造完毕,立刻返回此对象
1、我只对外提供一个可以取出该对象的方法
2、实际取出对象的操作和其他操作都在此方法中
3、让调用者一方认为就是调用此方法获得的对象
其真正逻辑基本为以下代码:
// 接口
public Interface A(){
String printMessage();
}
// 提供给调用者一方的暴露的代理类
public class Aproxy implements A {
ArealImpl aRealImpl;
public String printMessage(){
if(时间没到){
// 其他操作
}else{
aRealImpl = new ArealImpl();
return aRealImpl.printMessage();
}
return "什么都没拿到";
}
}
// 真正实现取出对象的实现类
public class ArealImpl implements A {
public String printMessage(){
return "取出对象";
}
}
虚拟代理看着是不是和JDK的动态代理很像?
建议:阅读HeadFirst中关于代理模式的章节,反复仔细阅读,直至完全理解。
了解了代理模式的产生背景之后,使用JDK的动态代理。
JDK动态代理详解:
JDK动态代理有两种用法,基于某个接口的实现类和基于接口。
1、基于接口实现类(Impl)
public interface ProxyService {
String print(String message);
}
public class ProxyServiceImpl implements ProxyService {
@Override
public String print(String message) {
return message;
}
实现自定义的Handler
public class CommonProxyHandler implements InvocationHandler {
private Object target;
public CommonProxyHandler(Object tarject) {
this.target = tarject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target,args);
}
}
接下来使用
@SpringBootTest
public class CommonProxyTest {
@Test
public void testCommonProxy(){
// 利用java的多态,接收其实例化的实例ProxyServiceImpl
ProxyService proxyService = new ProxyServiceImpl();
// handler中传入目标接口的实例化的对象ProxyServiceImpl
CommonProxyHandler commonProxyHandler = new CommonProxyHandler(proxyService);
// 第一个参数:类加载器
// 第二个参数:被代理类要实现的目标接口
// 第三个参数:handler的对象
// 返回:实现 该接口 的代理类
ProxyService proxyInstance = (ProxyService) Proxy.newProxyInstance(ProxyServiceImpl.class.getClassLoader(),
proxyService.getClass().getInterfaces(), commonProxyHandler);
String s = proxyInstance.print("我是一个小代理");
System.out.println(s);
}
}
上面的例子,是JDK代理的大多数入门例子(基本类似)
基于上面的例子能否想到几个问题
1、生成的代理类的类型是什么?是ProxyService?还是ProxyServiceImpl?
2、自定义的CommonProxyHandler为什么要传入ProxyServiceImpl?传入ProxyService(接口类型的可不可以)?
3、Proxy.newProxyInstance传入的类加载器、接口数组、和自定义的handler作用是什么?
4、invoke中只执行method.invoke吗?
从第三个问题开始说,要知道为什么传这几个参数,就要看看newProxyInstance是如何实现的
两大步:创建代理类和创建代理类的对象
首先JDK会现在缓存中查找,是否存在目标接口的实现类,如果存在则返回,如果不存在则创建,debug进入getProxyClass0(我们传的类加载器,我们传入的接口数组),在这里用到了。
接着调用factory.get()方法中的ProxyClassFactory.apply()方法生成代理类的class文件、并加载进JVM内
generateProxyClass方法是生成代理类的class文件
到这里,getPrxyClass0就执行完毕,利用我们传入的接口、和classloader,生成了一个实现该接口的类,并加载到JVM中 。
本地使用ProxyGenerator.generateProxyClass方法,看看生成的代理类长什么样?
@SpringBootTest
public class GenerateProxyClassTest {
@Test
public static void main(String[] args) throws Exception{
String path = "/Users/jacksparrow414/Downloads/$Proxy1.class";
ProxyService proxyService = new ProxyServiceImpl();
CommonProxyHandler commonProxyHandler = new CommonProxyHandler(proxyService,path);
ProxyService proxyInstance = (ProxyService) Proxy.newProxyInstance(ProxyServiceImpl.class.getClassLoader(), ProxyServiceImpl.class.getInterfaces(), commonProxyHandler);
byte[] proxies = ProxyGenerator.generateProxyClass("$Proxy1", ProxyServiceImpl.class.getInterfaces());
FileOutputStream outputStream = null;
outputStream = new FileOutputStream(path);
outputStream.write(proxies);
outputStream.flush();
outputStream.close();
}
}
在本地找到生成的$Proxy1的class文件,idea打开
// 新生成的代理类实现了我们传入的ProxyService接口,它的类型就是一个$Proxy+数字
public final class $Proxy1 extends Proxy implements ProxyService {
private static Method m1;
private static Method m2;
private static Method m0;
private static Method m3;
// 通过自身的构造器,传入handler对象来进行创建对象
public $Proxy1(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 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);
}
}
// 实现了接口中的方法
public final String print(String var1) throws {
try {
// 调用代理类的时候,就是调用的自定义的handler对象里的invoke方法
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.example.mybatis.demomybatis.service.ProxyService").getMethod("print", Class.forName("java.lang.String"));
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到,生成的代理类实现了目标接口,因为接下里我们还要用代理类来执行方法,需要代理类的一个对象,可以看到,在代理类中,通过一个构造器来创建对象,这个构造器的参数就是Invocationhandler类型的对象。
所以这也就解释了我们为什么还要传一个自定义的handler,因为代理类创建对象是通过自身的构造器创建对象,构造器的参数就是我们自定义的handler对象。
也可以在上面代理类的方法里看到,当代理类调用接口里的方法时,实际上是调用的就是handler对象里的invoke方法,这也是为什么重写invoke方法的原因
以上第三个问题解决了,第一个问题也解决了(既不是ProxyService类型,又不是ProxyServiceImpl类型)是一个实现了ProxyService接口的$Proxy+数字类型。
第四个问题,可以看到,method.invoke()里面传的是proxyServiceImpl的对象,所以会直接执行其对象的方法,如果只有一行method.invoke(),那么和直接调用proxyServiceImpl对象.方法()没有任何区别,所以一般我们不像上面那样写,因为那样写毫无意义,我们要加一些与业务无关的公共的操作,正确姿势
public class CommonProxyHandler implements InvocationHandler {
private Object target;
public CommonProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
开启事务管理器
这里invoke的第一个参数为被代理对象,也就是target,如果用proxy,则变成了自己重复调用自己本身的invoke的方法
Object invoke = method.invoke(target, args);
提交事务/回滚事务
return invoke;
}
}
在业务操作前后,加一些所有业务都用到的操作,这就是类似于spring的AOP
因为实现了接口,所以根据Java的多态,向下转型,代理类的对象可以使用对应的接口类型来接收,代理类对象当然也能调用接口的方法。
第二个问题最后说。
接下来是第二种方式
2、基于接口实现(Interface)
public interface SchoolService {
String getString(String message);
}
public class ProxyHandler<T> implements InvocationHandler {
private Class<T> proxyInterface;
public ProxyHandler(Class<T> proxyInterface) {
this.proxyInterface = proxyInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
String s = null;
System.out.println(proxyInterface.getName());
if ("getString".equals(method.getName())) {
s = args[0].toString();
System.out.println(s);
}
return s;
}
}
可以看到,在这个handler里的没有method.invoke()方法,上面已经说过,如果要使用这个方法,就要传入接口的实现类的对象,可是现在除了代理类没有其他实现该接口的类 ,所以,这里就不能使用,如果使用会报错,因为接口是必须要有实现的。
那么在这个invoke方法里加了一些操作,这些操作可以简单的认为,被当做接口的实现类要做的事情。
使用
@SpringBootTest
public class NoImplProxyTest {
@Test
public static void main(String[] args) {
ProxyHandler proxyHandler = new ProxyHandler(SchoolService.class);
SchoolService proxyInstance = (SchoolService) Proxy
.newProxyInstance(SchoolService.class.getClassLoader(),
new Class[]{SchoolService.class}, proxyHandler);
proxyInstance.getString("测试接口没有实现类的代理");
}
}
还可以测试一下,传入接口,生成的代理类是什么样的
@SpringBootTest
public class GenreateProxyOnlyInterfaceTest {
@Test
public static void main(String[] args) throws Exception{
String path = "/Users/jacksparrow414/Downloads/$Proxy2.class";
ProxyHandler proxyHandler = new ProxyHandler(SchoolService.class);
SchoolService proxyInstance = (SchoolService) Proxy
.newProxyInstance(SchoolService.class.getClassLoader(),
new Class[]{SchoolService.class}, proxyHandler);
byte[] proxies = ProxyGenerator.generateProxyClass("$Proxy2",new Class[]{SchoolService.class});
FileOutputStream outputStream;
outputStream = new FileOutputStream(path);
outputStream.write(proxies);
outputStream.flush();
outputStream.close();
}
}
基于接口生成的代理类
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.example.mybatis.demomybatis.service.SchoolService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy2 extends Proxy implements SchoolService {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy2(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 String getString(String var1) throws {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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.example.mybatis.demomybatis.service.SchoolService").getMethod("getString", Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到,无论是传入接口、还是接口的实现类,生成的代理类都是一样的,都是实现了接口,具体的调用取决于自定义的handler里的操作
所以第二个问题就是都可以传,只不过,传接口的话,invoke方法里的操作就不能用method.invoke了,就要自己手写了。
还有一点很重要,自定义的handler的构造函数可以传多个,具体要根据实际场景来决定
那么基于接口实现类和基于接口的JDK动态代理使用场景有什么不同?
1、跟人觉得,基于接口实现类的是跟业务操作有很大的关系,是把除了业务操作之外的公共操作行为抽象出来,在业务操作前后进行,这样使得公共的操作代码就一份,却能在每一个业务操作上都起到作用(例如:spring的AOP,spring的事务注解,日志的记录)
利用method.invoke()执行实际业务操作方法
2、而基于接口的是不会和业务操作挂钩,抽象的是一套规范化的流程,典型的就是mybatis的mapperProxy、feign中的feignInvocationHandler这些经典的框架,它们都是为了解决一个流程而高度抽象出来的。
最后说明几点
1、由Java动态产生的代理类,一般名字为$Proxy数字,debug的时候可以轻易发现。实现InvocationHandler的接口的类,里面的invoke的方法中method.invoke()是真正执行ServiceImpl的地方,这个地方Java会自动去真正的实现该逻辑的实习类中的方法中执行,在该方法之前或者之后的操作,就是你想要添加额外操作的位置
2、method.invoke()中接收的参数类型,method.invoke(" 要调用的方法的名字所隶属的对象实体",方法的参数值);而不是接口,
千万记得,你传接口根本没用,并且会报错,因为接口是抽象的,你没办法让一个抽象的东西去具体化你的动作,除非你给一个该抽象化的实例。
3、method.invoke方法建议使用try-catch,因为Java有的时候会抛出异常,导致你调用失败,错误信息类似于下面这样
Method threw 'java.lang.reflect.UndeclaredThrowableException' exception. Cannot evaluate com.sun.proxy.$Proxy数字....
4、当你在method.invoke()方法之前和之后都没有加自定义的操作,你的Handler中invoke()方法中仅仅有一句method.invoke()方法,那么此时和你直接调用ServiceImpl(被代理类)没有任何区别。
可以看到,除了第一种远程代理特别像代理模式之外,其他的所谓的代理模式都不过是在执行真正的业务操作之前、之后进行其他额外操作罢了,但是前辈们也是把这种方式统一归档为代理模式。
一点点小感悟:Spring的AOP是真正把代理模式玩的炉火纯青的地步啊、AOP内核就是一个代理模式,由此衍生出的什么事务管理啊、异步操作啊,都是换汤不换药。
把复杂的东西简单化,在把简单东西用到极致!啥都不说了,NB就完事了。
下一篇说说Mybatis中利用JDK的代理方式是如何用的?