代理模式-AOP
1. 什么是代理模式
定义:为其他对象提供一种代理以控制对这个对象的访问
作用:为其他对象提供一种代理以控制对这个对象的访问。代理对象可以在客户端和目标对象(被代理对象)之间起到中介的作用
即在不改变源码的情况下,实现对被代理对象的功能扩展
抽象角色:(subject)通过接口或抽象类声明被代理对象实现的业务方法
角色具有的行为放入该接口或抽象类中
代理角色(Proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作
真实角色(RealSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用
2. 优点
- 优点:
- 可以屏蔽用户真正请求的对象,是用户程序和真实对象解耦。
- 使用代理来担当那些创建耗时的对象的替身。
- 代理对象可以在用户端和被代理角色之间起到中介的作用
- 代理对象中可以有更多的操作,且不改变被代理对象的源码
3. 静态代理
静态代理实现:目标类和代理类实现了相同的接口,在代理类中依赖了目标类,代理类的方法中调用了目标类的方法,并做了一些增强性的工作。
静态是指代理类在编译阶段生成,在程序运行之前就已经存在了
要求:
- 代理对象和真实对象都要实现同一个接口
- 代理对象要代理真实角色,所以需要传参【真实角色】------》通过构造函数完成传参
- 好处:代理对象可以做很多真实对象做不了的事情;真实对象专注做自己的事情
-
实例1------租房
- 租房接口
javapublic interface RentingAHouse { public void rentingAHouse(); }
- 房东
javapublic class Landlord implements RentingAHouse{ @Override public void rentingAHouse() { System.out.println("租房子"); } }
- 租房中介
javapublic class Intermediary implements RentingAHouse{ private RentingAHouse target; public Intermediary(RentingAHouse target){ this.target=target; } @Override public void rentingAHouse() { System.out.println("看房子"); target.rentingAHouse(); System.out.println("收租金"); } }
- 测试
- 调用方法时不使用目标类,而是使用代理类来进行调用,增强操作可以替换成其他代码或方法
javapublic class Test { public static void main(String[] args) { Intermediary intermediary=new Intermediary(new Landlord() ); intermediary.rentingAHouse(); } }
-
实例2-婚前公司
java
public class StaticProxy {
public static void main(String[] args) {
// You you=new You();
WeddingCompany wedding =new WeddingCompany(new You());
wedding.HappyMarry();
//上述两行等价于
new WeddingCompany(new You()).HappyMarry();
}
}
//1.创建一个接口Marry
interface Marry{
//HappyMarry方法
void HappyMarry();
}
//2. 创建真实角色,继承Marry接口
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("结婚快乐!!!!!");
}
}
//3.创建代理角色------婚庆公司,继承Marry接口
class WeddingCompany implements Marry{
//3.1 需要构造有参方法,以便传递真实角色的值
private Marry targer;
public WeddingCompany(Marry targer) {
this.targer = targer;
}
@Override
public void HappyMarry() {
//结婚前
before();
this.targer.HappyMarry();
after();
}
private void before() {
System.out.println("结婚之前布置场景");
}
private void after() {
System.out.println("结婚之后收尾款");
}
}
缺点:代理对象必须提前写出, 需要为每一个被代理的接口或类都编写一个代理类,工作量太大 。如果接口层发生了变化,代理对象的代码也要进行维护。
4. 动态代理
在运行期生成代理类
4.1 JDK代理
Proxy类:创建代理角色的方法。
InvocationHandler接口:提供的执行被代理角色方法的方法。
调用Proxy类的静态方法newProxyInstance即可,该方法会返回代理类对象。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
接收的三个参数依次为:
- ClassLoader loader:指定当前目标对象使用类加载器,写法固定
- Class<?>[] interfaces:目标对象实现的接口的类型,写法固定
- InvocationHandler h:提供的执行被代理角色方法的方法。
-
实例
- 接口
javapublic interface RentingAHouse { public void rentingAHouse(); }
- 被代理类
javapublic class Landlord implements RentingAHouse { @Override public void rentingAHouse() { System.out.println("租房子"); } }
- 代理类
javapublic class JdkProxy { //被代理的类必须实现接口 private RentingAHouse target; public JdkProxy(RentingAHouse target){ this.target=target; } //获取代理对象 public RentingAHouse getProxyObject(){ /* ClassLoader loader,被代理对象的类加载器 Class<?>[] interfaces,被代理对象实现的接口的反射类。被代理的方法 InvocationHandler h,回调函数 */ //获取被代理对象的类加载器 ClassLoader classLoader = target.getClass().getClassLoader(); //被代理对象必须实现了接口 Class<?>[] interfaces = target.getClass().getInterfaces(); InvocationHandler h=new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("看房子"); Object result= method.invoke(target); System.out.println("收房租"); return result; } }; //通过jdk代理类Proxy获取代理对象 Object o = Proxy.newProxyInstance(classLoader,interfaces,h); return (RentingAHouse) o; } }
- 测试
javapublic class Test { public static void main(String[] args) { JdkProxy jdkProxy=new JdkProxy(new Landlord()); RentingAHouse proxyObject = (RentingAHouse) jdkProxy.getProxyObject(); proxyObject.rentingAHouse(); } }
缺点:同静态代理一样,目标对象即被代理类必须实现一个或多个接口
4.2 Cglib代理
前提条件:
- 需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-3.2.5jar
- 目标类不能为final
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
原理: 通过字节码
技术为一个类创建子类
,并在子类中采用方法拦截
的技术拦截所有父类方法的调用
。由于是通过创建子类来代理父类
,因此不能代理被final
修饰的类(代理final
修饰的类会抛异常,代理final
修饰的方法只会原样执行委托类的方法而不能做任何拦截)。
-
实例
- 被代理的类------不能用final修饰,要作为父类
javapublic class Landlord { public void rentingAHouse() { System.out.println("租房子"); } }
- 代理类------子类
javapublic class ProxyFactory implements MethodInterceptor { //维护目标对象 private Object target; public ProxyFactory(Object target){ this.target=target; } //给目标对象创建一个代理对象,得到代理对象 public Object getProxyInstance(){ //工具类 Enhancer en=new Enhancer(); //设置父类------cglib en.setSuperclass(target.getClass());//设置被代理对象的父类 //设置回调函数 en.setCallback(this); //创建子类(代理对象) return en.create(); } //在执行任何目标方法前先执行拦截方法 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("看房子!"); //执行被代理类对象的方法 Object invoke = method.invoke(target); System.out.println("收房租!"); return invoke; } }
- 测试
javapublic class Main { public static void main(String[] args) { ProxyFactory proxyFactory=new ProxyFactory(new Landlord()); Landlord proxyInstance = (Landlord) proxyFactory.getProxyInstance(); proxyInstance.rentingAHouse(); } }
5. 总结
三种代理模式各有优缺点和相应的适用范围,主要看目标对象是否实现了接口。以Spring框架所选择的代理模式举例
在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理
-
静态代理:代理类在编译阶段生成,在程序运行之前就已经存在了
- 要实现接口
- 缺点:要为每个被代理的接口实现代理类,代码冗余
-
动态代理:在运行时生成代理类。
根据是否需要实现接口分为两种
-
JDK动态代理:需要被代理类实现接口。
- 通过调用Proxy代理类的newProxyInstance静态方法获得代理类对象
-
Cgilb动态代理:不需要被代理类实现接口
- 被代理类不能被final修饰,因为要作为父类
- 通过创建子类代理父类,在子类中采用方法拦截技术拦截所有父类方法的调用。
-