《Spring揭秘》读书笔记 2:Spring AOP

7 一起来看AOP

2009年8月,《一起来看流星雨》开播。

2009年9月,《Spring揭秘》出版。

7.1 AOP核心概念

AOP

AOP全称为Aspect-Oriented Programming,面向方面编程。

使用AOP,我们可以对类似于日志记录和安全检查等系统需求进行模块化的组织。

AOL

与OOP需要相应的语言支持一样,AOP也需要某种语言以帮助实现相应的概念实体,我们将实现AOP的语言称为AOL,Aspect-Oriented Language。

常见的一种AOL是扩展自Java的AspectJ。

Aspect:切面

任何一个软件系统,日志记录、安全检查、事务管理等系统需求都像一把刀“恶狠狠”地横切到我们组织良好的各个业务功能模块之上,以AOP的行话来说,这些系统需求就是系统中的 横切关注点(cross-cutting concern)。AOP引入了Aspect的概念,用来以模块化的形式对系统中的横切关注点进行组织和封装。Aspect之于AOP,相当于Class之于OOP。

weave:织入

AOL实现的AOP各个概念实体,最终都需要某种方式集成到系统实现语言所实现的OOP实体组件中,所以,系统实现语言通常称为系统中使用的AOL的“寄生语言”,而将AO组件集成到OOP组件的过程,在AOP中称为织入(weave)过程。

7.2 AOP实现机制

动态代理

JDK提供了动态代理(Dynamic Proxy)机制,可以在运行期间,为相应的接口动态生成对应的代理对象。

我们可以将横切关注点逻辑封装到动态代理的InvocationHandler中,然后在系统运行期间,根据横切关注点需要织入的模块位置,将横切逻辑织入到相应的代理类中。以动态代理类为载体的横切逻辑就可以与系统其他实现模块一起工作了。

使用这种机制,所有需要织入横切关注点逻辑的模块类都得实现相应的接口,因为动态代理机制只针对接口有效。

Spring AOP默认情况下采用这种机制实现AOP机能。

动态字节码增强

通常的class文件都是从Java源代码文件使用Javac编译器编译而成的,但只要符含Java class规范,我们也可以使用ASM或者CGLIB等Java工具库,在程序运行期间,动态构建字节码的class文件。

在此前提下,我们可以为需要织入横切逻辑的模块类在运行期间,通过动态字节码增强技术,为这些系统模块类生成相应的子类,而将横切逻辑加到这些子类中,让应用程序在执行期间使用的是这些动态生成的子类,从而达到将横切逻辑织入系统的目的。

不过,如果需要扩展的类以及类中的实例方法等声明为final的话,则无法对其进行子类化的扩展。

Spring AOP在无法采用动态代理机制进行AOP功能扩展的时候,会使用CGLIB库的动态字节码增强支持来实现AOP的功能扩展。

自定义类加载器

所有的Java程序的class都要通过相应的类加载器加载到Java虚拟机之后才可以运行。我们可以通过自定义类加载器的方式完成横切逻辑到系统的织入,自定义类加载器通过读取外部文件规定的织入规则和必要信息,在加载class文件期间就可以将横切逻辑添加到系统模块类的现有逻辑中,然后将改动后的class交给Java虚拟机运行。

JBoss AOP是采用自定义类加载器的方式实现的。

AOL扩展

我们可以使用扩展过的AOL,实现任何AOP概念实体甚至OOP概念实体。

AOL扩展是最强大的一种方式,但代价是你需要重新学习一门扩展了旧有的语言的AOL。

7.3 AOP基本概念

Joinpoint

Jointpoint是可以进行织入操作的系统执行点。常见的Joinpoint类型有:

    方法调用、方法执行、构造方法调用、构造方法执行、字段设置、字段获取、异常处理执行、类初始化。

Pointcut

Pointcut是Jointpoint的表述方式。有点儿像引用。

Pointcut的表述方式有:直接指定Joinpoint所在方法名称、正则表达式、使用特定的Pointcut表述语言。使用得比较普遍的是正则表达式。

Advice

Advice是单一横切关注点逻辑的载体,它代表将会织入到Jointpoint的横切逻辑。如果将Aspect比作OOP中的Class,那么Advice就相当于Class中的Method。

按照Advice在Joinpoint位置执行时机的差异或者完成功能的不同,Advice可以分为:

1) Before Advice:在Joinpoint指定位置之前执行的Advice。

2) After Advice:在Joinpoint指定位置之后执行的Advice。又可以分为:

    After returning Advice:只有在当前Joinpoint处执行流程正常完成后才执行。

    After throwing Advice:只有在当前Jointpoint处执行过程中抛出异常情况下才执行。

    After Advice:不管Joinpoint处执行流程是正常终了还是抛出异常都会执行。

3) Around Advice:对附加其上的Joinpoint进行包裹,可以在Joinpoint之前和之后都指定相应的逻辑,甚至于中断或者忽略Joinpoint处原来程序流程的执行。我们常使用的Filter功能就是Around Advice的一种体现。

4) Introduction:可以为原有的对象添加新的特性或者行为。与之前的几种Advice类型不同,Introduction不是根据横切逻辑在Joinpoint处的执行时机来区分的,而是根据它可以完成的功能而区别于其他Advice类型。

Aspect

Aspect是对系统中的横切关注点逻辑进行模块化封装的AOP概念实体。

通常情况下,Aspect可以包含多个Pointcut以及相关Advice定义。

Weaver

Weaver是织入器,职责是完成横切关注点逻辑到系统的最终织入。

Java平台各AOP实现的织入器形式并不固定:

    Spring AOP使用一组类来完成最终的织入操作,ProxyFactory类是Spring AOP中最通用的织入器。

    AspectJ有专门的编译器,即ajc,来完成织入操作,所以ajc就是AspectJ的织入器。

    JBoss AOP采用自定义的类加载器来完成最终织入,这个自定义的类加载器就是它的织入器。

目标对象

符合Pointcut指定的条件,将被织入横切逻辑的对象。

8 Spring AOP概述及其实现机制

8.1 Spring AOP概述

Spring AOP的设计哲学简单而强大,它不打算将所有的AOP需求全部囊括在内,而是要以有限的20%的AOP支持,来满足80%的AOP需求。如果觉得Spring AOP无法满足你需要的那80%之外的需求,Spring AOP对AspectJ也提供了很好的集成,就求助于AspectJ好了。

Spring AOP的AOL是Java。

Spring AOP的实现主要是使用动态代理机制。

8.2 Spring AOP的实现机制

代理模式

代理模式通常涉及4种角色:

    ISubject:对被访问资源的抽象。

    SubjectImpl:被访问资源的具体实现类。

    SubjectProxy:被访问资源的代理实现类。

    Client:对访问者的抽象。

SubjectImpl和SubjectProxy都实现了ISubject,而SubjectProxy内部持有SubjectImpl的引用。当Client通过request()请求服务的时候,SubjectProxy将请求转发给SubjectImpl。从这个角度来说,SubjectProxy倒是有多此一举之嫌了。不过,SubjectProxy可以在转发请求的同时,对请求添加更多的访问限制,Spring AOP本质上就是采用这种代理机制实现的。

动态代理

使用静态代理机制有什么问题?

针对不一样的目标对象类型,我们要为其单独实现一个代理对象。而实际上,这些代理对象要添加的横切逻辑是一样的。

使用动态代理机制,我们可以为指定的缺口在系统运行期间动态地生成代理对象。

动态代理机制的实现主要由一个类和一个接口组成,即java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

代码示例:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.time.LocalTime;
 
public class Test {
    public static void main(String[] args) {
        ISubject subjectProxy = (ISubject) Proxy.newProxyInstance(Test.class.getClassLoader(),
                                                                  new Class[] { ISubject.class }, new RequestCtrlInvocationHandler(new SubjectImpl()));
        subjectProxy.request();
    }
}
 
interface ISubject {
    void request();
}
 
class SubjectImpl implements ISubject {
    @Override
    public void request() {
        System.out.println("ok");
    }
}
 
class RequestCtrlInvocationHandler implements InvocationHandler {
    private Object target;
 
    public RequestCtrlInvocationHandler(Object target) {
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("request")) {
            LocalTime startTime = LocalTime.MIN;
            LocalTime endTime = LocalTime.of(7, 59, 59);
            LocalTime currentTime = LocalTime.now();
            if (currentTime.isAfter(startTime) && currentTime.isBefore(endTime)) {
                System.out.println("service is not available now.");
                return null;
            }
            return method.invoke(target, args);
        }
        return null;
    }
}

动态代理的缺点是只能对实现了interface的类使用。默认情况下,如果Spring AOP发现目标实现了相应interface,则采用动态代理机制为其生成代理对象实例,而如果目标对象没有实现任何interface,Spring AOP会尝试使用一个称为CGLIB(Code Generation Library)的开源的动态字节码生成类库,为目标对象生成动态的代理对象实例。

动态字节码生成

使用动态字节码生成技术扩展对象行为的原理是,我们可以对目标对象进行继承扩展,为其生成相应的子类,而子类可以通过覆写来扩展父类的行为,只要将横切逻辑的实现放到子类中,然后让系统使用扩展后的目标对象的子类,就可以达到与代理模式相同的效果了。

但是,使用继承的方式来扩展对象定义,也不能像静态代理模式那样,为每个不同类型的目标对象都单独创建相应的扩展子类。所以我们要借助于CGLIB这样的动态字节码生成库,在系统运行期间动态地为目标对象生成相应的扩展子类。

要对目标类进行扩展,首先需要实现一个org.springframework.cglib.proxy.Callback接口,不过更多的时候,我们会直接使用扩展了Callback接口的org.springframework.cglib.proxy.MethodInterceptor接口。

代码示例:

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
 
import java.lang.reflect.Method;
import java.time.LocalTime;
 
public class Test {
    public static void main(String[] args) {
        // 通过CGLIB的Enhancer为目标对象动态地生成一个子类
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Requestable.class);
        enhancer.setCallback(new RequestCtrlCallback());
 
        Requestable proxy = (Requestable) enhancer.create();
        proxy.request();
    }
}
 
class Requestable {
    public void request() {
        System.out.println("rq in Requestable without implementint any interface");
    }
}
 
class RequestCtrlCallback implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        if (method.getName().equals("request")) {
            LocalTime startTime = LocalTime.MIN;
            LocalTime endTime = LocalTime.of(7, 59, 59);
            LocalTime currentTime = LocalTime.now();
            if (currentTime.isAfter(startTime) && currentTime.isBefore(endTime)) {
                System.out.println("service is not available now.");
                return null;
            }
            return proxy.invokeSuper(obj, args);
        }
        return null;
    }
}

使用CGLIB对类进行扩展的唯一限制是无法对final方法进行覆写。

9 Spring AOP一世

这章的方法已经过时了,大概看下就行了。

9.1 Spring AOP中的Joinpoint

在Spring AOP中,只支持方法执行(Method Execution)类型的Joinpoint。

9.2 Spring AOP中的Pointcut

org.springframework.aop.Pointcut:

public interface Pointcut {
    Pointcut TRUE = TruePointcut.INSTANCE;
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
}

ClassFilter:

public interface ClassFilter {
    ClassFilter TRUE = TrueClassFilter.INSTANCE;
    boolean matches(Class<?> var1);
}

MethodMatcher:

public interface MethodMatcher {
    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
    boolean matches(Method var1, Class<?> var2);
    boolean isRuntime();
    boolean matches(Method var1, Class<?> var2, Object... var3);
}

NameMatchMethodPointcut

名称匹配。

NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("matches");
// 匹配多个方法名
pointcut.setMappedNames(new String[]{ "matches", "isRuntime" });
// 使用 * 进行模糊匹配
pointcut.setMappedNames(new String[]{ "*match*", "ma*es" });

AbstractRegexpMethodPointcut实现类

正则匹配。

以抽象类AbstractRegexpMethodPointcut为统帅,其下设JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut两种具体实现。

JdkRegexpMethodPointcut的简单使用示例如下:

JdkregexpMethodPointcut pointcut = new JdkregexpMethodPointcut();
pointcut.setPattern(".*match.*");
pointcut.setPatterns(new String[]{ ".*match.*", ".*matches" });

AnnotationMatchingPointcut

注解匹配。

两个注解:

    @ClassLevelAnnotation:用于类层次。

    @MethodLevelAnnotation:用于方法层次。

注解的匹配规则:

1) Pointcut指定类级别的注解,匹配标注了@ClassLevelAnnotation的类中的所有方法。

AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);
// 等价于
// AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);

2) Pointcut指定方法级别的注解,仅匹配标注了@MethodLevelAnnotation的方法。

AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);

3) Pointcut同时指定类级别和方法级别的注解,仅匹配标注了@ClassLevelAnnotation的类中同时标注了@MethodLevelAnnotation的方法。

AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class, MethodLevelAnnotation.class);

ComposablePointcut

Pointcut提供了逻辑运算功能,ComposablePointcut是Spring AOP提供的Pointcut逻辑运算的实现。

9.3 Spring AOP中的Advice

Spring AOP加入了开源组织AOP Alliance,目的在于标准化AOP的使用,促进各个AOP实现产品之间的可交互性。Spring中的Advice实现遵循AOP Alliance规定的接口org.aopalliance.aop.Advice。

在Spring中,Advice按照其自身实例(instance)能否在目标对象类的所有实例中共享这一标准,可以划分为两大类:per-class类型和per-instance类型。

per-class类型的Advice

1) Before Advice。

    Before Advice实现的横切逻辑将在相应的Joinpoint之前执行。

    要在Spring中实现Before Advice,我们通常只需要实现org.springframework.aop.MethodBeforeAdvice接口即可。

2) ThrowsAdvice。

    ThrowsAdvice通常用于对系统中特定的异常情况进行监控,以统一的方式对所发生的异常进行处理。

    我们通常只需要实现org.springframework.aop.ThrowsAdvide接口即可。

3) AfterReturningAdvice。

    方法正常返回的情况下,执行AfterReturningAdvice。

    实现org.springframework.aop.AfterReturningAdvice接口即可。

    虽然AfterReturningAdvice可以访问到方法的返回值,但不能对其进行修改。

4) Around Advice。

    强大。之前提到的几种Advice能完成的事情,都不在话下。

    Spring中没有直接定义对应Around Advice的实现接口,而是直接采用AOP Alliance的标准接口org.aopalliance.intercept.MethodInterceptor。

@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation var1) throws Throwable;
}

per-instance类型的Advice

在Spring AOP中,Introduction是唯一一种per-instance型Advice。

Introduction可以在不改动目标类定义的情况下,为目标类添加新的属性以及行为。

实现 org.springframework.aop.IntroductionInterceptor。

9.4 Spring AOP中的Aspect

Advisor代表Spring中的Aspect。

Advisor通常只持有一个Pointcut和一个Advice。

Advisor分为两个分支,一个分支以org.springframework.aop.PointcutAdvisor为首,另一个分支则以org.springframework.aop.IntroductionAdvisor为首。

PointcutAdvisor分支

PointcutAdvisor常用的实现有:

1) DefaultPointcutAdvisor。

    最通用的PointcutAdvisor实现,除了不能为其指定Introduction类型的Advice,剩下的任何类型的Pointcut、任何类型的Advice都可以通过DefaultPointcutAdvisor使用。

2) NameMatchMethodPointcutAdvisor。

    与DefaultPointcutAdvisor相比,限定了可以使用的Pointcut类型为NameMatchMethodPointcut。

3) RegexpMethodPointcutAdvisor。

    与DefaultPointcutAdvisor相比,限定了可以使用的Pointcut类型为AbstractRegexpMethodPointcut。

IntroductionAdvisor分支

IntroductionAdvisor与PointcutAdvisor最本质的区别在于,IntroductionAdvisor只能应用于类级别的拦截,只能使用Introduction型的Advice。

IntroductionAdvisor只有一个默认实现DefaultIntroductionAdvisor。

9.5 Spring AOP的织入

在Spring AOP中,可以使用类org.springframework.aop.framework.ProxyFactory作为织入器。

ProxyFactory weaver = new ProxyFactory(yourTargetObject);
// 或者
// ProxyFactory weaver = new ProxyFactory();
// weaver.setTarget(task);
Advisor advisor = ...;
weaver.addAdvisor(advisor);
Object projectObject = weaver.getProxy();
// 使用projectObject

代码示例:

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.util.StopWatch;

public class Test {
    public static void main(String[] args) {
        ITask task = new MockTask();
        ProxyFactory weaver = new ProxyFactory(task);

        // 可有可无: weaver.setInterfaces(new Class[] { ITask.class });
        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
        advisor.setMappedName("execute");
        advisor.setAdvice(new PerformanceMethodInterceptor());
        weaver.addAdvisor(advisor);

        ITask proxyObject = (MockTask) weaver.getProxy();
        proxyObject.execute();
    }
}

interface ITask {
    void execute();
}

// 若不实现任何接口, 则采用CGLIB动态字节码生成
// 只需要把main方法中所有的ITask改成MockTask
class MockTask implements ITask {
    @Override
    public void execute() {
        System.out.println("task executed");
    }
}

class PerformanceMethodInterceptor implements MethodInterceptor {
    private final Log logger = LogFactory.getLog(this.getClass());

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        StopWatch watch = new StopWatch();
        try {
            watch.start();
            return invocation.proceed();
        } finally {
            watch.stop();
            if (logger.isInfoEnabled()) {
                logger.info(watch.toString());
            }
        }
    }
}

10 Spring AOP二世

10.1 @AspectJ形式的Spring AOP

编程方式织入

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import org.springframework.util.StopWatch;

public class Test {
    public static void main(String[] args) {
        AspectJProxyFactory weaver = new AspectJProxyFactory();
        weaver.setProxyTargetClass(true);
        weaver.setTarget(new Foo());
        weaver.addAspect(PerformanceTraceAspect.class);
        Foo proxy = weaver.getProxy();
        proxy.method1();
        proxy.method2();
    }
}

class Foo {
    public void method1() {
        System.out.println("method1 execution");
    }

    public void method2() {
        System.out.println("method2 execution");
    }
}

@Aspect
class PerformanceTraceAspect {
    private final Log logger = LogFactory.getLog(PerformanceTraceAspect.class);

    @Pointcut("execution(public void *.method1()) || execution(public void *.method2())")
    public void pointcutName() {
    }

    @Around("pointcutName()")
    public Object performanceTrace(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch watch = new StopWatch();
        try {
            watch.start();
            return joinPoint.proceed();
        } finally {
            watch.stop();
            if (logger.isInfoEnabled()) {
                logger.info("PT in method[" + joinPoint.getSignature().getName() + "] >>>> "
                            + watch.toString());
            }
        }
    }
}

通过自动代理织入

配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

  <!-- 这里可以基于Schema简化 -->
  <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator">
    <property name="proxyTargetClass" value="true"></property>
  </bean>

  <bean id="performanceAspect" class="com.test.PerformanceTraceAspect"/>
  <bean id="target" class="com.test.Foo"/>
</beans>

测试代码:

ApplicationContext ctx = new ClassPathXmlApplicationContext("test.xml");
Foo proxy = (Foo) ctx.getBean("target");
proxy.method1();
proxy.method2();

@AspectJ形式的Pointcut

@AspectJ形式的Pointcut声明包含两个部分:

    Pointcut Expression:Pointcut表达式。指定具体的匹配模式。

    Pointcut Signature:Pointcut签名。是一个方法定义,返回类型必须是void。

Pointcut Expression匹配模式:

1) execution。

    Spring AOP仅支持方法执行类型的Joinpoint。使用它,将帮助我们匹配拥有指定方法签名的JoinPoint。

    假设我们拥有以下类定义:

public class Foo {
    public void doSomething(String arg) { }
}

    那么可以使用如下Pointcut表达式来匹配Foo.doSomething方法:

    execution(public void Foo.doSomething(String))

    其中方法的返回类型、方法名以及参数的匹配模式是必须指定的,其他部分可以省略:

    execution(void doSomething(String))

    在execution表达式中还可以使用两种通配符:* 和 .. 。

2) within。

    within标识符只接受类型声明,它会匹配指定类型下所有的Joinpoint。不过,因为Spring AOP只支持方法级别的Joinpoint,所以在使用within指定某个类后,它将匹配指定类所声明的所有方法执行。

3) this和target。

    this指代目标对象的代理对象。

    target指代目标对象。

4) args。

    帮助我们捕捉拥有指定参数类型、指定参数数量的方法级Joinpoint,而不管该方法在什么类型中被声明。

5) @within。

    指定某种类型,只要对象标注了该类型的注解,将匹配该对象内部所有Joinpoint。

6) @target。

    如果目标对象拥有@target标识符所指定的注解类型,那么目标对象中所有的方法级别JoinPoint将被匹配。

7) @args。

    尝试检查当前方法级的Joinpoint的方法参数类型,如果该次传入的参数类型拥有@args所指定的注解,当前Joinpoint将被匹配,否则将不会被匹配。

8) @annotation。

    尝试检查系统中所有对象的所有方法级别Joinpoint。如果被检测的方法标注有@annotation标识符所指定的注解类型,那么当前方法所在的Joinpoint将被匹配。

@AspectJ形式的Advice

用于标注对应Advice定义方法的注解包括:

    @Before。

    @AfterReturning。

    @AfterThrowing。

    @After。

    @Around。标注拦截器类型的Advice。

    @DeclareParents。

10.2 基于Schema的AOP

Spring框架从1.x版本升级到2.x版本后,提倡的容器配置方式从基于DTD的XML基于Schema的XML,进一步提高了配置方式的灵活性和可扩展性。

命名空间声明

要使用基于Schema的AOP,IoC容器的配置文件应该使用基于Schema的XML,同时在文件头中增加针对AOP的命名空间声明:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

  <!-- 相关bean定义 -->

</beans>

<aop:config>

添加了aop的命名空间后,就可以使用aop命名空间的各种配置元素了。

基于Schema的AOP配置方式,针对Pointcut、Advisor以及Aspect等概念提供了独立的配置元素,所有这些配置元素都包含在统一的配置元素<aop:config>中。

<aop:config>只有一个属性proxy-target-class,对应ProxyConfig中的proxyTargetClass属性,使我们可以控制是使用基于接口的代理还是基于类的代理。

<aop:config>内部可以有三个子元素,分别是<aop:pointcut>、<aop:advisor>和<aop:aspect>。它们必须按顺序进行配置。

<aop:config proxy-target-class="false">
  <aop:pointcut/>
  <aop:advisor/>
  <aop:aspect></aop:aspect>
</aop:config>

代码示例

Java代码:

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.util.StopWatch;

public class Test {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("test.xml");
        MyTask task = (MyTask) ctx.getBean("task");
        task.doSth();
    }
}

class MyTask {
    public void doSth() {
        System.out.println("doSth");
    }
}

class SchemaBaseAspect {
    private final Logger logger = Logger.getLogger(SchemaBaseAspect.class);

    public void doBefore(JoinPoint jp) {
        if (logger.isInfoEnabled()) {
            logger.info("before method[" + jp.getSignature().getName() + "] execution.");
        }
    }

    public void doAfterReturning(JoinPoint jp) {
        if (logger.isInfoEnabled()) {
            logger.info("method[" + jp.getSignature().getName() + "] completed successfully.");
        }
    }

    public void doAfterThrowing(RuntimeException e) {
        logger.error(ExceptionUtils.getFullStackTrace(e));
    }

    public void doAfter() {
        logger.warn("release system resources, etc.");
    }

    public Object doProfile(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch watch = new StopWatch();
        try {
            watch.start();
            return pjp.proceed();
        } finally {
            watch.stop();
            if (logger.isInfoEnabled()) {
                logger.info(watch);
            }
        }
    }
}

配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

  <aop:config>
    <aop:aspect id="myAspect" ref="schemaBaseAspect" order="2">
      <aop:pointcut id="privatePointcut" expression="execution(public void *.doSth())"/>
      <aop:before pointcut-ref="privatePointcut" method="doBefore"/>
      <aop:after-returning pointcut-ref="privatePointcut" method="doAfterReturning"/>
      <aop:after-throwing pointcut-ref="privatePointcut" method="doAfterThrowing" throwing="e"/>
      <aop:after pointcut-ref="privatePointcut" method="doAfter"/>
      <aop:around pointcut-ref="privatePointcut" method="doProfile"/>
    </aop:aspect>
  </aop:config>

  <bean id="schemaBaseAspect" class="com.test.SchemaBaseAspect"/>
  <bean id="task" class="com.test.MyTask"/>
</beans>

输出:

[01-17 14:33:58] [INFO] [com.test.SchemaBaseAspect:30] before method[doSth] execution.
doSth
[01-17 14:33:58] [INFO] [com.test.SchemaBaseAspect:56] StopWatch '': running time = 11791700 ns; [] took 11791700 ns = 100%
[01-17 14:33:58] [WARN] [com.test.SchemaBaseAspect:45] release system resources, etc.
[01-17 14:33:58] [INFO] [com.test.SchemaBaseAspect:36] method[doSth] completed successfully.

11 AOP应用案例

11.1 异常处理

Java中的异常分为两类:

1) unchecked exception。

    java.lang.Error和java.lang.RuntimeException及其子类。编译器不会对这些类型的异常进行编译期检查。

    unchecked exception通常对应系统中的严重异常情况,这些情况应用程序一般无法恢复,比如数据库挂掉、网线连接断开、服务器崩溃等。

2) checked exception。

    java.lang.Exception及其子类,除去java.lang.RuntimeException分支。编译器会在编译期间对这些异常类型进行检查。

    checked exception通常用于表明系统中的某些罕见的非正常状态,要求调用发对这些非正常状态进行处理。通常是可恢复的。

unchecked exception实际上可以做的事很少,通常就是记录日志、通知相应人员。所以,这些相同的逻辑实现可以归并于一处进行处理,而不是让它们散落在系统的各处,也就是说,对于unchecked exception来说,它实际上就是一种横切关注点(cross-cutting concern)。

鉴于此,我们完全可以实现一个对应unchecked exception处理的Aspect,让其对系统中的所有可能的unchecked exception进行统一的处理。这个Aspect我们一般称为Fault Barrier。

11.2 安全检查

Spring Security。

11.3 缓存

Spring Cache。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值