JAVA提高第八篇 动态代理技术

对于动态代理,学过AOP的应该都不会陌生,因为代理是实现AOP功能的核心和关键技术。那么今天我们将开始动态代理的学习:

一、引出动态代理

生活中代理应该是很常见的,比如你可以通过代理商去买电脑,也可以直接找厂商买电脑,最终都是买到了电脑。程序中也一样存在代理的情况,比如要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如:异常处理、日志、计算方法耗时等等,那么我们会怎么做呢?

1.会编写一个与目标类拥有相同接口的代理类,代理类的每个方法调用目标类的相同方法,然后在调用方法前后加上系统功能所需要的代码。

2.如果采用工厂模式或者配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换。

样例如下:

public class X{
 public void sayHello(){
 syso:say hello;
 }
}

现在我要在这个方法之前添加一个时间,方法之后添加一个时间,计算这个方法执行的时间一共是多少.如果我没有得到sayHello源码,那么我怎么做呢?写一个代理:

public class XProxy
{
 private X x;
 public void sayHello
 {
  startTime:
  x.syHello();
  endTime;
 }
}

说明:上面的是伪代码。

把开始时间和结束时间放在这个方法的前后就可以了.
通常我们让两个方法实现同一个接口.那么client想用X也可以,想用XProxy也可以了.具体的原理图,如下图所示:

二、创建动态代理类

现在试想一下,上面只是代理了一个目标类,如果多个目标类,那么是不是要创建N多个代理类呢?那样不是代码太不灵活且笨重了。当然不会。

java虚拟机可以在运行期间动态生成类,这种类是以字节码的形式生成出来的。这种动态生成的类往往呢就是代理类。即动态代理类。

JVM生成的动态代理类必须满足一定的条件,这就是必须实现一个或多个接口。所以JVM生成的动态代理只能用作具有相同接口的目标类的代理。(动态生成的类不是代理,我们只是吧这个类当成代理来用。)

Proxy动态代理的API:

两个参数应该很容易理解:

第一个参数:我们知道任何一个字节码都是需要通过类加载器来加载的,那么这个动态生成的字节码也不例外,需要给它一个类加载器。

第二个参数:就是动态代理类生成,必须满足的一个条件,需要实现一个或者多个接口,否则这个生成的类字节码中就没有方法了,没有方法就失去了其功能意义。

下面我们动手来创建一个动态的代理类,大体思路为:

1.创建实现Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数

2.编码列出动态类中的所有构造方法和参数签名

3.编码列出动态类中的所有方法和参数签名

4.创建动态类的实例对象:1)用反射获取构造方法   2)编写一个最简单的invocationHandle类   3)调用构造方法创建动态类的实例对象,并将编写的InvocationHandle类的实例对象传递进去   4)打印创建对象和调用对象的没有返回的方法和getClass方法,演示调用其他有返回值方法报告了异常。

5)将创建动态类的实例对象的代理写成匿名内部类方式,简化代码。

样例分步实现如下:

(1)首先我们来完成前面3步:

package study.javaenhance;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;

public class ProxyTest
{
 public static void main(String[] args)
 {
  Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
  System.out.println(clazzProxy1.getName());

  //上面输出的为一个类,那么一个类肯定有其构造方法和方法,下面我们来列出来

  System.out.println("----------begin constructors list----------");
  //1.列出构造方法
  Constructor[] constructors = clazzProxy1.getConstructors();
  for (Constructor constructor : constructors)
  {
   String name = constructor.getName();
   StringBuilder sBuilder = new StringBuilder(name);
   sBuilder.append('(');
   Class[] clazzParams = constructor.getParameterTypes();
   for (Class clazzParam : clazzParams) {
    sBuilder.append(clazzParam.getName()).append(',');
   }
   if(clazzParams!=null && clazzParams.length != 0)
    sBuilder.deleteCharAt(sBuilder.length()-1);
   sBuilder.append(')');
   System.out.println(sBuilder.toString());
  }

  //2.列出这个类字节码中的所有方法
  System.out.println("----------begin methods list----------");
  Method[] methods = clazzProxy1.getMethods();
  for(Method method : methods){
   String name = method.getName();
   StringBuilder sBuilder = new StringBuilder(name);
   sBuilder.append('(');
   Class[] clazzParams = method.getParameterTypes();
   for(Class clazzParam : clazzParams){
    sBuilder.append(clazzParam.getName()).append(',');
   }
   if(clazzParams!=null && clazzParams.length != 0)
    sBuilder.deleteCharAt(sBuilder.length()-1);
   sBuilder.append(')');
   System.out.println(sBuilder.toString());
  }

 }

}

输出结果如下:

$Proxy0
----------begin constructors list----------
$Proxy0(java.lang.reflect.InvocationHandler)
----------begin methods list----------
add(java.lang.Object)
hashCode()
clear()
equals(java.lang.Object)
toString()
contains(java.lang.Object)
isEmpty()
addAll(java.util.Collection)
iterator()
size()
toArray([Ljava.lang.Object;)
toArray()
remove(java.lang.Object)
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait()
wait(long,int)
wait(long)
getClass()
notify()
notifyAll()

可以看到所有的方法均是来自Collection 和父类Object中的方法,符合我们的预期结果。接下来我们进入第四步和第五步的实现:

首先,我们来创建这个动态代理类的实例。那么直接clazzProxy1.newInstance();可不可以呢?显然是不可以的嘛.我们刚刚说了,动态生成的这个代理类只有一个构造方法,有没有无参构造方法呢?没有啊.所以,创建 一个参数的构造方法.参数类型是java.lang.reflect.InvocationHandler。

下面我们来实现:

1.定义一个上述接口的实例:

package study.javaenhance;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

 @Override
 public Object invoke(Object proxy, Method method, Object[] args)
   throws Throwable {
  // TODO Auto-generated method stub
  return null;
 }

}

2.调用实现创建实例动态类:

//3.创建实例对象
  System.out.println("----------begin create instance object----------");
  Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
  MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
  Collection collection = (Collection) constructor.newInstance(myInvocationHandler);
  System.out.println(collection);
  collection.clear();
  //collection.size(); //报错,异常会发生,产生异常的原因在于其返回值为int类型,但是每一次调用一个方法都会调用到invoke方法,我们此时的invoke返回的为null,所以是没有办法转换为int类型的。

3.我们采用匿名内部类的方式优化:

//3.1 采用匿名内部类方式进行创建
  Collection collection2 = (Collection) constructor.newInstance(new InvocationHandler()
  {

   @Override
   public Object invoke(Object proxy, Method method, Object[] args)
     throws Throwable {
    // TODO Auto-generated method stub
    return null;
   }

  });
  System.out.println(collection2);
  collection2.clear();
  //collection2.size();

4.继续优化:思考如果我们每次都想上面方式去创建动态的代理类实在有点重复,那么这个是JDK的Proxy类中提供了简单的方法直接去创建动态代理类,方式如下:

//3.3 采用Proxy 中提供的简单方法创建
  Collection collection3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
    new Class[]{Collection.class},
    new InvocationHandler()
  {
   private ArrayList target = new ArrayList();
   @Override
   public Object invoke(Object proxy, Method method, Object[] args)
     throws Throwable {
    return method.invoke(target, args);
   }

  });
  //System.out.println(collection3);
  collection3.add("abc");
  collection3.add("def");
  collection3.add("hij");
  System.out.println(collection3.size());

三、动态代理的原理简单分析

上面我们创建了动态代理类,下面我们分析下代理的原理:

下面在来看一个问题:

动态代理的工作原理图:

对上面的这个图,我们简单来说说:客户端动态生成代理类,然后调用代理类的方法,代理类内部调用handler.invoke()方法,在invoke中呢,我们又指向的目标类.这样就实现了代理了.我客户端调用代理的什么方法,invoke就只向目标类的同一个方法.而在指定目标类方法的前后呢,我们还可以做其他的操作,比如记录日志.图中用圈圈出来的部分就是代理类自己实现的功能了.这就是代理类的原理.

我们来做最后一步,将上面的动态生成的代理类,编写可生成代理和插入通告的通用方法:

test代码:

package study.javaenhance;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

public class ProxyTest
{
 public static void main(String[] args) throws Exception
 {
  Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
  System.out.println(clazzProxy1.getName());

  //上面输出的为一个类,那么一个类肯定有其构造方法和方法,下面我们来列出来

  System.out.println("----------begin constructors list----------");
  //1.列出构造方法
  Constructor[] constructors = clazzProxy1.getConstructors();
  for (Constructor constructor : constructors)
  {
   String name = constructor.getName();
   StringBuilder sBuilder = new StringBuilder(name);
   sBuilder.append('(');
   Class[] clazzParams = constructor.getParameterTypes();
   for (Class clazzParam : clazzParams) {
    sBuilder.append(clazzParam.getName()).append(',');
   }
   if(clazzParams!=null && clazzParams.length != 0)
    sBuilder.deleteCharAt(sBuilder.length()-1);
   sBuilder.append(')');
   System.out.println(sBuilder.toString());
  }

  //2.列出这个类字节码中的所有方法
  System.out.println("----------begin methods list----------");
  Method[] methods = clazzProxy1.getMethods();
  for(Method method : methods){
   String name = method.getName();
   StringBuilder sBuilder = new StringBuilder(name);
   sBuilder.append('(');
   Class[] clazzParams = method.getParameterTypes();
   for(Class clazzParam : clazzParams){
    sBuilder.append(clazzParam.getName()).append(',');
   }
   if(clazzParams!=null && clazzParams.length != 0)
    sBuilder.deleteCharAt(sBuilder.length()-1);
   sBuilder.append(')');
   System.out.println(sBuilder.toString());
  }

  //3.创建实例对象
  System.out.println("----------begin create instance object----------");
  Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
  MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
  Collection collection = (Collection) constructor.newInstance(myInvocationHandler);
  System.out.println(collection);
  collection.clear();
  //collection.size(); //报错,异常会发生,产生异常的原因在于其返回值为int类型,但是每一次调用一个方法都会调用到invoke方法,我们此时的invoke返回的为null,所以是没有办法转换为int类型的。

  //3.1 采用匿名内部类方式进行创建
  Collection collection2 = (Collection) constructor.newInstance(new InvocationHandler()
  {

   @Override
   public Object invoke(Object proxy, Method method, Object[] args)
     throws Throwable {
    // TODO Auto-generated method stub
    return null;
   }

  });
  System.out.println(collection2);
  collection2.clear();
  //collection2.size();

  //3.3 采用Proxy 中提供的简单方法创建
  Collection collection3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
    new Class[]{Collection.class},
    new InvocationHandler()
  {
   private ArrayList target = new ArrayList();
   @Override
   public Object invoke(Object proxy, Method method, Object[] args)
     throws Throwable {
    return method.invoke(target, args);
   }

  });
  //System.out.println(collection3);
  collection3.add("abc");
  collection3.add("def");
  collection3.add("hij");
  System.out.println(collection3.size());
  System.out.println(collection3.getClass().getName());//这个返回的是

  System.out.println("----------begin create instance object 抽化----------");
  //3.4抽出动态代理让目标对象和切面对象都是传入进去的

  final ArrayList target = new ArrayList();
  Collection collection4 = (Collection)getProxy(target,new MyAdvice());
  collection4.add("test1");
  collection4.add("test2");
  System.out.println(collection4.size());
 }

 private static Object getProxy(final Object target,final Advice advice) {
  Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new InvocationHandler()
  {
   @Override
   public Object invoke(Object proxy, Method method, Object[] args)
     throws Throwable {
    advice.beforeMethod(method);
    Object retValue = method.invoke(target, args);
    advice.afterMethod(method);
    return retValue;
   }

  });
  return object;
 }

}

四、实现类似Spring的可配置的AOP框架

首先,我们要完成的要求如下:

我们来模拟Spring的工厂模式读取从配置文件传递过来的类。如果这个类是一个普通类则直接返回。如果是一个代理类,则创建代理对象,返回代理类。

具体的理论知识可以看上面的图片

首先创建一个BeanFactory.java类。这是一个Bean工厂,用于读取配置文件中的类

package study.javaenhance.aopframework;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import study.javaenhance.Advice;

public class BeanFactory
{
 private Properties properties = new Properties();

 public BeanFactory(InputStream inStream)
 {
  try
  {
   properties.load(inStream);
  }
  catch (IOException e)
  {
   e.printStackTrace();
  }
  finally
  {
   if(inStream != null)
   {
    try
    {
     inStream.close();
    }
    catch (IOException e)
    {
     e.printStackTrace();
    }
   }
  }
 }

 public Object getBean(String name)
 {
  String className = properties.getProperty(name);
  Object bean = null;
  try
  {
   Class clazz = Class.forName(className);
   bean = clazz.newInstance();
   if(bean instanceof ProxyFactoryBean)
   {
    ProxyFactoryBean proxyBean = (ProxyFactoryBean) bean;
    Advice advice = (Advice)Class.forName(properties.getProperty(name + ".advice")).newInstance();
    Object target = Class.forName(properties.getProperty(name + ".target")).newInstance();
    proxyBean.setAdvice(advice);
    proxyBean.setTarget(target);
    Object proxy = proxyBean.getProxy();
    return proxy;
   }
  }
  catch (Exception e)
  {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  return bean;
 }
}

如果这个类中包含ProxyFactoryBean,则调用ProxyFactoryBean中的getProxy方法。动态生成代理类。
ProxyFactoryBean.java

package study.javaenhance.aopframework;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import study.javaenhance.Advice;

public class ProxyFactoryBean
{

 private Object target;

 private Advice advice;

 public Object getTarget() {
  return target;
 }

 public void setTarget(Object target) {
  this.target = target;
 }

 public Advice getAdvice() {
  return advice;
 }

 public void setAdvice(Advice advice) {
  this.advice = advice;
 }

 public Object getProxy()
 {
  Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),
    new InvocationHandler()
  {

   @Override
   public Object invoke(Object proxy, Method method, Object[] args)
     throws Throwable {
    advice.beforeMethod(method);
    Object retValue = method.invoke(target, args);
    advice.afterMethod(method);
    return retValue;
   }

  });
  return object;
 }

}

接下来创建一个config.Properties文件.config.Properties

xxx=java.util.ArrayList
#xxx=study.javaenhance.aopframework.ProxyFactoryBean
xxx.advice=study.javaenhance.MyAdvice
xxx.target=java.util.ArrayList

最后建立测试类:

package study.javaenhance.aopframework;

import java.io.InputStream;
import java.util.Collection;

public class AopFrameworkTest
{
 public static void main(String[] args)
 {
  InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
  Object bean = new BeanFactory(ips).getBean("xxx");
  System.out.println(bean.getClass().getName());
  ((Collection)bean).clear();
 }

}

测试OK

总结:整个java增强的视频学习完成了,一共记住了多少我也不知道.但知道了很多内在的知识,如果有时间的话,或者说过一段时间可以拿出来问下,提供自己的技能。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 基于接口实现java动态代理示例

    Subject.java 复制代码 代码如下: package _20140416_; import java.util.List; public interface Subject {   public String say(String name,int age);   public List<Person> getAllList(String name);} RealSubject.java 复制代码 代码如下: package _20140416_; import java.util.

  • java jdk动态代理详解

    jdk动态代理要对一个类进行代理,被代理的类必须实现至少一个接口,并且只有接口中的方法才能被代理. jdk实现动态代理一般分为三步: 1. 编写接口和实现类. 2. 写一个处理器,该处理器实现InvocationHandler接口,该接口只有一个方法,其签名为public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;可在该处理器的实现方法中,在方法调用前和调用后加入自己的代码,从而进行动态拦截

  • java实现动态代理方法浅析

    一些Java项目中在mybatis与spring整合中有MapperScannerConfigurer的使用,该类通过反向代理自动生成基于接口的动态代理类. 有鉴于此,本文浅析了java的动态代理. 本文使用动态代理模拟处理事务的拦截器. 接口: public interface UserService { public void addUser(); public void removeUser(); public void searchUser(); } 实现类: public class

  • java动态代理(jdk与cglib)详细解析

    JAVA的动态代理 代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务. 按照代理的创建时期,代理类可以分为两种. 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类的.class文件就已经

  • Java动态代理的应用详解

    动态代理其实就是java.lang.reflect.Proxy类动态的根据您指定的所有接口生成一个class byte,该class会继承Proxy类,并实现所有你指定的接口(您在参数中传入的接口数组):然后再利用您指定的classloader将 class byte加载进系统,最后生成这样一个类的对象,并初始化该对象的一些值,如invocationHandler,以即所有的接口对应的Method成员. 初始化之后将对象返回给调用的客户端.这样客户端拿到的就是一个实现你所有的接口的Proxy对象

  • 十分钟理解Java中的动态代理

    若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的. 通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类. 一.概述 1. 什么是代理 我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家"委托"代理为其销售商品.关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,"委托者"对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就

  • java代理模式与动态代理模式详解

    1.代理模式 所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用.代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用. 生活中的例子:过年加班比较忙,没空去买火车票,这时可以打个电话到附近的票务中心,叫他们帮你买张回家的火车票,当然这会附加额外的劳务费.但要清楚票务中心自己并不卖票,只有火车站才真正卖票,票务中心卖给你的票其实是通过火车站实现的.这点很重要!

  • java动态代理和cglib动态代理示例分享

    java动态代理类可以分为两种. 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类的.class文件就已经存在了. 动态代理:在程序运行时,运用反射机制动态创建而成. 一.首先我们进行java动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: 复制代码 代码如下: package testAOP;public interface Saying {public void sayHello(String name);    public void ta

  • 代理模式之Java动态代理实现方法

    今天一个偶然的机会我突然想看看JDK的动态代理,因为以前也知道一点,而且只是简单的想测试一下使用,使用很快里就写好了这么几个接口和类:接口类:UserService.java 复制代码 代码如下: package com.yixi.proxy;public interface UserService {    public int save() ;    public void update(int id);} 实现类:UserServiceImpl.java 复制代码 代码如下: packag

  • java使用动态代理来实现AOP(日志记录)的实例代码

    下面是一个AOP实现的简单例子: 首先定义一些业务方法: 复制代码 代码如下: /** * Created with IntelliJ IDEA. * Author: wangjie  email:tiantian.china.2@gmail.com * Date: 13-9-23 * Time: 下午3:49 */public interface BussinessService {    public String login(String username, String password

随机推荐