27、代理和动态代理

------- <a href="http://www.itheima.com" target="blank">android培训</a>、<a href="http://www.itheima.com" target="blank">java培训</a>、期待与您交流! ----------


代理和动态代理

动态代理的概念: JVM可以在运行期动态生成类的字节码,这种动态生成往往被用作代理类,即为动态代理.
      jvm生成的动态类必须实现一个或多个接口,所以,jvm生成的动态类只能用作具有相同接口的目标类的代理
     CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理
应用场合:有多个已经开发完成的类,他们具有相同的接口,现在要为这些类增加一些其他的功能又不能改变这些类,这时候我们只能编写一个与这些目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并且在调用时增加上需要的功能代码,这就是代理。
动态代理机制: 其实动态代理机制最核心的就是InvocationHandler(调用处理器)这个接口InvocationHandler 是代理实例的调用处理程序 实现的接口。
代理对象:代理类所生成的对象。
目标对象:代理类所代理的那个类生成的对象。
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
静态代理的缺点:
(1)如果接口加一个方法,代理类和目标类都要做个实现,这就增加了代码的复杂度。
(2)要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情
动态代理:
(1) JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
(2) JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
(3) CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
(4) 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke 方法。
面向方面的编程就使用代理
代理的作用:
  要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能
  代理类与目标类具有相同的接口,代理类的每个方法调用与目标类相同的方法,并且在调用方法时加上系统功能的代码。使用接口引用
  如果采用工厂模式和配置文件的方式进行管理,不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类

创建动态类的实例对象
 // 得到的是一个类的实例对象,就是代理类
  Class clazz = Proxy.getProxyClass(Collection.class.getClassLoader
    Collection.class);
 // 调用动态代理类的方法
MyInvocationHandler mi = new MyInvocationHandler();
  Collection cc = (Collection) clazz.getConstructor(
    InvocationHandler.class).newInstance(mi);
客户端程序调用proxy的方法时设计三个要素,也就是invocationHandler接受的三个参数 hnder.invoke(Object proxy, Method method, Object[] args)分别是 调用代理对象,调用代理对象的那个方法,传递的什么参数
代理类中只有hashcode equals tostring三个方法交个了handler去实现,对于object的其他方法不交给handler去开发,有自己的实现
面向切面编程 把切面的代码用对象的方式进行封装,然后以对象的方式进行传递。执行对象就等于执行了切面的代码


JAVA 自带的动态代理是基于java.lang.reflect.Proxy、java.lang.reflect.InvocationHandler两个类来完成的,使用JAVA 反射机制。
(1) Proxy.getProxyClass(ClassLoadercl,Class<?>…interface),动态的生成一个类的字节码,由于字节码都有getClassLoader()方法,而且都是由类加载器加载到内存的,所以必须为其指定classLoader(通常为接口参数的类加载器),而且必须要指定它所代理的类所实现或者继承的接口,即要代理的目标对象的归属接口数组,从这份动态生成的字节码中,我们可以得到一个带有InvasionHandler类型的参数的构造函数,可以用该构造函数创建一个代理对象。
以下代码创建一个代理类,去代理那些实现了Collection接口的类
//动态生成代理类的字节码
Class clazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
 
/*从字节码的得到代理类的构造方法(含有一个InvocationHandler参数)
* 用该构造方法创建对象,必须要有一个InvocationHandler
 * InvocatinHandler是一个接口,不能直接创建对象,只能创建其子类对象,并实现其中的抽象方法
*/
Collection proxy = (Collection) clazz.getConstructor(InvocationHandler.class).newInstance(new InvocationHandler(){
  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
   return method.invoke(目标对象,args);
   }});
以下代码也能起到同样的效果:
Collection proxy2 = (Collection)Proxy.newProxyInstance(
    Collection.class.getClassLoader(),
    new Class[]{Collection.class},
    new InvocationHandler(){
     @Override
     public Object invoke(Object proxy, Method method,
       Object[] args) throws Throwable {
      return method.invoke(目标对象,args);
     }
    }
    );
第二种方式更加直接简便,并且隐藏了代理$Proxy0 对象的结构。
JDK 的动态代理会动态的创建一个$Proxy0的类,这个类继承了Proxy并且实现了要代理的
目标对象的接口,但你不要试图在JDK 中查找这个类,因为它是动态生成的。
总结思考:让jvm创建动态类及其实例对象,需要给它提供三个方面信息:
(1)生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
(2)产生的类字节码必须有个一个关联的类加载器对象;
(3)生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
动态代理类的内部实现:
$Proxy0 implements interfaceX
{
 InvocationHandler handler;
 public $Proxy0(InvocationHandler handler)
 {
  this.handler = handler;
 }
 //生成的Collection接口中的方法的运行原理
 int methodX()
 {
  return handler.invoke(this, this.getClass().getMethod("methodX"),null);
 }
 void methodY (){
  handler.invoke(this, this.getClass().getMethod("methodY"),null);
 }
 boolean methodZ(Object obj){
  return handler.invoke(this, this.getClass().getMethod("methodZ "),obj);
 }
}
InvocationHandler的作用:InvocationHandler起到一个中介的作用,当客户使用代理对象调用某一个方法时,代理对象会把他转交给InvocationHandler对象,让其代理,InvocationHandler对象会调用其invoke()方法,invoke方法再去调用目标对象的相同方法,以及实现附加功能的模块,这就完成了代理。这里需要说一下invoke()方法的三个参数:
Object obj 代理对象,也就是指出哪个对象调用了InvocationHandler对象的invoke方法
Method method 方法,指出调用了哪个方法
Object[]args 接收method方法的参数
明白了InvocationHandler的作用也就明白了动态代理的工作原理。
动态代理的小例子:
代理接口:
代理接口的实现类
class Eat implements Action
  {
   @Override
   public void doSomething(){
    System.out.println( "eat!!!");
   }
  }
回调类
class myInvocationHandler implements InvocationHandler
   {
      private Object target;
      public myInvocationHandler(Object target)
      {
         this.target = target;
      }
      @Override
      public Object invoke(Object proxy, Method method, Object[]args)
            throws Throwable {
         System.out.println("BeforeEat");
         Object retVal = method.invoke(target, args);
         System.out.println("AfterEat");
         return retVal;
      }
   }
测试代码:
Eat eatObj = new Eat();
  Object proxy = Proxy.newProxyInstance(
    Action.class.getClassLoader(),
    /*这里必须用Eat.class.getInterfaces(),而不能使用Action.class.getInterfaces()
     * 因为getInterfaces()方法返回的是当前类所实现的接口
     */
    Eat.class.getInterfaces(),
    new myInvocationHandler(eatObj)
    );
  /*
   * 只能强制转换为Action,而不能强制转换为Eat
   * 也就是说代理对象不能强制转换为目标对象的类型
   * 因为两者之间没有继承或者实现关系
   * 而代理对象却实现了代理接口,所以能强制转换为Action
   */
  ((Action)proxy).doSomething();

 实现类似Spring的可配置的AOP框架
(1) 工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据字符串参数(从配置文件读取得到)所指定的类名返回一个相应实例对象,如果字符串 参数对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,如果是ProxyFactoryBean,则通过ProxyFactoryBean的getProxy方法返回的配置文件所指定的目标类的代理对象。
实现:a:从输入流中得到配置文件信息,并根据配置文件中的类名建立一个实例
      B:如果该实例不是ProxyFactoryBean类的实例对象,返回该实例对象,如果是,则调用ProxyFactoryBean类中的getProxy()方法得到一个代理对象并返回。
(2) BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
    #xxx=java.util.ArrayList
    xxx=AOPFramwork.ProxyFactoryBean
    xxx.target=java.util.ArrayList
    xxx.advice=AOPFramwork.MyAdvice
(3)ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?
  (a)目标类,即指定代理类为哪个类代理。
  (b)通告,一个接口,其中的抽象方法规定了代理类相对于目标类所添加的那些附加功能的规则,实现它的子类必须实现它的抽象方法,即按照这些抽象方法的约定去实现附加功能,使得这些附加功能即使是交叉业务,也能够实现模块化。
实现:1、作为一个JavaBean,它必须有一个无参的构造方法
2、 getProxy()方法得到代理类的实例对象时,必须加载代理类,在加载代理类时,必须为其制定目标类和通告,这两部分将作为ProxyFacotryBean的私有变量,传递到类中,以便于getProxy方法使用。
(4)编写客户端应用:
编写实现Advice接口的类和在配置文件中进行配置
调用BeanFactory获取对象
该框架应该有的类:
(1) BeanFactory充当创建目标类或代理类的实例对象的工厂
(2) ProxyFacotryBean充当生成动态代理的工厂
(3) Advice充当制定附加功能的添加规则,MyAdvice作为其实现类,按照其规则(定义的抽象方法)实现所需添加的系统功能。
具体实现:
1、 工厂类BeanFactory

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
 
 private Properties properties = new Properties();
 public BeanFactory(InputStream is)
 {
   try {
   properties.load(is);
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
 public Object getBean(String key)
 {
  Object bean = null;
   try {
   Class clazz = Class.forName((String)properties.get(key));
    bean = clazz.newInstance();
  } catch (Exception e) {
   e.printStackTrace();
  }
   Object target= null;
   MyAdvice advice = null;
   if(bean instanceof ProxyFactoryBean)
   {
    ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
   
   try {
    advice = (MyAdvice)Class.forName((String)properties.get(key+".advice")).newInstance();
    target = Class.forName((String)properties.get(key+".target")).newInstance();
   } catch (Exception e) {
    e.printStackTrace();
   }
   
    proxyFactoryBean.setAdvice(advice);
    proxyFactoryBean.setTarget(target);
    return proxyFactoryBean.getProxy();
   }
   return bean;
 }
}
2、 动态代理类的工厂类ProxyFacotryBean

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactoryBean {
 private Object target;
 private MyAdvice advice;
 public void setTarget(Object target) {
  this.target = target;
 }
 public void setAdvice(MyAdvice advice) {
  this.advice = advice;
 }
 public ProxyFactoryBean(){
 
 }
 public Object getProxy() {
  Object proxy = Proxy.newProxyInstance(
    //为代理类指定加载器
    target.getClass().getClassLoader(),
    //目标类实现的接口
    target.getClass().getInterfaces(),
    new InvocationHandler(){
     public Object invoke(Object proxy, Method method,
       Object[] args) throws Throwable {
      advice.beforeMethod();
      Object retVal = method.invoke(target, args);
      advice.afterMethod();
      return retVal;
     }
     
    });
  return proxy;
 }
}
3、 通告Advice

public interface Advice {
 void beforeMethod();
 void afterMethod();
}
//通告的实现类
package AOPFramwork;
public class MyAdvice implements Advice {
 @Override
 public void beforeMethod() {
  System.out.println("before method!");
 }
 @Override
 public void afterMethod() {
  System.out.println("after method!");
 }
}
4、 客户端测试类

 
import java.io.InputStream;
import java.util.Collection;
public class AOPFramworkTest {
    public static void main(String[] args) throws Exception {
        InputStream is = AOPFramworkTest.class.getResourceAsStream("config.properties");
        BeanFactory beanFactory = new BeanFactory(is);
        Collection bean = (Collection)beanFactory.getBean("xxx");
        System.out.println(bean.getClass().getName());
        bean.add("afa");
    }
 
}  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值