Java动态代理原来是这么回事?

代理模式定义

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用(摘自百度百科
程序来源于生活,生活中也有代理模式的例子。比如电影《我不是药神》中,程勇拿到了印度"格列宁"在中国的代理权,中国的病人要买印度"格列宁"只能通过程勇来买。那程勇就是代理对象,印度药厂就是真实对象,程勇在这个过程中就起到了中介的作用。

代理模式涉及的角色

抽象角色:一般是接口或抽象类,定义角色的功能
真实角色:抽象角色的具体实现
代理角色:抽象角色的具体实现,并且包含真实角色的引用,通过调用真实对象同名的方法来实现代理的作用,还可以加入附加的操作,增强真实角色的功能

代理模式的好处

1.职责清晰,真实的对象可以专注于业务的具体实现,一些附加的操作就由代理对象来完成,结构清晰,分工明确
2.代理对象可以在客户端跟目标对象之间起到中介的作用,从而起到保护目标对象引用的作用

静态代理

静态代理是由程序员创建或工具生成代理类的源码,再编译代理类
实现如下:

public interface PersonService {

  public void addPerson();

}

public class PersonServiceImpl implements PersonService {

  public void addPerson() {
    System.out.println("add person----");
  }
}

public class ProxyPersonServiceImpl implements PersonService {

  private PersonService personService;

  public ProxyPersonServiceImpl(PersonService personService){
    this.personService = personService;
  }

  public void addPerson() {
    System.out.println("新增之前添加额外操作 -------");
    personService.addPerson();
    System.out.println("新增之后添加额外操作   --------");
  }
}

静态代理的缺点:
代理对象只能服务于单一的对象,如果需要代理的对象比较多,会有很多个代理对象;比如,我要在很多个对象中加入日志记录,如果使用静态代理就要创建很多代理对象,造成代码冗余,不利于维护。此时,就需要动态代理了

动态代理

动态代理是在实现阶段不用关心代理类,而在运行阶段才指定代理的对象
一般动态代理有两种实现方式:JDK动态代理、CGLIB动态代理

JDK动态代理

实现方式:利用Java的反射机制,在java.lang.reflect 包下提供了Proxy类和InvocationHandler 接口来实现动态代理

代码实现

下面以增加日志的需求来演示一下JDK动态代理的用法

//抽象对象
public interface CustService {
  public void editCust();
}

//真实对象
public class CustServiceImpl implements CustService {
  public void editCust() {
    System.out.println("edit cust ----");
  }
}

//日志对象
public class LogService {
  public void addLog(){
    System.out.println("add log ----");
  }
}

//动态代理对象
public class JDKProxyCustService implements InvocationHandler {

  private Object target;
  private LogService logService = new LogService();

  public JDKProxyCustService(Object target){
    this.target = target;
  }

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    System.out.println("methodName:" + methodName );
    logService.addLog();
    Object o = method.invoke(target,args);
    logService.addLog();
    return o;
  }
}

//测试
public class TestJDKProxy {

  public static void main(String[] args){
    CustService custService = new CustServiceImpl();
    Class c = custService.getClass();
    ClassLoader classLoader = c.getClassLoader();//目标对象的类加载器
    Class[] interfaces = c.getInterfaces();//目标对象实现的所有接口
    InvocationHandler h = new JDKProxyCustService(custService);//获取一个InvocationHandler,并将custService对象传入

    /**
     * 参数说明:
     * 1.classLoader表示目标对象的类加载器
     * 2.interfaces表示目标对象实现的所有接口
     * 3.InvocationHandler接口的实现
     */
    CustService proxy = (CustService) Proxy.newProxyInstance(classLoader,interfaces,h);
    proxy.editCust();
  }
}

运行结果:
methodName:editCust
add log ----
edit cust ----
add log ----

每个动态代理类(JDKProxyCustService)都必须实现InvocationHandler接口,当我们通过代理对象调用方法时,调用会被转到InvocationHandler接口的invoke方法。

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

proxy:被代理的真实对象
method:调用真实对象的某个方法
args:调用真实对象某个方法所传的参数

实际上,真正有用的代码是:

Object o = method.invoke(target,args);

利用Java的反射机制,动态的去调用目标对象的方法。
再来看看Proxy类,我们在测试类中真正有用的代码是:

CustService proxy = (CustService) Proxy.newProxyInstance(classLoader,interfaces,h);

Proxy类的作用就是创建一个动态代理对象的类,newProxyInstance方法的作用是创建一个动态代理的对象
参数说明:
classLoader:目标对象的类加载器
interfaces:目标对象所实现的所有接口
h:InvocationHandler接口的实现

还有这句:

InvocationHandler h = new JDKProxyCustService(custService);

传入需要代理的真实对象,实现调用的就是真实对象的方法

CGLIB动态代理

JDK动态代理要求被代理的类必须实现接口,并且代理对象与真实对象必须实现同一接口,CGLIB则没有这样的限制,简单来说CGLIB动态代理的原理是,生成的代理类继续自被代理类,在代理类中增加额外的逻辑实现功能增强,但由于代理类要继承自被代理类,final类是无法由CGLIB动态代理的

//被代理类
public class HelloWorldService {
  public void hello(){
    System.out.println("Hello World!");
  }
}

//代理类
public class HelloWorldInterceptor implements MethodInterceptor {
  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
      throws Throwable {
    System.out.println(method.getName() + "调用之前");
    Object object = methodProxy.invokeSuper(o,objects);
    System.out.println(method.getName() + "调用之后");
    return object;
  }
}

//测试类
public class HelloWorldClient {
  public static void main(String[] args){
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(HelloWorldService.class);//继承被代理类
    enhancer.setCallback(new HelloWorldInterceptor());//回调
    HelloWorldService helloWorldService = (HelloWorldService) enhancer.create();//创建代理类对象
    helloWorldService.hello();
  }
}

运行结果:
hello调用之前
Hello World!
hello调用之后

从逻辑上来看,CGLIB实现动态代理更加简单
要实现CGLIB动态代理,首先要实现MethodInterceptor 接口,然后重写intercept方法
代理类对象是由Enhancer类创建的,Enhancer是CGLIB的字节码增强器,可以很方便的对类进行拓展

JDK动态代理与CGLIB动态代理的对比

1.JDK动态代理,代理类与真实类实现同一接口,通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中实现额外的处理。缺点:只能够代理实现了接口的类
2.CGLIB动态代理,生成的代理类继承被代理类,通过实现MethodInterceptor接口并重写intercept方法来实现额外的处理。
缺点:因为要继承,final类以及final方法无法使用CGLIB动态代理。

评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值