Spring 动态切入点

 由于动态切入点效率十分低下,并且一般并不会使用动态切入点。因此Spring 只提供了一个动态切入点:ControlFlowPointcut 类,它指定了执行aop 的类,即只有该类调用aop 方法时,方法才会动态的织入通知,其他类调用aop 方法和普通的方法调用一样。例子如下:

     1)通知代码

Java代码 复制代码
  1. public class Before implements MethodBeforeAdvice {   
  2.   
  3.     public void before( Method method, Object[] args, Object target)throws Throwable {   
  4.         System.out.println( "");   
  5.         System.out.println( "Before.before()");   
  6.         System.out.println( "target: " + target.toString());   
  7.         System.out.println( "method name: " + method.getName());   
  8.         Type[] type = method.getGenericParameterTypes();   
  9.         for (int i = 0; i < type.length; i++) {   
  10.             System.out.println( type[i].toString() + ": " + args[i]);   
  11.         }   
  12.         System.out.println( "--end--");   
  13.         System.out.println( "");   
  14.     }     
  15. }  
public class Before implements MethodBeforeAdvice {

    public void before( Method method, Object[] args, Object target)throws Throwable {
        System.out.println( "");
        System.out.println( "Before.before()");
        System.out.println( "target: " + target.toString());
        System.out.println( "method name: " + method.getName());
        Type[] type = method.getGenericParameterTypes();
        for (int i = 0; i < type.length; i++) {
            System.out.println( type[i].toString() + ": " + args[i]);
        }
        System.out.println( "--end--");
        System.out.println( "");
    }  
}

     2)ControlFlowPointcut 的指定类

Java代码 复制代码
  1. public class Argument implements ApplicationContextAware {   
  2.   
  3.     private Implement impl;   
  4.   
  5.     public void setApplicationContext(ApplicationContext context)throws BeansException  {   
  6.         impl = (Implement)context.getBean( "aop");   
  7.     }   
  8.   
  9.     public void test() {   
  10.         System.out.println( "Argument.test()");   
  11.         impl.test();   
  12.     }   
  13.   
  14.     public void test2(Implement i) {   
  15.         i.test();      
  16.     }   
  17. }  
public class Argument implements ApplicationContextAware {

    private Implement impl;

    public void setApplicationContext(ApplicationContext context)throws BeansException  {
        impl = (Implement)context.getBean( "aop");
    }

    public void test() {
        System.out.println( "Argument.test()");
        impl.test();
    }

    public void test2(Implement i) {
        i.test();   
    }
}

     3)目标对象

Java代码 复制代码
  1. public class Target implements Implement {   
  2.   
  3.     public void test() {   
  4.         System.out.println( "Target.test()");   
  5.     }   
  6. }  
public class Target implements Implement {

    public void test() {
        System.out.println( "Target.test()");
    }
}

     4)接口定义

Java代码 复制代码
  1. public interface Implement {   
  2.   
  3.     void test();   
  4. }  
public interface Implement {

    void test();
}

     5)配置文件

Xml代码 复制代码
  1. <beans>  
  2.     <bean id="arg" class="spring.Argument" />  
  3.   
  4.     <bean id="advice" class="org.springframework.aop.support.DefaultPointcutAdvisor">  
  5.         <property name="advice">  
  6.             <bean class="spring.Before" />  
  7.         </property>  
  8.         <property name="pointcut">  
  9.             <bean class="org.springframework.aop.support.ControlFlowPointcut">  
  10.                 <constructor-arg>  
  11.                     <value>spring.Argument</value>  
  12.                 </constructor-arg>  
  13.             </bean>  
  14.         </property>  
  15.     </bean>  
  16.   
  17.     <bean id="aop" class="org.springframework.aop.framework.ProxyFactoryBean">  
  18.         <property name="proxyInterfaces">  
  19.             <list>  
  20.                 <value>spring.Implement</value>  
  21.             </list>  
  22.         </property>  
  23.         <property name="interceptorNames">  
  24.             <list>  
  25.                 <value>advice</value>  
  26.             </list>  
  27.         </property>  
  28.         <property name="target">  
  29.             <bean class="spring.Target" />  
  30.         </property>  
  31.     </bean>  
  32. </beans>  

     6)测试代码

Java代码 复制代码
  1. public static void main(String[] args) {   
  2.         ApplicationContext context = new ClassPathXmlApplicationContext( "spring.xml");   
  3.         Argument arg = (Argument)context.getBean( "arg");   
  4.         arg.test();   
  5.         System.out.println( "----------------");   
  6.         Implement impl = (Implement)context.getBean( "aop");   
  7.         impl.test();   
  8.         System.out.println( "----------------");   
  9.         arg.test2( impl);   
  10.     }  
public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext( "spring.xml");
        Argument arg = (Argument)context.getBean( "arg");
        arg.test();
        System.out.println( "----------------");
        Implement impl = (Implement)context.getBean( "aop");
        impl.test();
        System.out.println( "----------------");
        arg.test2( impl);
    }

 ()

1.概念

Spring的切入点模型能够使切入点独立于通知类型被重用。 同样的切入点有可能接受不同的通知。

org.springframework.aop.Pointcut接口是重要的接口,用来指定通知到特定的类和方法目标,完整的接口定义如下。

public interface Pointcut {

 

    ClassFilter getClassFilter();

 

    MethodMatcher getMethodMatcher();

 

}

Pointcut接口分成两个部分有利于重用类和方法的匹配部分,并且组合细粒度的操作(如和另一个方法匹配器执行一个“并”的操作)。

ClassFilter接口被用来将切入点限制到一个给定的目标类的集合。如果matches()永远返回true,所有的目标类都将被匹配。

public interface ClassFilter {

 

    boolean matches(Class clazz);

}

MethodMatcher接口通常更加重要,完整的接口如下。

public interface MethodMatcher {

 

    boolean matches(Method m, Class targetClass);

 

    boolean isRuntime();

 

    boolean matches(Method m, Class targetClass, Object[] args);

}

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

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

如果可能,尽量使切入点是静态的,使当AOP代理被创建时,AOP框架能够缓存切入点的测试结果。

2.切入点的运算

Spring支持的切入点的运算有

并表示只要任何一个切入点匹配的方法。交表示两个切入点都要匹配的方法。并通常比较有用。

切入点可以用org.springframework.aop.support.Pointcuts类的静态方法来组合,或者使用同一个包中的ComposablePointcut类。

3.实用切入点实现

Spring提供几个实用的切入点实现,一些可以直接使用,另一些需要子类化来实现应用相关的切入点。

1)静态切入点

静态切入点只基于方法和目标类,而不考虑方法的参数。静态切入点足够满足大多数情况的使用。Spring可以只在方法第一次被调用的时候计算静态切入点,不需要在每次方法调用的时候计算。

让我们看一下Spring提供的一些静态切入点的实现。

正则表达式切入点

一个很显然的指定静态切入点的方法是正则表达式。除了Spring以外,其他的AOP框架也实现了这一点。org.springframework.aop.support.RegexpMethodPointcut是一个通用的正则表达式切入点,它使用Perl 5的正则表达式的语法。

使用这个类你可以定义一个模式的列表。如果任何一个匹配,那个切入点将被计算成true(所以,结果相当于是这些切入点的并集)。

用法如下。

<bean id="settersAndAbsquatulatePointcut"

    class="org.springframework.aop.support.RegexpMethodPointcut">

    <property name="patterns">

        <list>

            <value>.*get.*</value>

            <value>.*absquatulate</value>

        </list>

    </property>

</bean>

RegexpMethodPointcut一个实用子类,RegexpMethodPointcutAdvisor允许我们同时引用一个通知(通知可以是拦截器、before通知、throws通知等)。这简化了bean的装配,因为一个bean可以同时当作切入点和通知,如下所示。

<bean id="settersAndAbsquatulateAdvisor"

    class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">

    <property name="interceptor">

        <ref local="beanNameOfAopAllianceInterceptor"/>

    </property>

    <property name="patterns">

        <list>

            <value>.*get.*</value>

            <value>.*absquatulate</value>

        </list>

    </property>

</bean>

RegexpMethodPointcutAdvisor可以用于任何通知类型。RegexpMethodPointcut类需要Jakarta ORO正则表达式包。

属性驱动的切入点

一类重要的静态切入点是元数据驱动的切入点。它使用元数据属性的值,典型地,使用源代码级元数据。

2)动态切入点

动态切入点的演算代价比静态切入点高得多。它们不仅考虑静态信息,还要考虑方法的参数。这意味着它们必须在每次方法调用的时候都被计算,并且不能缓存结果,因为参数是变化的。

控制流切入点

Spring的控制流切入点概念上和AspectJcflow切入点一致,虽然没有其那么强大(当前没有办法指定一个切入点在另一个切入点后执行)。一个控制流切入点匹配当前的调用栈。例如,连接点被com.mycompany.web包或者SomeCaller类中一个方法调用的时候,触发该切入点。控制流切入点的实现类是org.springframework.aop.support.ControlFlowPointcut

4.切入点超类

Spring提供非常实用的切入点的超类帮助你实现你自己的切入点。

因为静态切入点非常实用,你很可能子类化StaticMethodMatcherPointcut,如下所示。 这只需要实现一个抽象方法(虽然可以改写其他的方法来自定义行为)。

class TestStaticPointcut extends StaticMethodMatcherPointcut {

 

    public boolean matches(Method m, Class targetClass) {

        // return true if custom criteria match

    }

}

当然也有动态切入点的超类。

Spring 1.0 RC2或以上版本,自定义切入点可以用于任何类型的通知。

5.自定义切入点

因为Spring中的切入点是Java类,而不是语言特性(如AspectJ),因此可以定义自定义切入点,无论静态还是动态。但是,没有直接支持用AspectJ语法书写的复杂的切入点表达式。不过,Spring的自定义切入点也可以任意的复杂。

()

1.通知的生命周期

Spring的通知可以跨越多个被通知对象共享,或者每个被通知对象有自己的通知。这分别对应per-classper-instance通知。

Per-class通知使用最为广泛。它适合于通用的通知,如事务adisor。它们不依赖被代理的对象的状态,也不添加新的状态。它们仅仅作用于方法和方法的参数。

Per-instance通知适合于导入,来支持混入(mixin)。在这种情况下,通知添加状态到被代理的对象。

可以在同一个AOP代理中混合使用共享和per-instance通知。

2Spring中通知类型

Spring提供几种现成的通知类型并可扩展提供任意的通知类型。让我们看看基本概念和标准的通知类型。

1Interception around advice

Spring中最基本的通知类型是interception around advice

Spring使用方法拦截器的around通知是和AOP联盟接口兼容的。实现around通知的类需要实现接口MethodInterceptor

public interface MethodInterceptor extends Interceptor {

 

    Object invoke(MethodInvocation invocation) throws Throwable;

}

invoke()方法的MethodInvocation参数暴露将被调用的方法、目标连接点、AOP代理和传递给被调用方法的参数。invoke()方法应该返回调用的结果:连接点的返回值。

一个简单的MethodInterceptor实现看起来如下。

public class DebugInterceptor implements MethodInterceptor {

 

    public Object invoke(MethodInvocation invocation) throws Throwable {

        System.out.println("Before: invocation=[" + invocation + "]");

        Object rval = invocation.proceed();

        System.out.println("Invocation returned");

        return rval;

    }

}

注意MethodInvocationproceed()方法的调用。这个调用会应用到目标连接点的拦截器链中的每一个拦截器。大部分拦截器会调用这个方法,并返回它的返回值。但是,一个MethodInterceptor,和任何around通知一样,可以返回不同的值或者抛出一个异常,而不调用proceed方法。但是,没有好的原因你要这么做。

MethodInterceptor提供了和其他AOP联盟的兼容实现的交互能力。这一节下面要讨论的其他的通知类型实现了AOP公共的概念,但是以Spring特定的方式。虽然使用特定通知类型有很多优点,但如果你需要在其他的AOP框架中使用,请坚持使用MethodInterceptor around通知类型。注意,目前切入点不能和其他框架交互操作,并且AOP联盟目前也没有定义切入点接口。

2Before通知

Before通知是一种简单的通知类型。 这个通知不需要一个MethodInvocation对象,因为它只在进入一个方法前被调用。

Before通知的主要优点是它不需要调用proceed()方法,因此没有无意中忘掉继续执行拦截器链的可能性。

MethodBeforeAdvice接口如下所示(SpringAPI设计允许成员变量的Before通知,虽然一般的对象都可以应用成员变量拦截,但Spring有可能永远不会实现它)。

public interface MethodBeforeAdvice extends BeforeAdvice {

 

    void before(Method m, Object[] args, Object target) throws Throwable;

}

注意,返回类型是voidBefore通知可以在连接点执行之前插入自定义的行为,但是不能改变返回值。如果一个Before通知抛出一个异常,这将中断拦截器链的进一步执行。这个异常将沿着拦截器链后退着向上传播。如果这个异常是unchecked的,或者出现在被调用的方法的签名中,它将会被直接传递给客户代码;否则,它将被AOP代理包装到一个unchecked的异常里。

下面是Spring中一个Before通知的例子,这个例子计数所有正常返回的方法。

public class CountingBeforeAdvice implements MethodBeforeAdvice {

    private int count;

    public void before(Method m, Object[] args, Object target) throws Throwable {

        ++count;

    }

 

    public int getCount() {

        return count;

    }

}

Before通知可以被用于任何类型的切入点。

3Throws通知

如果连接点抛出异常,Throws通知在连接点返回后被调用。Spring提供强类型的Throws通知。注意,这意味着org.springframework.aop.ThrowsAdvice接口不包含任何方法,它是一个标记接口,标识给定的对象实现了一个或多个强类型的Throws通知方法。这些方法形式如下。

afterThrowing([Method], [args], [target], subclassOfThrowable)

只有最后一个参数是必需的。这样从1个参数到4个参数,依赖于通知是否对方法和方法的参数感兴趣。下面是Throws通知的例子。

如果抛出RemoteException异常(包括子类),这个通知会被调用。

public  class RemoteThrowsAdvice implements ThrowsAdvice {

 

    public void afterThrowing(RemoteException ex) throws Throwable {

        // Do something with remote exception

    }

}

如果抛出ServletException异常,下面的通知会被调用。和上面的通知不一样,它声明了4个参数,所以它可以访问被调用的方法、方法的参数和目标对象。

public static class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

 

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {

        // Do something will all arguments

    }

}

最后一个例子演示了如何在一个类中使用两个方法来同时处理RemoteExceptionServletException异常。任意个数的throws方法可以被组合在一个类中。

public static class CombinedThrowsAdvice implements ThrowsAdvice {

 

    public void afterThrowing(RemoteException ex) throws Throwable {

        // Do something with remote exception

    }

 

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {

        // Do something will all arguments

    }

}

Throws通知可被用于任何类型的切入点。

4After Returning通知

Spring中的After Returning通知必须实现org.springframework.aop.AfterReturningAdvice 接口,如下所示。

public interface AfterReturningAdvice extends Advice {

 

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)

            throws Throwable;

}

After Returning通知可以访问返回值(不能改变)、被调用的方法、方法的参数和目标对象。

下面的After Returning通知统计所有成功的没有抛出异常的方法调用。

public class CountingAfterReturningAdvice implements AfterReturningAdvice {

    private int count;

 

    public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable {

        ++count;

    }

 

    public int getCount() {

        return count;

    }

}

这方法不改变执行路径。如果它抛出一个异常,这个异常而不是返回值将被沿着拦截器链向上抛出。

After returning通知可被用于任何类型的切入点。

5Introduction通知

SpringIntroduction通知看作一种特殊类型的拦截通知。

Introduction需要实现IntroductionAdvisorIntroductionInterceptor接口。

public interface IntroductionInterceptor extends MethodInterceptor {

 

    boolean implementsInterface(Class intf);

}

继承自AOP联盟MethodInterceptor接口的invoke()方法必须实现导入,也就是说,如果被调用的方法是在导入的接口中,导入拦截器负责处理这个方法调用,它不能调用proceed()方法。

Introduction通知不能被用于任何切入点,因为它只能作用于类层次上,而不是方法。你可以只用InterceptionIntroductionAdvisor来实现导入通知,它有下面的方法。

public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor {

 

    ClassFilter getClassFilter();

 

    IntroductionInterceptor getIntroductionInterceptor();

 

    Class[] getInterfaces();

}

这里没有MethodMatcher,因此也没有和导入通知关联的切入点,只有类过滤是合乎逻辑的。

getInterfaces()方法返回advisor导入的接口。

让我们看看一个来自Spring测试套件中的简单例子。我们假设想要导入下面的接口到一个或者多个对象中。

public interface Lockable {

    void lock();

    void unlock();

    boolean locked();

}

在这个例子中,我们想要能够将被通知对象类型转换为Lockable,不管它们的类型,并且调用lockunlock方法。如果我们调用lock()方法,我们希望所有setter方法抛出LockedException异常。这样我们能添加一个方面使对象不可变,而它们不需要知道这一点,这是一个很好的AOP例子。

首先,我们需要一个做大量转化的IntroductionInterceptor。在这里,我们继承org.spring framework.aop.support.DelegatingIntroductionInterceptor实用类。我们可以直接实现Introduction Interceptor接口,但是,大多数情况下,DelegatingIntroductionInterceptor是最合适的。

DelegatingIntroductionInterceptor的设计是将导入委托到真正实现导入接口的接口,隐藏完成这些工作的拦截器。委托可以使用构造方法参数设置到任何对象中,默认的委托就是自己(当无参数的构造方法被使用时)。这样,在下面的例子里,委托是DelegatingIntroduction Interceptor的子类LockMixin。给定一个委托(默认是自身)的DelegatingIntroductionInterceptor实例寻找被这个委托(而不是IntroductionInterceptor)实现的所有接口,并支持它们中任何一个导入。子类如LockMixin也可能调用suppressInterflace (Class intf)方法隐藏不应暴露的接口。然而,不管IntroductionInterceptor准备支持多少接口,IntroductionAdvisor将控制哪个接口将被实际暴露。一个导入的接口将隐藏目标的同一个接口的所有实现。

这样,LockMixin继承DelegatingIntroductionInterceptor并自己实现Lockable。父类自动选择支持导入的Lockable,所以我们不需要指定它。用这种方法我们可以导入任意数量的接口。

注意locked实例变量的使用,这有效地添加了额外的状态到目标对象。

public class LockMixin extends DelegatingIntroductionInterceptor

    implements Lockable {

 

    private boolean locked;

 

    public void lock() {

        this.locked = true;

    }

 

    public void unlock() {

        this.locked = false;

    }

 

    public boolean locked() {

        return this.locked;

    }

 

    public Object invoke(MethodInvocation invocation) throws Throwable {

        if (locked() && invocation.getMethod().getName().indexOf("set") == 0)

            throw new LockedException();

        return super.invoke(invocation);

    }

 

}

通常不要需要改写invoke()方法,实现DelegatingIntroductionInterceptor就足够了,如果是导入的方法,DelegatingIntroductionInterceptor实现会调用委托方法,否则继续沿着连接点处理。在现在的情况下,我们需要添加一个检查:在上锁状态下不能调用setter方法。

所需的导入advisor是很简单的。只有保存一个独立的LockMixin实例,并指定导入的接口,在这里就是Lockable。一个稍微复杂一点例子可能需要一个导入拦截器(可以定义成prototype)的引用:在这种情况下,LockMixin没有相关配置,所以我们简单地使用new来创建它。

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

 

    public LockMixinAdvisor() {

        super(new LockMixin(), Lockable.class);

    }

}

我们可以非常简单地使用这个advisor,它不需要任何配置(但是,有一点必要的,就是不可能在没有IntroductionAdvisor的情况下使用IntroductionInterceptor)。和导入一样,通常advisor必须是针对每个实例的,并且是有状态的。我们会有不同的LockMixinAdvisor每个被通知对象,会有不同的LockMixinadvisor组成了被通知对象的状态的一部分。

和其他advisor一样,我们可以使用Advised.addAdvisor()方法以编程地方式使用这种advisor,或者在XML中配置(推荐这种方式)。下面将讨论所有代理创建,包括“自动代理创建者”,选择代理创建以正确地处理导入和有状态的混入。

()

由于动态切入点除了要考虑方法的名称等静态信息外,还要考虑方法的参数。由于它是动态的,在执行时既要计算方法的静态信息,还要计算其参数,结果也不能被缓存。因此,动态切入点要消耗更多的系统资源。

    Spring中提供了如下几种动态切入点的实现,说明如下:

    1、ControlFlowPointcut:控制流程切入点。比如只有在某个特定的类或方法中调用某个连接点时,装备才会被触发,这时就可以使用ControlFlowPointcut.但是它的系统开销很大,在追求高效的应用中,不推荐使用。

    2、DynamicMethodMatcherPointcut:动态方法匹配器。是抽象类,扩展该类可以实现自己的动态Pointcut.

()

昨天,我们主要学习了Spring的控制反转(IOC),IOC容器把对象单独出来,使程序的层次更加分明。Spring的配置比较集中,灵活性比较好。好了,咱们继续昨天AOP的学习。

1.Annotation(注解)的方式实现AOP:

1)加入Spring的依赖库

2)采用Aspect定义切面

3) 在Aspect定义Pointcut和Advice

4)启动Aspect对Annotation的支持并且将Aspect类和目标对象配置到Ioc容器中

注意:在这种方法定义中,切入点的方法是不被执行的,它存在的目的仅仅是为了重用切入点,即Advice中通过方法名引用这个切入点

2.配置文件的方式实现AOP:

1)加入Spring的依赖库

2)配置文件

配置文件范例:

<aop:config>

       <aop:aspect id="security" ref="securityHandler">

              <aop:pointcut expression="execution(*cn.itcast.spring.UserManagerImpl.add*(..))"id="addAllMethod"/>

              <aop:before method="checkSecurity" pointcut-ref="addAllMethod"/>

       </aop:aspect>

</aop:config>

3.Aspect默认情况下不用实现接口,但对于目标对象,在默认情况下必须实现接口,如果没有实现接口必须引入CGLIB库,我们可以通过Advice中添加一个JoinPoint参数,这个值会由spring自动传入,从JoinPoint中可以取得参数值和方法名等

4.Spring对AOP的支持:

1)如果目标对象实现了接口,默认情况下采用JDK的动态代理实现AOP

2)如果目标对象实现了接口,可以强制使用CGLIB实现AOP

3)如果目标对象没有实现接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换

强制使用CGLIB实现AOP的方法步骤:首先需要添加CGLIB库,SPRING_HOME/cglib/*.jar

然后在配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

5.JDK动态代理和CGLIB字节码生成的区别:

1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类

2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值