静态代理与动态代理回顾
对于学习spring框架,想要很好的理解AOP的话,必定是要先理解java静态代理和动态代理原理的。
- 在23大设计模式中,代理模式(Proxy Pattern)是比较常用的一种,一句话就可以概括起来:一个类代表另一个类的功能。我们创建具有现有对象的对象,以便向外界提供功能接口,为其他对象提供一种代理以控制对这个对象的访问。用于解决当直接去访问某个对象时,可能会对那个对象带来问题,从而影响整体的功能。例如:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层(即一个代理)。
静态代理
静态代理是代理模式中的一种实现,也是较为简单的一种实现方法:要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。
我们用最常用的一个案例来解释:租客与房主的案例。
假设一个客户要找房子住,而一个房主正好有房要出租,一个有需求,一个有供应,但是这两类人想要交集到一起不一定容易。这时为了方便租客和房主,房屋出租中介出现了,房主找中介来帮忙出租房屋,中介收集了所有的房子信息,租客找中介帮忙租房子,最后考中介这一代理人完成了这笔交易。
分析:在案例中,中介充当房主的代理,就是掌握了房主的所有房屋信息,他们两同时拥有出租房屋的功能(rent),即共用一套方法接口。房主是真实对象,而中介是代理对象。
代码演示:
Interface:
public interface Rent {
/**
* 房屋出售功能
*/
public void rent();
}
Class Homeowners:
public class Homeowners implements Rent {
@Override
public void rent() {
System.out.println("北京xxx地址有一套房屋出租");
}
}
Class HouseProxy:
public class HouseProxy implements Rent {
private Rent rentHouse = null;
public HouseProxy(Rent rentHouse) {
//传入真实对象,用于代理
this.rentHouse = rentHouse;
}
@Override
public void rent() {
rentHouse.rent();
}
}
Class Client:
public class Client {
public static void main(String[] args) {
//客户不需要知道房主,他只需要找中介就可以知道房子的信息了
HouseProxy house = new HouseProxy(new Homeowners());
house.rent();
}
}
结果:
静态代理好处:
1. 可以使得真实角色处理的业务更加纯粹不再关注一些公共的事情
2. 公共的业务由代理来完成,实现业务的分工
3. 公共业务发生拓展时变得更加集中和方便
可以通过代理类给真实类增加功能,比如代理在每次出售房子时,都可以进行日志登记。
public class HouseProxy implements Rent {
private Rent rentHouse = null;
public HouseProxy(Rent rentHouse) {
//传入真实对象,用于代理
this.rentHouse = rentHouse;
}
@Override
public void rent() {
log();
rentHouse.rent();
}
/**
* 增强方法
*/
public void log(){
System.out.println("出售房屋日志登记");
}
}
静态代理的缺点:
-
代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
-
一个代理对象只服务于一种类型的对象,如果要服务多类型的对象。就要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为Homeowners类的访问提供了代理,但是如果中介还要代理门面,就要为其他类如Shop类提供代理,就需要我们为Shop类添加代理ShopProxy。
静态代理的缺点在小程序难以体现,虽然大量代理类实现起来不困难却较为麻烦,工作效率低下。所以就出现了动态代理,只继承了静态代理的优点,抛弃了缺点。
动态代理
从静态代理我们可以了解到在程序中为什么要产生一个对象的代理对象,
- 主要用于拦截对真实业务对象的访问。
- 代理对象应该具有和目标对象相同的方法。
动态代理顾名思义就是代理类是动态的生成,在java中JDK 自带的动态代理
- java.lang.reflect.Proxy : 生成动态代理类和对象;
- java.lang.reflect.InvocationHandler(处理器接口):可以通过invoke方法实现对真实角色的代理访问。这个是真正在干活的类,可以随着自己的想法在调用前增加一些日志代码等。
每次通过 Proxy 生成的代理类对象都要指定对应的处理器对象。
由此可以看出动态代理是通过java的反射机制来完成的,使用动态代理的五大步骤:
- 通过实现InvocationHandler接口来自定义自己的InvocationHandler(主要是编写invoke方法
- 通过Proxy.getProxyClass获得动态代理类
- 通过反射机制获得代理类的构造方法,方法名getConstructor(InvocationHandler.class)
- 通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入
- 通过代理对象调用目标方法
下面通过上面的案例来完成一个动态代理的编写。
需要实现的接口RentDynamic :
public interface RentDynamic {
/**
* 房屋出售功能
*/
public void rent();
}
真实对象HomeownersDynamic :
public class HomeownersDynamic implements RentDynamic {
@Override
public void rent() {
System.out.println("北京xxx地址有一套房屋出租");
}
}
动态代理类的实现 HouseDynamicProxy :
代理类的工作就是把实体类接过来,然后调用它的方法,也就是说本来实体类可以自己执行的方法现在由代理类来触发执行,这样做的好处是,在调用实体类方法的前后我们可以插入监控方法。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class HouseDynamicProxy {
public RentDynamic getHouseProxy(RentDynamic rd){
return (RentDynamic)Proxy.newProxyInstance(HouseDynamicProxy.class.getClassLoader(),
HomeownersDynamic.class.getInterfaces(),
new InvocationHandler(){
@Override
public Object invoke(Object proxy,
Method method, Object[] args) throws Throwable {
log();
//去调用真实对象的方法
Object invoke = method.invoke(rd,args);
after();
return invoke;
}
});
}
/**
* 增强方法
*/
public void log(){
System.out.println("出售房屋日志登记");
}
/**
* 提交事务/回滚
*/
public void after(){
System.out.println("提交事务/回滚");
}
}
* public static Object newProxyInstance(ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h)
* loader是选用的类加载器,在这里HouseDynamicProxy就是代理类,所以加载它的类加载器。
* interfaces 被代理的类所实现的接口,这个接口可以是多个。这里是HomeownersDynamic
* h 绑定代理类的一个方法。
*
*
* InvocationHandler作用就是,当代理对象的原本方法被调用的时候,
* 会绑定执行一个方法,这个方法就是InvocationHandler里面定义的内容,同时会替代原本方法的结果返回。
* InvocationHandler接收三个参数
* proxy 代理后的实例对象。
* method 对象被调用方法。
* args 调用时的参数。
客户类Client 调用测试:
public class Client {
public static void main(String[] args) {
HouseDynamicProxy hdp = new HouseDynamicProxy();
HomeownersDynamic hd = new HomeownersDynamic();
RentDynamic proxy = hdp.getHouseProxy(hd);
proxy.rent();
}
}
结果:
我们分析下为什么是这样的结果,因为proxy是代理类的对象,当该对象方法被调用的时候,会触发InvocationHandler,即proxy.rent();会先去触发new InvocationHandler()里的方法,把proxy.rent();的调用变成
去调用
log();
Object invoke = method.invoke(rd,args);
after();
可能有人会觉得和静态代理一比较没什么特别的,为了体现动态代理的优势,我们来把上面说的Shop类来实现一下。
Class ShopImpl:
public class ShopImpl implements RentDynamic {
@Override
public void rent() {
System.out.println("北京xxx地方有一个商铺出租");
}
}
Class Client:
public class Client {
public static void main(String[] args) {
HouseDynamicProxy hdp = new HouseDynamicProxy();
HomeownersDynamic hd = new HomeownersDynamic();
RentDynamic proxy = hdp.getHouseProxy(hd);
proxy.rent();
System.out.println("\n");
ShopImpl shop = new ShopImpl();
RentDynamic proxy1 = hdp.getHouseProxy(shop);
proxy1.rent();
}
}
结果:
由这个案例我们可以感受到动态代理带来的方便性,无需自己再多实现一个代理类。
动态代理的使用场景
动态代理的好处我们从例子就能看出来,它比较灵活,可以在运行的时候才切入改变类的方法,而不需要预先定义它。
动态代理一般我们比较少去手写,但我们用得其实非常多。在Spring项目中用的注解,例如依赖注入的@Bean、@Autowired,事务注解@Transactional等都有用到,这就是Srping的AOP(切面编程)。
这种场景的使用是动态代理最佳的落地点,可以非常灵活地在某个类,某个方法,某个代码点上切入我们想要的内容,就是动态代理其中的内容。
–看得开心的话麻烦点个关注^.^
–