传智博客---java反射之动态代理

动态代理
package com.zhou.dongtaidaili;

public interface ArithmeticCalculator {

//这里面有4个方法:加、减、乘、除,前两个方法我写带返回值的,后两个方法我写不带返回值的;
int add(int i,int j);
int sub(int i,int j);
void mul(int i,int j);
void div(int i,int j);
}

package com.zhou.dongtaidaili;
public interface Validator {
 void validate();
}

package com.zhou.dongtaidaili;
public interface Service {
  void addNew(Person person);
  void delete(Integer id);
  void update(Person person);
}
package com.zhou.dongtaidaili;
/**
* 动态代理:
*     动态代理是反射的一部分,实际上是让我们来学一个类,一个方法,就是Proxy类的newInstance()方法;
*     为什么会有动态代理的这样一个技术?在Spring里面我们会学一个技术点叫AOP(Aspect Orient Program,
*     面向切面编程),现在我们学的是面向对象编程,面向切面编程是面向对象编程的一个很好的补充,这个时候实际上我们会有这
*     样一个技术点,叫AOP代理的方法;我们具体来看一个需求,这个需求本身我们是可以理解的,我们上面有一个接口叫数学计
*     算器,但还可以有一个单位计算器,其中我们用一个就可以了,然后,我们把这个数学计算器进行实现,实现基本上挺简单的,
*     但是我们会有一些额外的需求,需求是,在执行程序的时候,可以跟踪正在发生的活动,即日志;
*     ---后面的话我们可能会有一些额外的需求:
*           1.在程序执行期间,跟踪正在发生的活动;
*           2.希望计算器只能处理正数的运算;
*/
public class ArithmeticCalculatorInfo implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
//方法执行之前,打印一行代码:
System.out.println("add方法执行以"+i+","+j+"开始。");
int result = i + j;
//在方法结束之前再打印一句话:
System.out.println("add方法执行以"+result+"�Y束。");
return result;
}

/**
* 方法以什么什么开始,和以什么什么结束,那么这将导致在每个方法里面都需要写这样的打印语句;
*   ---这个时候会发现一个问题:是不是写的代码都类似啊,但是有的地方,比如说每个方法的方法名不一样,每个方法传入的参数可能也不一样;
*      我们这个有两个需求,一个是要实现这个需求,这个无可厚非;第二就是要求在执行方法时执行日志,方法执行之前打印一句话,方法执行结
*      束之前打印一句话,这个需求我们觉得也很好,但这样写完之后我们会发现一个问题:这个这一写的话,每一个方法的额外代码有点太多了,
*      而且做日志的代码都已经比我实现功能的实际代码都要多了,导致这个方法的这个代码急剧膨胀,这个是目前的问题,当然了,我在执行的时
*      候是没问题的,结果是没问题,也符合要求,但是代码膨胀得厉害,这个时候我们会有一个想法:也就是我们应该有办法能够解决这样的,需
*      求的代码的功能都差不多,但是方法名和参数不同的这样代码膨胀的问题-----解决这个问题的技术就叫动态代理;
*      
* 非模块化的横切关注点所带来的问题:
*      横切关注点是跨越应用程序多个模块的功能,如跨越多个方法的这些日志的功能就叫横切关注点,即每个方法都需要加的这些额外的代码,我
*      们称之为横切关注点,横切关注点所带来的问题就是使代码急剧膨胀,越来越多的非业务需求(日志和验证)加入后,原有的计算器方法急剧膨胀;  
*      属于系统范围内的需求通常需要跨越多个模块(横切关注点),这些类似的需求包括日志,验证,事物等。  这个时候,一个日志可能会跨越多个方
*      法,一个验证也可能跨越多个方法,这个时候它就像一个切面一样;
*    非模块化的横切关注点将会导致的问题有:
*        1.代码混乱:每个方法在处理核心逻辑的同时,还必须兼顾其他多个关注点;
*        2.代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块里多次重复相同的日志代码,如果日志需求发生变化,必须修改所有模块;
*        
* 如何解决非模块化的横切关注点所带来的这些问题:
*   怎么解决呢?在我调用这个目标方法之前,我先来做一个验证,然后我再来做一个日志,在调用目标方法之前我先来干这样的事,这个就是代理设计模式的原理思想;
*    ----使用动态代理模块化横切关注点:
*        代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上;
*/
@Override
public int sub(int i, int j) {
//方法执行之前,打印一行代码:
System.out.println("sub方法执行以"+i+","+j+"开始。");
int result = i - j;
//方法结束之前,打印一行代码:
System.out.println("sub方法执行以"+result+"结束。");
return result;
}
@Override
public void mul(int i, int j) {
//方法执行之前,打印一行代码:
System.out.println("mul方法执行以"+i+","+j+"开始。");
int result = i * j;
//方法结束之前,打印一行代码
System.out.println("mul方法执行以"+result+"结束。");
System.out.println(result);
}
@Override
public void div(int i, int j) {
//方法执行之后,打印一行代码:
System.out.println("div方法执行以"+i+","+j+"开始。");
int result = i / j;
//方法结束之前,打印一行代码:
System.out.println("div方法执行以"+result+"结束。");
System.out.println(result);
}
}

//下面是没有日志信息等额外需求的接口的实现类;  
package com.zhou.dongtaidaili;
public class ArithmeticCalculatorInfo2 implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public void mul(int i, int j) {
int result = i * j;
System.out.println(result);
}
@Override
public void div(int i, int j) {
int result = i / j;
System.out.println(result);
}
}

package com.zhou.dongtaidaili;
public class Person {
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + "]";
}
public Person(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
private void person() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private Integer id;
private String name;
}
package com.zhou.dongtaidaili;
import java.util.HashMap;
import java.util.Map;
import javax.management.RuntimeErrorException;
public class PersonServiceInFo implements Service {
/**
* 增加一个人、删除一个人,更新一个人,前提是你要往容器中放这些信息吧?
*   具体做的话,我们可以来写一个Map进行模拟;
*/
private static Map<Integer,Person> persons = new HashMap<Integer,Person>();
//写Map<Integer,Person>类型成员变量的getter方法;
public static Map<Integer, Person> getPersons() {
return persons;
}  
  /**
     * 因为现在我们还没有连接数据库;
     * 这样的话,我们在构造器里面提供一个模拟的实现;
     */
public PersonServiceInFo() {
persons.put(1001, new Person(1001,"zhangsan"));
persons.put(1002, new Person(1002,"lisi"));
}
@Override
public void addNew(Person person) {
  persons.put(person.getId(), person); //增加一个人;
}
@Override
public void delete(Integer id) {
//为了看回滚事务,我们做一个判断:
 if(id == 1001){
throw new RuntimeException("id为1001的人不能被删除");
 }
 persons.remove(id); //删除一个人;
}
@Override
public void update(Person person) {
 persons.put(person.getId(),person); //更新一个人,我把以前那个人给替换掉就更新一个人了;
}
}

package com.zhou.dongtaidaili;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 首先要得到一个PersonServiceInFo被代理类的一个代理类PersonServiceInFoProxy;
* @author Administrator
*
*/
public class PersonServiceInFoProxy {
   private PersonServiceInFo target = null; //声明一个被代理的对象target;
//然后我在创建这个被代理对象的时候把这个target传进来,这里用构造方法传;
   public PersonServiceInFoProxy(PersonServiceInFo target){
this.target = target;
   }
//然后我有一个方法,返回Service类型的代理对象;
   public Service getPersonServiceProxy(){
//声明一个被代理对象;
Service proxy = null;
//然后得到一个代理对象;
proxy = (Service) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//在之前写一句话;
System.out.println("开启事务");
//然后是执行被代理对象的目标方法;
try {
Object result = method.invoke(target, args); //传的是被代理对象,注意,这个被代理对象是成员变量,所以不能加final;
   System.out.println("提交事务");
   return result;
} catch (Exception e) {
                   e.printStackTrace();  
//出异常时进行回滚;
                   System.out.println("回滚事务");
}
return null;
}
});
return proxy; //这个方法返回一个代理对象;
   }
}
package com.zhou.dongtaidaili;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import org.junit.Test;
public class ProxyTest {
// @Test
// public void testCalculator() {
// ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculatorInfo();
// arithmeticCalculator.mul(25, 4);
//
// }
// 我们来测试一下ProxyTest
@Test
public void testProxy() {
/**
* ---第一步:我需要有一个被代理对象:
*     ArithmeticCalculator arithmeticCalculator;
*     这个被代理对象是已经实现接口的类的对象,并且实现那个接口时的方法中没有日志等额外需求的代码,实现接口的方法                    *       比较干净;
*/
/**
* public static Object newProxyInstance(ClassLoader loader, Class<?>[]
* interfaces, InvocationHandler h) throws
* IllegalArgumentException的参数说明:
* 第一个参数,ClassLoader:由动态代理产生的对象由哪个类加载器来加载,这个对象比较特殊,
* 正常的对象我们是不是new出来的啊,new的话应该是由系统类加载器进行加载,而这个对象是这个方
* 法凭空生成的,是我们用Proxy.newProxyInstance(null, null, null);这
* 个方法来做的,那么这个时候我们要指定它所使用的类加载器是谁,即这个时候我们需要指定这个对象由 哪一个类                                  *  加载器来进行加载;
* ---哪一个类加载器呢?通常是与我目标对象使用同一个类加载器,即通常情况下和被代理对象使用一样的类加载器                                  *   ;
*/
final ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculatorInfo2(); // 这个里面没有其他任何日志代码,所以里面没有参数,这个arithmeticCalculator也就是这个被代理对象;
/**
* ---第二步:获得被代理对象的代理对象:
*/
/**
* 需要代理的情况:
* 第一个参数:我通常和被代理对象(也就是原来没有任何日志的那个对象共用一个的类加载器,即arithmeticCalculator
* .getClass().getClassLoader()---即通过的是对象所在的类,再找到类加载器); 第二个参数Class<?>[]
* interfaces:是Class类型的数组,由动态代理产生对象必须需要实现的那些接口,将其放Class数组里,
* 这里只实现了一个ArithmeticCalculator接口,所以这样写new
* Class[]{ArithmeticCalculator.class,
* 接口其实也是一个抽象类,所以直接用(类.class)就得到其Class类的对象
* ,也就是说我们要产生这个对象的话,我们必须要知道这个代理对象是什么类型的
* ,这个类型必须是放在接口的Class[]数组。--这个说明什么?说明我产生 这个代理对象后,这个代理对象也可以来实现这些接口的方法;
* 第三个参数:当我调用接口的那些方法的时候,它要产生什么行为呢?就是我们这个第三个参数InvocationHandler---
* 当具体调用代理对象的方法时
* ,将产生什么行为(我们已经有类加载器了,也就是第一个参数,比如说经纪人和艺人都是属于同一家公司的;我还代理的是哪一个艺人(也
* 就是这里的第二个参数
* ,放在Class类的数组里面的那些所要实现的接口所在的Class对象),现在是艺人给了你了,她怎么做啊,这个就是第三个参数要干的事情
* ,当我在调用执行被代理对象的一个方法时同时会产生的反应),InvocationHandler是一个接口,我们需要提供这个接口的实现;
* ----------写完之后呢?这个就是一个 proxy,我现在来调proxy的方法;
*
*/
ArithmeticCalculator proxy = (ArithmeticCalculator) Proxy
.newProxyInstance(arithmeticCalculator.getClass()
.getClassLoader(),
new Class[] { ArithmeticCalculator.class },
new InvocationHandler() {
/**
* 目标方法在哪呢?目标方法在public Object invoke(Object proxy,
* Method method, Object[] args)这个里面;
* ---这个里面有三个参数:第一个参数proxy: 第二个参数method:是正在被调用的方法;
* 第三个参数args:调用方法时传入的参数;
*/
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
// System.out.println("invoke...");//我先来打印一句话;
// 我打印一个method:
// System.out.println("Method: "+method);
// /**
// * 调用proxy.mul(5, 10)时:Method: public abstract
// void
// com.zhou.dongtaidaili.ArithmeticCalculator.mul(int,int)
// * 调用proxy.add(8, 10)时:Method: public abstract
// int
// com.zhou.dongtaidaili.ArithmeticCalculator.add(int,int)
// */
// System.out.println(Arrays.asList(args));//看看调用方法时传入的参数,这里将数组用List集合的形式列出,看得清楚点;
//
// 在调用这个方法开始执行的时候我来打印一句话:
System.out.println("the method:" + method.getName()
+ ",begin with:" + Arrays.asList(args));
/**
* 到这个时候清楚了:这个时候来调用被代理类的目标方法:
*/
Object result = method.invoke(
arithmeticCalculator, args); // 第一个参数说明是代理谁的,arithmeticCalculator这个就是那个被代理对象;
/**
* 这个时候会讲被代理对象所在的类变成一个final类:如下所示: final
* ArithmeticCalculator arithmeticCalculator =
* new ArithmeticCalculatorInfo2();//
* 这个里面没有其他任何日志代码
* ,所以里面没有参数,这个arithmeticCalculator也就是这个被代理对象;
*/
// 在调用这个方法执行结束的时候我再来打印一句话:
System.out.println("the method:" + method.getName()
+ ",end with:" + Arrays.asList(args));
return result; // 现在统一返回一个值0;
}
}); // 我需要的是一个对象,Proxy类有一个方法,这个方法叫newProxyInstance(参数1,参数2,参数3),然后呢,我来产生一个方法,它有三个参数,暂且先写为null;
/**
* 第三步:我写完代理对象之后,我肯定要去调用这个代理对象代理的被代理对象的某些个方法;
*/
// 写完之后呢?这个就是一个 proxy,我现在来调proxy的方法;
proxy.mul(5, 10); // 在执行的时候在调用这句System.out.println("invoke...");
int result = proxy.add(8, 10); // 在执行的时候在调这句System.out.println("invoke...");----即都在调用InvocationHandler这个接口的实现;
System.out.println(result);
   /**
    * ---这个时候程序执行的结果是:
    *          the method:mul,begin with:[5, 10]
*         50
    *        the method:mul,end with:[5, 10]
* the method:add,begin with:[8, 10]
* the method:add,end with:[8, 10]
* 18
*  这个时候我们就将这个程序模块化了,使用动态代理,修改起来也很容易;            
    */
}
/**
* --小结:首先讲了什么是代理,这里并没有讲代理模式
*       然后讲了一个方法,Proxy类的public static Object newProxyInstance(ClassLoader loader,
    *                                 Class<?>[] interfaces,
    *                                 InvocationHandler h)
    *                          throws IllegalArgumentException
    *       方法,这里面有三个参数,我将通过这个方法,而不使用new的方式生成一个代理对象,那么这个时候,这个代理对象就需要指定由哪个类加载器
    *          来进行加载,第二个需要指定,我们这个代理产生的对象需要实现哪些接口,即我这个代理是什么类型的,你可以指定多个接口,但是必须是
    *          接口对应的Class类类型的数组,不是接口不行;最后一个InvocationHandler参数,是在我具体调用被代理对象的方法的时候,
    *          我都需要干些什么,就需要一个InvocationHandler接口的实现类对象,InvocationHandler是个接口,我们用这个接
    *          口创建一个匿名内部类的实现类实例,需要实现InvocationHandler接口的public Object invoke(Object proxy, Method method,
* Object[] args) throws Throwable;这个方法,第一个参数是通过Proxy类的ewProxyInstance(ClassLoader loader,
    *                                 Class<?>[] interfaces,
    *                                 InvocationHandler h)方法获得的被代理对象的类的代理对象;第二个参数是被代理对象实现的比较干净的正在被调用的那个目标方法,第三个参数是正在调用的那个方法需要传入的参数;
    *          再在这个InvocationHandler接口的实现类中调用Method对象的 method.invoke(obj, args)方法来执行被代理对象的这个目标方法,这里的obj是被代理对象,args是这个被代理对象的这个方法的参数;
*/
/**
* 关于动态代理的一些细节:
*   1.需要一个被代理的对象;
*   2.一般的,代理对象Proxy.newProxyInstance()的返回值一定是一个被代理对象实现的接口的类型ArithmeticCalculator proxy,当然也可以是其他的接口类型;
*      注意:第二个参数必须是一个接口类型的数组,里面不能有任何的类;
*      提示:如果代理对象不需要实现被代理对象实现的接口以外的接口时,可以使用(被代理对象.getClass().getInterfaces())这样的方法来获取第二个接口数组类型的参数;
*   3.创建InvocationHandler接口的实现类对象,通常使用匿名内部类的方式创建;
*       ---注意:被代理对象需要是final类型的:因为有可能这个方法调用结束了,但是这个代理对象还没有变成垃圾,但是我这个代理对象是不是用到了被代理对象?那我这个方法要是结束
*             了的话,我写的这个代理对象是个局部变量,方法结束了局部变量是不是会变成垃圾啊?但是我产生的代理对象有可能在方法结束时不是垃圾吧,这个时候我在调其他方法的时候我
*             需要用到这个被代理对象,就要求这个被代理对象不能是一个局部变量,要求这个在方法执行完之后不能变成垃圾,所以说它需要是final类型;
*   4.InvocationHandler的invok()方法中的第一个参数Object类型的proxy指的是正在被返回的那个代理对象,一般情况下不使用它;    
*/
@Test
public void testProxy2(){
//第一步:需要一个被代理的对象;
final ArithmeticCalculator target = new ArithmeticCalculatorInfo2();//被代理对象;
//打印一下被代理对象所在类实现的接口:
System.out.println(Arrays.asList(target.getClass().getInterfaces())); //[interface com.zhou.dongtaidaili.ArithmeticCalculator]
//第二步:获得代理对象;类加载器通常和被代理对象的类加载器一样;
Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),
//new Class[]{ArithmeticCalculator.class,Validator.class},
//如果我要跟被代理对象用同样的接口,我可以像下面这样写:通过被代理对象的类来获取所有的被代理对象所在类实现了的所有的接口;
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object obj = null;
try {
obj = method.invoke(target, args);
} catch (Exception e) {
e.printStackTrace(); //当调用方法出异常了,就打印异常信息,叫异常通知;
}
return obj;
}
}); // 这里不写调用被代理方法要做的额外的需求的代码;
 int re = ((ArithmeticCalculator) proxy).add(1,2); //Object proxy,是Object类型的代理对象,使用的时候需要强转;
 System.out.println(re);
 ((ArithmeticCalculator) proxy).mul(2,3);
}
/**
* 练习:
*    定义一个Service接口:
*       定义如下方法:
*        addNew(Person person);
*        delete(Integer id);
*        update(Person person);
*        
*    并提供具体的实现类。
*    
*    使用动态代理实现事物操作:
*       1.在具体调用每个Service方法前,都打印:开始事物;
*       2.方法正常结束,都打印:事物提交;
*       3.若在调用目标方法出异常的情况下:打印:事物回滚;
*/
@Test
public void testPersonService(){
//在测试之前首先要得到ServiceInFo类的一个代理类
Service target = new PersonServiceInFo(); //创建一个被代理对象;
Service proxy = (Service) new PersonServiceInFoProxy((PersonServiceInFo) target).getPersonServiceProxy();
//下面来操作这个代理对象proxy;
  //往里面放一个人;
     //我在执行之前先打印这个人,看看效果;
System.out.println(PersonServiceInFo.getPersons());
proxy.addNew(new Person(2007, "周星驰"));
//为了看看效果,我在被代理对象所在的那个PersonServiceInFo类里面改写:
         //我在执行之后再打印一下这个人,看看效果;
   System.out.println(PersonServiceInFo.getPersons());
              /**
               * 程序执行结果:
               *       {1001=Person [id=1001, name=zhangsan], 1002=Person [id=1002, name=lisi]}
* 开启事务
* 提交事务
* {1001=Person [id=1001, name=zhangsan], 1002=Person [id=1002, name=lisi], 2007=Person [id=2007, name=周星驰]}
               */
  //添加方法之后还有一个方法,删除方法:
   proxy.delete(1001);
 //再打印一遍:
   System.out.println(PersonServiceInFo.getPersons());
             /**
               * 程序执行结果:
               *        {1001=Person [id=1001, name=zhangsan], 1002=Person [id=1002, name=lisi]}
* 开启事务
* 提交事务
* {1001=Person [id=1001, name=zhangsan], 1002=Person [id=1002, name=lisi], 2007=Person [id=2007, name=周星驰]}
* 开启事务
* 提交事务
* {1002=Person [id=1002, name=lisi], 2007=Person [id=2007, name=周星驰]}
               */
//还有一个方法,更新的方法:
   proxy.update(new Person(1002, "周润发"));
 //更新之后再打印一遍:
   System.out.println(PersonServiceInFo.getPersons());
              /**
               * 程序执行结果:
               *        {1001=Person [id=1001, name=zhangsan], 1002=Person [id=1002, name=lisi]}
* 开启事务
* 提交事务
* {1001=Person [id=1001, name=zhangsan], 1002=Person [id=1002, name=lisi], 2007=Person [id=2007, name=周星驰]}
* 开启事务
* 提交事务
* {1002=Person [id=1002, name=lisi], 2007=Person [id=2007, name=周星驰]}
* 开启事务
* 提交事务
* {1002=Person [id=1002, name=周润发], 2007=Person [id=2007, name=周星驰]}
               */
}
}


本文出自 “IT技术JAVA” 博客,转载请与作者联系!

你可能感兴趣的:(java反射之动态代理)