Spring framework(Version 5.0.8)摘要 5,Aspect Oriented Programming With Spring

版权声明:文章撰写不易,请勿转载。 https://blog.csdn.net/alicc/article/details/82454155

                                                        喜欢的话,还可以打赏哦,以资鼓励

                                                                      

                                                                          (支付宝)             (微信)


5.1 Introduction

AOP框架是Spring的核心组件。但是Spring IoC容器并不依赖于AOP。AOP是Spirng IoC的补充,如果你不需要的话,完全可以不用。

Spring 2.0开始提供schema-base(XML schema)和@AspectJ注解两种自定义aspects的方式。

AOP在Spring Framework中用于:

  •            提供声明式的企业级服务,特别是用于替代EJB服务。最重要的应用场景是事务管理。
  •            允许用户自定义aspects,作为OOP的补充。

5.1.1 AOP 概念

  • Aspect:是对一个横跨了多个Classes的问题的模块化,比如事物处理(事物处理的操作以切面的形式插入到业务处理的流程中,这些切面的集合可以抽象成一个模块,代表一个完整的事务处理逻辑)。
  • Join point:程序执行过程中的一个切入点,它可以是一个方法的执行或一个异常的处理(我们找到一个切入点,并在此处加入我们需要的处理逻辑)。
  • Advice:即我们在切入点所加入的处理逻辑(比如在一个方法执行后打印一段日志)。它有几种类型“before”,“after”(分别代表在切入点的前面,后面)。很多AOP框架把advice抽象成interceptor(拦截器),围绕在切入点前后,形成一个拦截器链。
  • Pointcut:一个申明,用来匹配哪些才是你要找的切入点。(比如,我们声明“所有已Service结尾的类它的do方法”都需要执行日志打印,引号内的申明就是Pointcut)。Spring默认使用的是AspectJ pointcut expression。
  • Introduction:Introduction允许你为adviced对象引入新的interface,并为其提供该interface的一个实现(相当于为其添加了一个父类,使它具有父类的功能接口)。这样依赖这个adviced对象就具有了新的功能。
  • Target object:被aspect植入操作的对象。由于Spring AOP是使用运行时代理实现的,所以也可以认为是被代理对象。
  • AOP proxy:被AOP框架创建的用于植入代码的对象。在Spring中,AOP代理指JDK动态代理或CGLIB代理。
  • Weaving:代码织入。把aspect与类或对象联系起来,得到一个植入了新操作的对象。代码织入可以在编译,加载或运行时进行。Spring AOP和其他纯Java实现的AOP框架一样,都是在运行时织入代码的。

advice的类型

  • Before advice:在join point之前执行。不过这种方式无法制止代码继续执行,除非抛出异常。
  • After returning advice:在join point不抛错,并且正常执行完成之后执行。
  • After throwing advice:在join point执行抛错时执行。
  • After (finally) advice:无论正常执行亦或抛错,都执行。
  • Around advice:与前面的advice类型不同。在Around advice中,join point的执行由用户自己控制。你可以在调用join point之前,之后加入自己的代码逻辑。也可以选择调用或不调用join point。Around advice相当于对join point进行了封装,为用户提供了很大的自主权。

在advice的选择上,应该遵循最小化影响原则(够用就行)。

 

5.1.2 Spring AOP的能力和目标

Spring AOP是纯Java实现的。

Spring AOP当前只支持方法执行这种join points,不支持属性拦截。如果你需要的话,可以使用AspectJ。

Spring AOP的目标不是提供完整的AOP功能,它更倾向于通过与Spring IoC的紧密整合,来提供企业级应用中的问题。

5.1.3 AOP 代理

Spring AOP默认使用JDK动态代理机制,动态代理需要业务代码继承于某个接口。Spring AOP也支持CGLIB代理,如果业务代码不继承任何接口的情况下可以选择CGLIB。

 

5.2 @AspectJ支持

@AspectJ指的是一种使用注解声明aspects的方式。@AspectJ是由AspectJ项目引入的。Spring使用AspectJ提供的jar包解析@AspectJ注解。不过尽管使用了相同的注解,AOP仍然是纯Spring AOP,并不依赖于AspectJ编译器或代码织入。

5.2.1 启用@AspectJ支持

使用Java Configuration启用@AspectJ注解

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

使用XML Configuration启用@AspectJ注解

<?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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->

    <aop:aspectj-autoproxy/>

</beans>

不过,无论使用哪种引入方式,都需要依赖于aspectjweaver.jar包。

 

5.2.2 声明一个aspect

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of aspect here as normal -->
</bean>
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}

也可以不配置xml,而使用注解自动扫描的方式,不过因为@Aspect无法被自动扫描到,所以需要加上@Component注解。

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component
public class NotVeryUsefulAspect {

}

注意:被@Aspect标注的类,不能再作为AOP的target。

5.2.3 声明一个pointcut

一个pointcut包含两部分:一个由name和参数构成的签名(由于Spring AOP只支持基于方法执行的pointcut,所以这里的签名可以理解为方法签名),一个pointcut表达式用来标识作用于哪些方法。

@Pointcut("execution(* transfer(..))")// the pointcut expression 表达式 
private void anyOldTransfer() {}// the pointcut signature 方法签名

支持的pointcut指示符(AspectJ pointcut designators ,PCD)

PCD就是pointcut expression中的关键字,Spring支持如下关键字。

  • execution - 匹配方法执行join points
  • within - 限定匹配的类型范围
  • this - 限定AOP代理是一个指定类型的实例
  • target - 限定AOP被代理对象是一个指定类型的实例
  • args - 限定参数是指定类型
  • @target - 限定执行对象的类有一个指定类型的注解
  • @args - 限定传入的参数类有指定类型的注解
  • @within - 限定匹配的类有指定类型的注解
  • @annotation - 限定join point 方法有指定类型注解

另外Spring添加了一个bean关键字,用于限定join points为指定名称的spring bean。针对实例的PCD是Spring特有的。


pointcut表达式的结合使用

pointcut表达式可以使用“&&”,“||”,“!”。

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

上面这个例子中我们先定义了anyPublicOperation和inTrading两个pointcut,然后把anyPublicOperation和inTrading作为组件,定义了新的pointcut。


共享通用的pointcut定义

上面介绍了如何把pointcut作为组件来使用,spring推荐预定义通用的ponitcut表达式,作为共享组件。

package com.xyz.someapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class SystemArchitecture {

    /**
     * A join point is in the web layer if the method is defined
     * in a type in the com.xyz.someapp.web package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.someapp.web..*)")
    public void inWebLayer() {}

    /**
     * A join point is in the service layer if the method is defined
     * in a type in the com.xyz.someapp.service package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.someapp.service..*)")
    public void inServiceLayer() {}

    /**
     * A join point is in the data access layer if the method is defined
     * in a type in the com.xyz.someapp.dao package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.someapp.dao..*)")
    public void inDataAccessLayer() {}

    /**
     * A business service is the execution of any method defined on a service
     * interface. This definition assumes that interfaces are placed in the
     * "service" package, and that implementation types are in sub-packages.
     *
     * If you group service interfaces by functional area (for example,
     * in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then
     * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
     * could be used instead.
     *
     * Alternatively, you can write the expression using the 'bean'
     * PCD, like so "bean(*Service)". (This assumes that you have
     * named your Spring service beans in a consistent fashion.)
     */
    @Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
    public void businessService() {}

    /**
     * A data access operation is the execution of any method defined on a
     * dao interface. This definition assumes that interfaces are placed in the
     * "dao" package, and that implementation types are in sub-packages.
     */
    @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
    public void dataAccessOperation() {}

}

完整的pointcut表达式包含以下几个部分(就像正真的方法签名),除了returning type pattern,name pattern,parameters-pattern其他都是可选的。

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
            throws-pattern?)

表达式中可以使用通配符表示。比较复杂的部分是方法的参数,()代表没有参数,(..)代表任意数量的参数,(*)代表一个不限定类型的参数,(*,String)代表两个参数一个不限定类型,另一个是字符串类型。

pointcut表达式例子

表示任何public方法的执行

execution(public * *(..))

表示任何名字以set开头的方法的执行

execution(* set*(..))

表示任何AccountService接口中定义的方法的执行

execution(* com.xyz.service.AccountService.*(..))

表示包中定义的任何方法的执行

execution(* com.xyz.service.*.*(..))

表示包中的任何join point

within(com.xyz.service.*)

表示此包或其子包中的任何join point

within(com.xyz.service..*)

表示任何join point其代理实现了AccountService接口。this只适用于在Spring AOP中执行的情况。

this(com.xyz.service.AccountService)

表示任何join point其target对象实现了AccountService接口。target只适用于在Spring AOP中执行的情况。

target(com.xyz.service.AccountService)

表示任何join point其在运行时,传入的参数只有一个,且是Serializble。

注意:这个例子与execution(* *(java.io.Serializable))是不同的,execution关注的是方法签名,args关注的是运行时传入的实际参数。

args(java.io.Serializable)

表示任何join point其target对象带有@Transactional注解

@target(org.springframework.transaction.annotation.Transactional)

表示任何join point其target的类声明上带有@Transactional注解

@within(org.springframework.transaction.annotation.Transactional)

表示任何join point其运行的方法上带有@Transactional注解

@annotation(org.springframework.transaction.annotation.Transactional)

表示任何join point其只有一个参数,且运行时传入的参数类型带有@Classified注解

@args(com.xyz.security.Classified)

表示任何join point其spring bean name是tradeService

bean(tradeService)

也可以带有通配符

bean(*Service)

PCD的三种类型

PCD可以总结为三种类型:Kinded,Scoping,Contextual。

Kinded(指向特定类型的join point):execution

Scoping:within

Contextual:this,target,@annotation

5.2.4 声明advice

advice由一个pointcut表达式,和before、after、around等组成。

Before advice

这里的com.xyz.myapp.SystemArchitecture.dataAccessOperation()指的是声明好的pointcut(这种预先声明的方式在前面已经讲到过),不是实际的切入点。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

当然也可以直接使用pointcut表达式

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }

}

After ruterning advice

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

如果要对返回值进行处理,可以加上returning属性

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }

}

After throwing advice

当匹配到的方法,抛出异常时执行。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }

}

可以传入ex

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }

}

After(finally)advice

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }

}

Around advice

我们可以看到这里传入的参数就是原本要执行的业务逻辑,这样一来我们就有了更大的自由发挥的空间,可以选择是否执行它,或者在其周围加上自定义的代码。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("com.xyz.myapp.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

}

advice parameters

获取当前的JointPoint

任何advice方法,都可以声明其第一个参数为org.aspectj.lang.JoinPoint类型的。在around advice中必须是ProceedingJoinPoint类型,它是JoinPoint的子类。JoinPoint提供了很多方法,getArgs、getThis、getTarget、getSignature、toString等等。

传参到advice方法中

我们可以使用args PCD来传入参数,下例中传入了一个名为account的参数,这样在advice方法中就可以接受到了。在这里args(account,..)有两个作用,一个是限定执行的方法必须至少有一个参数,且类型是Account;另一个是为advice传参。

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
    // ...
}

以上的例子,也可以用如下方式表示。

事先声明好一个带参数的pointcut,然后直接在advice中使用。

@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
    // ...
}

更多有趣的用法可以参考AspectJ的文档。

一个使用@annotation PCD传参的例子

先定义一个Auditable标签。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    AuditCode value();
}

然后使用@annotation去匹配

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
    AuditCode code = auditable.value();
    // ...
}

advice参数和泛型

public interface Sample<T> {
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection<T> param);
}

假设有如上泛型接口,那么对应的advice可以这样写

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Advice implementation
}

不过需要指出的是,advice不支持泛型集合Collection<T>。

决定参数名

我们可以使用argNames属性来指定pointcut表达式和advice方法之间参数的对应关系。argNames属性中的参数顺序与advice方法的参数顺序一致,argNames属性中的参数名称需要与pointcut表达式中的参数名称一致。

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code and bean
}

如果advice的第一个参数,其类型是JoinPoint,ProceedingJoinPoint,JoinPoint.StaticPart,则不需要在argNames属性中指出。

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code, bean, and jp
}

如果不指定argNames属性,Spring AOP会查找class的debug information,根据本地变量表决定参数名。如果是在AspectJ编辑器中编译则不需要debug information。

advice执行顺序

当多个advice都作用于同一个join point时,如果不指定顺序,那么执行顺序是不确定的。你可以通过优先级来控制执行顺序,advice根据优先级决定执行顺序。在进入join point时,优先级最高的先执行;在离开join point时,优先级最高的后执行。控制优先级可以通过让aspect class实现org.springframwork.core.Ordered接口或使用@Order注解,order值越小,优先级越高。

注意:如果同一个aspect下面的两个advice都作用于同一个join point,这种情况下是没办法控制优先级的。所以最好考虑分拆到不同的aspect中。

5.2.5 Introductions

Introduction,允许aspect为advised对象添加一个父interface,并为advised对象提供了该interface的一个实现。

例子:

先定义一个Student类,等一下我们会为该类的实例加入新逻辑。

package com.zjc.spring.person;

import org.springframework.stereotype.Component;

@Component
public class Student {
}

准备加入的新逻辑接口

​
package com.zjc.spring;

public interface Driver {

    void drive();
}

​

接口的实现

package com.zjc.spring;

public class CarDriver implements Driver{
    @Override
    public void drive() {
        System.out.println("driving car");
    }
}

一个aspect,用于给Student加入Dirver功能

package com.zjc.spring;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {

    @DeclareParents(value = "com.zjc.spring.person.*", defaultImpl = com.zjc.spring.CarDriver.class)
    public Driver driver;
}

运行

package com.zjc.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;

@SpringBootApplication
public class Test {

    public static void main(String args[]){
        ApplicationContext context = SpringApplication.run(Test.class, args);
        Driver driver = (Driver)context.getBean("student");
        driver.drive();
    }
}

我们可以看到student可以转换成Driver类型了,具有了Driver接口的功能。

5.2.6 Aspect 实例化模型

默认情况下,aspect都是单例模式的,即在application context中每个aspect只会存在一个实例。不过Spring也支持AspectJ的perthis,pertarget模式。

@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {

    private int someState;

    @Before(com.xyz.myapp.SystemArchitecture.businessService())
    public void recordServiceUsage() {
        // ...
    }

}

perthis表示spring会为每个符合条件的代理对象创建一个aspect实例。

5.3 Schema-based AOP支持

要使用aop namespace标签,你需要引入spring-aop schema,就像下面这样。

<?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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->

</beans>

注意:在使用<aop:config>配置模式的情况下,不要启用<aop:aspectj-autoproxy/>,否则会出问题。

5.3.1 声明一个aspect

<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
    ...
</bean>

5.3.2 声明一个pointcut

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

</aop:config>

也可以把声明指向一个预先声明好的pointcut

<aop:config>

    <aop:pointcut id="businessService"
        expression="com.xyz.myapp.SystemArchitecture.businessService()"/>

</aop:config>

也可以在aspect中声明pointcut

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        ...

    </aop:aspect>

</aop:config>

结合before advice使用

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) &amp;&amp; this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...

    </aop:aspect>

</aop:config>

结合多个pointcut表达式,在这里&&、||、!是不适用的,需要用and、or、not替代。

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service..(..)) and this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...
    </aop:aspect>
</aop:config>

5.3.3 声明advice

Before advice

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...

</aop:aspect>
<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
        method="doAccessCheck"/>

    ...

</aop:aspect>

After returning advice

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...

</aop:aspect>

可以带上返回值

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        returning="retVal"
        method="doAccessCheck"/>

    ...

</aop:aspect>
public void doAccessCheck(Object retVal) {...

After throw advice

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        method="doRecoveryActions"/>

    ...

</aop:aspect>

也可以把异常作为参数传入advice方法

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        throwing="dataAccessEx"
        method="doRecoveryActions"/>

    ...

</aop:aspect>
public void doRecoveryActions(DataAccessException dataAccessEx) {...

After (finally) advice

<aop:aspect id="afterFinallyExample" ref="aBean">

    <aop:after
        pointcut-ref="dataAccessOperation"
        method="doReleaseLock"/>

    ...

</aop:aspect>

Around advice

<aop:aspect id="aroundExample" ref="aBean">

    <aop:around
        pointcut-ref="businessService"
        method="doBasicProfiling"/>

    ...

</aop:aspect>
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // start stopwatch
    Object retVal = pjp.proceed();
    // stop stopwatch
    return retVal;
}

Advice parameters

业务类

package x.y.service;

public interface FooService {

    Foo getFoo(String fooName, int age);
}

public class DefaultFooService implements FooService {

    public Foo getFoo(String name, int age) {
        return new Foo(name, age);
    }
}

aspect类

package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class SimpleProfiler {

    public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
        StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
        try {
            clock.start(call.toShortString());
            return call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
    }
}

配置

<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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- this is the object that will be proxied by Spring's AOP infrastructure -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- this is the actual advice itself -->
    <bean id="profiler" class="x.y.SimpleProfiler"/>

    <aop:config>
        <aop:aspect ref="profiler">

            <aop:pointcut id="theExecutionOfSomeFooServiceMethod"
                expression="execution(* x.y.service.FooService.getFoo(String,int))
                and args(name, age)"/>

            <aop:around pointcut-ref="theExecutionOfSomeFooServiceMethod"
                method="profile"/>

        </aop:aspect>
    </aop:config>

</beans>

启动类

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.FooService;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
        FooService foo = (FooService) ctx.getBean("fooService");
        foo.getFoo("Pengo", 12);
    }
}

运行结果

StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0
-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  ?  execution(getFoo)

5.3.4 Introductions

<aop:aspect id="usageTrackerAspect" ref="usageTracking">

    <aop:declare-parents
        types-matching="com.xzy.myapp.service.*+"
        implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
        default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>

    <aop:before
        pointcut="com.xyz.myapp.SystemArchitecture.businessService()
            and this(usageTracked)"
            method="recordUsage"/>

</aop:aspect>

5.3.5 Aspect实例化模型

在schema定义的aspects中唯一支持的实例化模式是单例模型,其他的展示不支持。

5.3.6 Advisors

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

    <aop:advisor
        pointcut-ref="businessService"
        advice-ref="tx-advice"/>

</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

5.8 在Spring应用中使用AspectJ

在Spring中使用aspects,你需要引入spring-aspects.jar。

5.8.1 在Spring中使用AspectJ做领域对象的依赖注入

Spring容器可以实例化和配置那些定义在application context中的beans。也可以配置一个已经存在的对象。

Spring提供了对那些在容器之外创建的对象的支持,比如领域对象(Domain objects),他们通常由ORM工具等通过new操作创建。

@Configurable注解使一个类适用于Spring的配置,被标注的类的新实例会被Spring配置,注入依赖。

package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable
public class Account {
    // ...
}

当用@Configurable标识类时,Spring会以全限定名来命名配置这个类的新实例。

package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable("account")
public class Account {
    // ...
}

你也可以通过以上方式指定实例的名称。

另外,你可以使用@Configurable(autowire=Autowire.BY_TYPE)或@Configurable(autowire=Autowire.BY_NAME)来指定依赖注入到这个类的新实例的方式。使用@Configurable(dependencyCheck=true)来对新实例中的依赖进行检查(当所有属性配置完成后,spring会进行校验)。

注解本身并没有做什么,真正起作用的是spring-aspects.jar中的AnnotationBeanConfigurerAspect。本质上,他做了这么一件事——“当被@Configurable注解标注的类的新实例被初始化后,根据注解的属性使用Spirng来配置这个新生成的实例”。

注意:以上的表述暗示了依赖注入发生在构造函数执行完成后。如果你希望在构造函数方法体执行前注入依赖,你可以做如下配置。

@Configurable(preConstruction=true)

以上这些必须使用AspectJ的代码织入(可以通过Ant或Maven任务,也可以使用类加载期织入)。另外,你需要通过spring配置来启用AnnotationBeanConfigurerAspect,如果你使用Java based配置,那么可以通过在任一@Configuration class上添加@EnableSpringConfigured注解来启用AnnotationBeanConfigurerAspect。

@Configuration
@EnableSpringConfigured
public class AppConfig {

}

如果是xml based配置,则可以如下配置

<context:spring-configured/>

如果一个类确定要用到@Configurable的功能,为了防止它在AnnotationBeanConfigurerAspect之前创建。可以使用depends-on

<bean id="myService"
        class="com.xzy.myapp.service.MyService"
        depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">

    <!-- ... -->

</bean>

注意:如果一个类是在Spring的控制下,那么前往不要在上面加@Configurable注解,否则会有两次初始化,一次是容器发起的,一次是aspect发起的。

@Configurable对象的单元测试

@Configurable的一个目标就是使对领域对象的单元测试更简单。

多个application contexts的情况

AnnotationBeanConfigurerAspect是个AspectJ单例aspect,这意味一个classloader上只能有一个这样的aspect实例。如果你有多个applictaion context在同一个classloader上,你就需要考虑在哪里定义@EnableSpringConfigured bean,哪里放置spring-aspects.jar。

在拥有共享的parent application context的情况下,建议把@EnableSpringConfigured bean定义在parent application context上。

当servlet容器上部署了多个web-apps时,要确保每个web-app都使用自己的classloader加载spring-aspects.jar(比如,把spring-aspect.jar放到WEB-INF/lib目录下),如果放置在servlet容器的classpath下,则所有的web-app都会共用同一个aspect实例。

5.8.2 其它的AspectJ aspects

除了@Configurable aspect,spring-aspects.jar包含一个AspectJ aspect,对那些使用@Transactional标注的类和方法运行Spring的事务管理。它主要用于在Spring容器控制之外使用Spring的事务支持。

@Transactional注解由AnnotationTransactionAspect解释。(注意,你必须把注解放置在具体的实现类上,而不是父接口上。因为接口上的注解是不会被继承的)

类上的@Transactional注解为该类的所有public方法提供了事务支持。

你可以在方法上使用@Transactional注解,即使该方法是private的。

注意:Spring Framework4.2以来,spring-aspects提供了一个小的aspect支持同样的javax.transaction.Transactional注解特性。详见JtaAnnotationTransactionAspect。

如果你希望使用Spring configuration和事务管理支持,但不想用注解,spring-aspects.jar提供了抽象的aspects来支持你定义自己的pointcut定义。详见AbstractBeanConfigurerAspect 和AbstractTransactionAspect 。

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {

    public DomainObjectConfiguration() {
        setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
    }

    // the creation of a new bean (any object in the domain model)
    protected pointcut beanCreation(Object beanInstance) :
        initialization(new(..)) &&
        SystemArchitecture.inDomainModel() &&
        this(beanInstance);

}

 

猜你喜欢

转载自blog.csdn.net/alicc/article/details/82454155
今日推荐