1.代理模式的作用
能通过代理对象间接实现对目标对象的访问,在不改变源代码的情况下对目标对象的方法进行增强。
什么是通过代理对象间接实现对目标对象的访问?
举个生活中的例子:例如你买车是通过4s店(代理对象),而不是直接去车工厂(目标对象),你只需访问4s店。
那什么不改变源代码的情况下对目标对象的方法进行增强?
还是4s店和车工厂的例子:
你去4s店买车,4s店除了卖车给你,它还有一系列的服务,例如:推荐保险,上车牌等等;
但是你去工厂就只能买车,没有其他的附加服务;也就是说4s店(代理对象)在车工厂(目标对象)的卖车基础上进行了一系列增强,但是车厂还是一样的卖车,并没有被改变。
2.代理模式的分类
代理模式分为静态代理和动态代理。
2.1静态代理
2.1.1静态代理的实现
实现步骤
1.代理类与目标类实现同一接口
2.代理对象有目标对象的引用,在同名方法中调用目标对象的方法,并在前后根据需求进行增强。
实现代码
接口代码
public interface AInterface {
void say();
}
目标类代码
public class AInterfaceImpl implements AInterface{
@Override
public void rap() {
System.out.println("rap");
}
@Override
public void basketball() {
System.out.println("篮球");
}
}
代理类代码
public class AStaticProxy implements AInterface{
//目标对象
private AInterface aInterface;
//注入目标对象
public AStaticProxy(AInterface aInterface) {
this.aInterface = aInterface;
}
@Override
public void rap() {
System.out.println("前置增强");
aInterface.rap();
System.out.println("后置增强");
}
@Override
public void basketball() {
System.out.println("前置增强");
aInterface.basketball();
System.out.println("后置增强");
}
}
2.2.1静态代理的缺点
1.只能代理某一类接口
2.若进行多次增强,可能代理类暴增
3.修改接口,要同时维护目标类和代理类
4.若是代理类的增强逻辑一致,可能造成大量重复代码
5.增强代码和目标方法硬编码
2.2动态代理
针对静态代理的缺点,我们可能观察到,许多静态代理的缺点都来自于代理类;
如果我们将代理类干掉,就能解决静态代理的大部分缺点。
可是在静态代理中我们通过代理类的构造方法才能得到代理对象,若是没有代理类,怎么创建代理对象呢?
我们先观察一下到底静态代理是如何通过代理类得到代理对象的,这就涉及到java代码的运行原理了。
编译 类加载 反射得到构造方法
代理类-------> 代理类.class----------> JVM中的Class------------------------>创建得到代理对象
那么动态代理没有代理类,只能通过接口.class,很明显会在得到构造方法这里卡住,因为接口没有构造方法。
那么有没有办法能够既有接口的方法信息又有构造方法呢?
答案是:通过Proxy类的getProxyClass(classLoader,Interface)方法,我们只需传入将接口的信息作为参数传入,就能得到一个既有接口信息又有构造方法的Class对象,通过该Class得到构造方法,我们就能创建代理对象了。
那为什么Proxy类的getProxyClass方法那么神奇?
这里尚且不做深入了解,你可以理解为它就是 想办法把接口Class里面的方法信息拼接一个构造方法得到新的Class。
2.2.1动态代理的实现
实现步骤
1.编写一个工具类,里面提供一个静态方法getProxy(Object target,InvocationHandler i)
2.在getProxy()方法中,通过Proxy类的静态方法getProxyClass(classLoader,Interface),传入目标对象接口的参数,然后得到新的代理Class对象
3.通过代理Class对象调用getDeclaredConstructor(InvocationHandler.class)方法,传入Invocationhandler的Class对象得到构造方法对象
4.通过构造方法对象的newInstance(InvocationHandler)传入Invocationhandler对象创建得到代理对象
实现代码
接口代码
public interface AInterface {
void say();
}
目标类代码
public class AInterfaceImpl implements AInterface{
@Override
public void rap() {
System.out.println("rap");
}
@Override
public void basketball() {
System.out.println("篮球");
}
}
代理工具类的代码
public class MyProxy {
/**
* 获取代理对象
* @param target 目标对象
* @param invocationHandler 代理处理器
* @return
* @throws Exception
*/
public static Object getProxy(Object target, InvocationHandler invocationHandler) throws Exception{
//获得目标对象的Class对象
Class clazz = target.getClass();
//根据目标对象的Class对象得到代理对象的Class
Class proxyClass = Proxy.getProxyClass(clazz.getClassLoader(), clazz.getInterfaces());
//根据代理对象Class获得代理对象的构造器
Constructor declaredConstructor = proxyClass.getDeclaredConstructor(InvocationHandler.class);
//根据代理对象的构造器创建代理对象
Object o = declaredConstructor.newInstance(invocationHandler);
//返回得到的代理对象
return o;
}
}
得到的代理对象的结构是怎么样的?
代理对象大致结构:
public class MProxy implements AInterface{
private InvocationHandler ih;
public MProxy(InvocationHandler ih) {
this.ih = ih;
}
@Override
public void rap() {
ih.invoke();
}
@Override
public void basketball() {
ih.invoke();
}
}
2.2.2动态代理的缺点
只剩下增强代码和目标方法硬编码
静态代理的缺点有:
1.只能代理某一类接口
2.若进行多次增强,可能代理类暴增
3.修改接口,要同时维护目标类和代理类
4.若是代理类的增强逻辑一致,可能造成大量重复代码
5.增强代码和目标方法硬编码
动态代理如何优化的
对于静态代理的缺点1,动态代理只传入目标对象,目标对象可以是任意接口的,所以解决了
对于静态代理的缺点2、3、4,动态代理没有代理类,也解决了
但是对于静态代理的缺点5增强代码和目标方法硬编码,在动态代理的invoke方法
中还是没能避免,这个缺点在后来的aop(面向切面编程才得以解决),它将方法的逻辑进行拆分,无侵入式的对方法进行增强。
2.2.3动态代理的拓展
本文章的动态代理是JDK根据接口实现的,此外还有第三方的动态代理,如cglib,它实现的原理同JDK不同,它是基于继承来实现的,子类继承父类重写父类方法实现的增强