从JDK动态代理到spring AOP

JDK动态代理到spring AOP

引言:

所谓JDK动态代理(Dynamic Proxy),就是指在运行时生成目标类的代理类,并能通过反射机制调用目标类的代码,在调用目标代码的前后可以加入横切逻辑,实现目标方法增加的目的。在讲述动态代理之前,我们先了解下静态代理,在这之后,我们将学习JDK动态代理,并延伸学习spring AOP,因为spring AOP的原理主要就是动态代理机制,当然这包括JDK 动态代理与CGLIB两种方式。

1.        静态代理:

 静态代理是指由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。看下边的例子:

1.1         首选声明一个接口

/**

 * 

 * @author hyp

 * @date 2013-3-19

 * 做菜的接口

 */

public interface MakeFoodInterface {

    /**

     * 制作麻婆豆腐

     * @param df 豆腐

     * @return

     */

    public String makeMpdf(String df);

    

    /**

     * 制作鱼香豆丝

     * @param rou 肉

     * @param suger 糖

     * @return

     */

    public String makeYxrs(String rou,String suger);

}

 

 

 

 

 

1.2         接口的实现

 

 

/**

 * 做菜的实现类,四川菜

 * @author hyp

 * @date 2013-3-19

 */

public class MakeFoodSCImpl implements MakeFoodInterface{

 

    @Override

    public String makeMpdf(String df) {

       System.out.println("makMpdf--------------"+df);

       return df;

    }

    @Override

    public String makeYxrs(String rou, String suger) {

       System.out.println("makeYxrs--------------"+rou+suger);

       return rou+suger;

    }

}

 

1.3         我们自己实现的代理类,

用来在方法前后添加一些我们想要进行的操作

 

/**

 * 四川菜的代理类,方法加强

 * @author hyp

 * @date 2013-3-19

 */

public class MakeFoodSCImplProxy implements MakeFoodInterface {

    private MakeFoodSCImpl makeFoodSC;

    /**

     * 构造函数,传入一个四川菜的实例

     * @param makeFoodSC

     */

    public MakeFoodSCImplProxy(MakeFoodSCImpl makeFoodSC) {

       super();

       this.makeFoodSC = makeFoodSC;

    }

     /**

     * 方法加强,在做菜前后进行一些别的操作

     */

    @Override

    public String makeMpdf(String df) {

       System.out.println("MakeFoodSCImplProxy方法调用前-------------------------");

       String ret= this.makeFoodSC.makeMpdf(df);

       System.out.println("MakeFoodSCImplProxy方法调用后-------------------------");

       return ret;

    }

 

    @Override

    public String makeYxrs(String rou, String suger) {

       return null;

    }
}

1.4         测试类

 

/**

 * 静态代理类测试

 * @author hyp

 * @date 2013-3-19

 */

public class StaticProxyTest {

    public static void main(String[] args) {

       MakeFoodSCImplProxy makeFoodProxy = new MakeFoodSCImplProxy(new MakeFoodSCImpl());

       makeFoodProxy.makeMpdf("mpdf");

    }

}

  

1.5         运行结果:

MakeFoodSCImplProxy方法调用前-------------------------

makMpdf--------------mpdf

MakeFoodSCImplProxy方法调用后-------------------------

6,总结:我们通过实现目标类的接口,实现接口方法的时候,在调用目标对象方法前后,进行了我们添加的操作,优点是没有改变原代码的结构,缺点是对于每个方法,我们都必须手动去实现。

2.        动态代理:

  动态代理是指在运行过程中生成目标类的代理,主要用到了ProxyInvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,在并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。

   目标接口与类都和上一个例子一样,下边我们看如何实现InvocationHandler,并获得代理对象:

2        

2.1   代理实例,必须要实现InvocationHandler接口

 

public class MyInvocationHandler implements InvocationHandler {

     private Object originalObject;   

 

   // 将欲代理的对象传入,返回一个代理对象   

     public Object newProxy(Object obj) {   

         this.originalObject = obj;   

          // 三个参数,第一个是欲代理对象的类加载器,第二个是得到这个类的接口集合,第三个参数是一个handler   

         return (Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj   

                     .getClass().getInterfaces(), this));   

      }  

    @Override

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

           throws Throwable {

       System.out.println("MyInvocationHandler---------方法调用前");

       System.out.println(proxy.getClass()+";"+originalObject.getClass());

       //传入目标对象,调用 目标对象的方法

       method.invoke(originalObject, args);

       System.out.println("MyInvocationHandler---------方法调用后");

       return null;

    }

}

 

2.2         测试例子:

 

/**

 * 采用JDK动态代理,生成目标对象的代理类

 * @author hyp

 * @date 2013-3-19

 */

public class ProxyTest {

    public static void main(String[] args) {

       MakeFoodSCImpl foodSC = new MakeFoodSCImpl();

       MyInvocationHandler invocationHandler = new MyInvocationHandler();

       MakeFoodInterface foodSCProxy = (MakeFoodInterface)invocationHandler.newProxy(foodSC);

       foodSCProxy.makeMpdf("mpdf");

    }

}

 

2.3         运行结果:

MyInvocationHandler---------方法调用前

class $Proxy0;class test.proxy.MakeFoodSCImpl

makMpdf--------------mpdf

MyInvocationHandler---------方法调用后

2.4         总结:

通过实现 InvocationHandler,我们实现了自己的目标类代理实例类,代理实例类拥有一个目标类的变量,通过目标类变量,我们用Proxy.newProxyInstance方法获取代理实例,并实现invoke方法,我们通过反射调用目标类的方法,并做到让目标类方法的目的。

Cglib动态代理:

  JDK的动态代理,是通过实现目标类的接口,来生成一个代理类。但如果目标类没有实现接口,我们可以通过另一种方式来实现动态代理,那就是cglib ,cglib 通过生成目标类的子类,获取的代理对象是目标类的子类,我们也可能对方法加强,当然目标类不能是final类型,以下是具体实现代码:

在这里我们让目标类不在实现接口,其它和上边程序一样。

2.5         通过cglib

1.       

2.       

2.1.       

2.2.       

2.3.       

2.4.       

2.5.       

2.5.1.      获得代理

 

import java.lang.reflect.Method;

 

import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

/**

 * 

 * @author hyp

 * @date 2013-3-19

 * 通过cglib生成目标类的代理类,代理类其实是目标类的子类

 */

public class CglibProxyFactory implements MethodInterceptor {

    //原对象(或叫目标对象)

    private Object originalObject;

    //获取目标对象的代理类

    public Object getProxy(Object originalObject){

       this.originalObject= originalObject;

       Enhancer enhancer = new Enhancer();

       enhancer.setSuperclass(this.originalObject.getClass());

       enhancer.setCallback(this);

       Object o= enhancer.create();

       System.out.println(o.getClass());

       System.out.println(originalObject.getClass());

       return o;

       

    }

       @Override

    public Object intercept(Object proxy, Method method, Object[] args,   

       MethodProxy methodProxy) throws Throwable {

       System.out.println("CglibProxyFactory 方法执行前---------------");

       Object ret = method.invoke(this.originalObject, args);  

       System.out.println("CglibProxyFactory 方法执行后---------------");

       return ret;

    }

}

2.5.2.      cglib测试

 

public class CglibTest {

 

    public static void main(String[] args) {

       MakeFoodSC foodSC= new MakeFoodSC();

       CglibProxyFactory cglibFactory = new CglibProxyFactory();

       //获取代理类,这里是目标类的子类

       MakeFoodSC proxyFoodSC= (MakeFoodSC)cglibFactory.getProxy(foodSC);

       proxyFoodSC.makeMpdf("mpdf");

    }

}

 

 2.5.3.      运行结果:

class test.cglib.MakeFoodSC$$EnhancerByCGLIB$$a4ffb679

class test.cglib.MakeFoodSC

CglibProxyFactory 方法执行前---------------

makMpdf--------------mpdf

CglibProxyFactory 方法执行后---------------

2.5.4.      总结:

结果和jdk动态代理一样,区别是目标对象不用实现任务接口

3.        Spring AOP:

  Spring aop(Aspect Oriented Programming)的原理就是通过动态代理实现,我们通过<aop:aspectj-autoproxy>这个配置让spring 生成目标对象的代理类,下边看具体代码:

3          

3.1         目标类(Target Object)

这里的A接口就是对方法makeMpdf()的声明,这里不帖出,

/**

 * @author hyp

 * @date 2013-3-19

 */

public  class MakeFood implements A{

    @Override

    public String makeMpdf(String df){

       System.out.println("MakeFood-------------------"+df);

       return df;

    }

}

 

 

 

3.2         通知(Advice

 

import java.lang.reflect.Method;

 

import org.springframework.aop.MethodBeforeAdvice;

 

/**

 * @author hyp

 * @date 2013-3-19

 */

public class MakeFoodAdvice implements MethodBeforeAdvice{
    @Override
    public void before(Method arg0, Object[] arg1, Object arg2)  throws Throwable {

       System.out.println("before Method invoke");

    }
}

 

3.3    切入点(PointCut

 

import java.lang.reflect.Method;

 import org.springframework.aop.ClassFilter;

import org.springframework.aop.MethodMatcher;

import org.springframework.aop.Pointcut;

 

/**

 * @author hyp

 * @date 2013-3-19

 */

public class MakeFoodPointCut implements Pointcut {

     @Override

    public ClassFilter getClassFilter() {

       return new MakeFoodFilter();

    }    

    @Override

    public MethodMatcher getMethodMatcher() {

              return new MakeFoodMethodMather();

    }
}

class MakeFoodFilter implements ClassFilter{
    @Override
  public boolean matches(Class clazz) {

       System.out.println("mathches--------"+clazz.getSimpleName());

       if(MakeFood.class.equals(clazz)){

           return true;

       }

       return false;

    }

}

class MakeFoodMethodMather implements MethodMatcher{

 @Override

    public boolean isRuntime() {

       return false;

    }

     @Override

    public boolean matches(Method method, Class clazz, Object[] arg2) {

       System.out.println("matches(3 args)-------"+clazz.getSimpleName()+method.getName());

       if(MakeFood.class.equals(clazz)&&method.getName().equals("makeMpdf")){

           return true;

       }

       return false;

    }
    @Override

    public boolean matches(Method method, Class clazz) {

       System.out.println("matches(2 args)-------"+clazz.getSimpleName()+method.getName());

       if(clazz.getSimpleName().equals("MakeFood")&&method.getName().equals("makeMpdf")){

           return true;

       }

       return false;

    }

}

3.4         配置信息

<bean id="makeFoodAdvice" class="test.springAop.MakeFoodAdvice" />

    <bean id="makeFoodPointCut" class="test.springAop.MakeFoodPointCut" />

    <bean id="makeFood" class="test.springAop.MakeFood" />

    <bean class="org.springframework.aop.support.DefaultPointcutAdvisor">

       <property name="advice" ref="makeFoodAdvice"></property>

       <property name="pointcut" ref="makeFoodPointCut"></property>

    </bean>

    <aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>

3.5         运行结果:

mathches--------MakeFoodPointCut

mathches--------MakeFood

matches(2 args)-------MakeFoodmakeMpdf

matches(2 args)-------MakeFoodmakeMpdf

before Method invoke

MakeFood-------------------mpdf

这里边,通过输出我们可以看到ClassFiltermatches方法是执行了多次,MethodMatchermatches方法也执行也多次,是什么原因呢,下边会讲到。

我们先讲一下Spring的切入点框架的核心接口Pointcut,这个类的作用是将通知应用到特定的类和方法,将pointcut 分成两个部分有利于重用类和方法的匹配部分。

ClassFilter接口用来将切入点限定在一个给定的类集合中(ClassFilter接口决定了一个类是否符合通知的要求)。如果ClassFilter接口的matches()方法总是返回true,那么所有的目标类都将被匹配。实现这个接口的类决定了以参数传入进来的类是否应该被通知。实现这个接口的类一般根据类名决定,但这并不是必须的

MethodMatcher接口有三个方法,但是每个方法用在被代理对象生命周期的特定时刻。matches(Method m, Class targetClass)方法根据目标类和方法决定一个方法是否该被通知。因此可以静态地判断,所以可以在AOP代理被创建的时候调用一次这个方法。这个方法的结果最终决定了通知是否被织入。

如果matches(Method, Class)方法返回trueisRunime()被调用来决定MethodMatcher的类型。有两种类型:静态的和动态的。静态切入点的意思是通知总是被执行。如果一个切入点是静态的,isRuntime()方法应该返回false。动态切入点根据运行时方法的参数值决定通知是否应该被执行。如果切入点是动态的,isRuntime()方法应该返回true。和matches(Method, Class)方法一样,isRuntime()方法也是只在代理类被创建时调用一次。

如果一个切入点是静态的,matches(Method, Class, Object[])方法永远不会被调用,因为不需要根据参数运行时值来决定通知是否需要被应用。对于动态切入点,目标方法每次被调用的时候,matches(Method, Class, Object[])方法就要被调用。每次这个方法被调用的时候,就增加了运行时的负担,因此要尽可能的使用静态切入点。

matches(Method, Class) 方法被用来测试这个切入点是否匹配目标类的给定方法。这个测试可以在AOP代理创建的时候执行,避免在所有方法调用时都需要进行测试。如果2个参数的匹配方法对某个方法返回true,并且MethodMatcher isRuntime()也返回true,那么3个参数的匹配方法将在每次方法调用的时候被调用。这使切入点能够在目标通知被执行之前立即查看传递给方法调用的参数。

大部分MethodMatcher都是静态的,意味着isRuntime()方法返回false。这种情况下3个参数的匹配方法永远不会被调用。

通过这些理论知识的学习我们再来看上边的输出结果,mathches--------MakeFoodPointCut

mathches--------MakeFood

这个输出了两次,分别是对MakeFoodPointCutMakeFood进行了判断,这个动作发生在类加载时,

matches(2 args)-------MakeFoodmakeMpdf 也是发生在类加载时,

而后边的输出

matches(2 args)-------MakeFoodmakeMpdf

before Method invoke

MakeFood-------------------mpdf

发生在方法调用这里,我们可能通过注释

    /*    A makeFood=(A)bf.getBean("makeFood");

         makeFood.makeMpdf("mpdf");*/这个来获得验证。

 

 3.6        理解Advisor

在配置文件中,我们通过DefaultPointcutAdvisor,这个将advicepointcut组合在一起,这就相当于一个切面的功能。在Spring中,一个advisor就是一个aspect的完整的模块化表示。一般地,一个advisor包括一个通知和一个切入点。

3.7       Spring aop 相关概念:

Aspect(切面),切面就是你要实现的横切功能。

Jointpoint(连接点),连接点是应用程序执行过程中插入切面的地点(执行点)。这个地点可以是方法调用,异常抛出,或者甚至是要修改的字段。切面代码在这些地方插入你的应用流程中,添加新的行为。

Advice(通知),通知是切面的实际实现。它通知应用系统新的行为。在日志例子中,日志通知(logging advice)包含了实际实现日志功能的代码,例如向日志文件中写日志。通知在连接点被插入到应用程序。

PointCut(切入点),切入点定义了通知应该应用到哪些连接点。通知可以应用到AOP框架支持的任何连接点。

Introduction(引入),引入允许你为已经存在的类添加新方法和属性。

Target Object,目标对象,即被代理的对象。

AOP ProxyAOP代理。代理是将通知应用到目标对象后创建的对象。AOP代理,AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

Weaving(织入),织入是将切面应用到目标对象从而创建一个新的代理对象的过程。切面在指定的连接点被织入到目标对象中。分为静态织入(AspectJ)和动态织入(Spring AOP)。

3.8    Spring 中的通知类型

通知类型

接口

说明

Before

org.springframework.aop.BeforeAdvice

在目标方法被调用之前调用

After

org.springframework.aop.AfterReturningAdvice

在目标方法被调用之后调用

Around

org.aopalliance.intercept.MethodInterceptor

拦截对目标方法的调用

Throws

org.springframework.aop.ThrowsAdvice

当目标方法抛出异常时调用

Introdcution

org.springframework.aop.IntroductionInterceptor

引入整个类

 

 

 

 

你可能感兴趣的:(java,cglib,spring aop,dynamic proxy)