Aspect Oriented Programming with Spring

1. 简介

AOP是与OOP不同的一种程序结构。在OOP编程中,模块的单位是class(类);然而,在AOP编程中模块的单位是aspect(切面)。也就是说,OOP关注的是类,而AOP关注的是切面。

Spring AOP是用纯Java实现的。目前,只支持方法执行级别的连接点。

Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.
Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. CGLIB is used by default if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes; business classes normally will implement one or more business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface, or where you need to pass a proxied object to a method as a concrete type.

对于AOP代理,Spring AOP默认使用JDK动态代理。这意味着任意接口都可以被代理。

Spring AOP也可以用CGLIB代理。CGLIB代理的是类,而不是接口。如果一个业务对象没有实现一个接口,那么默认用CGLIB代理。这是一种很好的实践,面向接口编程,而不是面向类;业务类通常会实现一个或者多个业务接口。可以强制使用CGLIB代理,这种情况下你需要通知一个方法而不是一个接口,你需要传递一个代理对象而不是一个具体的类型给一个方法。

2. @AspectJ支持

@AspectJ是一种声明切面的方式(或者说风格),它用注解来标注标准的Java类。

2.1. 启用@AspectJ支持

autoproxying(自动代理)意味着如果Spring检测到一个Bean被一个或者多个切面通知,那么它将自动为这个Bean生成一个代理以拦截其上的方法调用,并且确保通知被执行。

可以使用XML或者Java方式来配置以支持@AspectJ。为此,你需要aspectjweaver.jar

启用@AspectJ用Java配置的方式

为了使@AspectJ生效,需要用@Configuration和@EnableAspectJAutoProxy注解

 @Configuration
@EnableAspectJAutoProxy
public class AppConfig { }

启用@AspectJ用XML配置的方式

为了使@AspectJ生效,需要用到aop:aspectj-autoproxy元素

 <aop:aspectj-autoproxy/>

2.2. 声明一个切面

下面的例子显示了定义一个最小的切面:

首先,定义一个标准的Java Bean

 <bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of aspect here as normal -->
</bean>

其次,用org.aspectj.lang.annotation.Aspect注解标注它

 package org.xyz;
import org.aspectj.lang.annotation.Aspect; @Aspect
public class NotVeryUsefulAspect { }

一个切面(PS:被@Aspect注解标注的类)可以向其它的类一样有方法和字段。这些方法可能包含切点、通知等等。

通过组件扫描的方式自动侦测切面

你可能在XML配置文件中注册一个标准的Bean作为切面,或者通过classpath扫描的方式自动侦测它,就像其它被Spring管理起来的Bean那样。为了能够在classpath下自动侦测,你需要在在切面上加@Component注解。

In Spring AOP, it is not possible to have aspects themselves be the target of advice from other aspects. The @Aspect annotation on a class marks it as an aspect, and hence excludes it from auto-proxying.

在Spring AOP中,不可能有切面自己本身还被其它的切面作为目标通知。用@Aspect注解标注一个类作为切面,因此需要将它自己本身从自动代理中排除。

什么意思呢?举个例子,比如

package com.cjs.aspect

@Aspect

@Component

public class LogAspect {

  @Pointcut("execution(* com.cjs..*(..))")

  public void pointcut() {}

}

在这个例子中,切面LogAspect所在的位置是com.cjs.aspect,而它的切入点是com.cjs下的所有的包的所类的所有方法,这其中就包含LogAspect,这是不对的,会造成循环依赖。在SpringBoot中这样写的话启动的时候就会报错,会告诉你检测到循环依赖。

2.3. 声明一个切入点

切入点是用来控制什么时候执行通知的,简单地来讲,就是什么样的方法会被拦截。Spring AOP目前只支持方法级别的连接点。

一个切入点声明由两部分组成:第一部分、由一个名称和任意参数组成的一个签名;第二部分、一个切入点表达式。

在@AspectJ注解方式的AOP中,一个切入点签名就是一个标准的方法定义,而切入点表达式则是由@Pointcut注解来指明的。

(作为切入点签名的方法的返回值类型必须是void)

下面是一个简单的例子:

 @Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature

支持的切入点标识符

  • execution  -  主要的切入点标识符
  • within  -  匹配给定的类型
  • target  -  匹配给定类型的实例
  • args  -  匹配实例的参数类型
  • @args  -  匹配参数被特定注解标记的
  • @target  -  匹配有特定注解的类
  • @within  -  匹配用指定的注解类型标注的类下的方法
  • @annotation  -  匹配带有指定的注解的方法

对于JDK代理,只有public的接口方法调用的时候才会被拦截。对于CGLIB,public和protected的方法调用将会被拦截。

组合切入点表达式

Pointcut expressions can be combined using '&&', '||' and '!'

请看下面的例子:

 @Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} @Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {} @Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

在企业应用开发过程中,你可能想要经常对一下模块应用一系列特殊的操作。我们推荐你定义一个“系统架构”层面的切面用来捕获公共的切入点。下面是一个例子:

 package com.xyz.someapp;

 import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut; @Aspect
public class SystemArchitecture { /**
* 匹配定义在com.xyz.someapp.web包或者子包下的方法
*/
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {} /**
* 匹配定义在com.xyz.someapp.service包或者子包下的方法
*/
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() {} /**
* 匹配定义在com.xyz.someapp.dao包或者子包下的方法
*/
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() {} /**
* 匹配定义在com.xyz.someapp下任意层级的service包下的任意类的任意方法
*/
@Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
public void businessService() {} /**
* 匹配定义在com.xyz.someapp.dao包下的所有类的所有方法
*/
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() {} }

execution表达式

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

通配符*表示任意字符,(..)表示任意参数

下面是一些例子

  • 任意public方法

execution(public * *(..))

  • 以set开头的任意方法

execution(* set*(..))

  • AccountService中的任意方法

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

  • service包下的任意方法

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

  • service包或者子包下的任意方法

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

  • service包下的任意连接点

within(com.xyz.service.*)

  • service包或者子包下的任意连接点

within(com.xyz.service..*)

  • 实现了AccountService接口的代理类中的任意连接点

this(com.xyz.service.AccountService)

  • 只有一个参数且参数类型是Serializable的连接点

args(java.io.Serializable)

  • 有@Transactional注解的目标对象上的任意连接点

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

  • 声明类型上有@Transactional注解的目标对象上的任意连接点

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

  • 执行方法上有@Transactional注解的任意连接点

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

  • 只有一个参数,且运行时传的参数上有@Classified注解的任意连接点

@args(com.xyz.security.Classified)

  • 名字叫tradeService的bean

bean(tradeService)

  • 名字以Service结尾的bean

bean(*Service)

2.4. 声明通知

前置通知

 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() {
// ...
} }

返回通知

 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) {
// ...
} }

异常通知

 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) {
// ...
} }

后置通知(最终通知)

 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注解来声明,第一个参数必须是ProceedingJoinPoint类型的。在通知内部,调用ProceedingJoinPoint的proceed()方法造成方法执行。

 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;
} }

如果用户明确指定了参数名称,那么这个指定的名称可以用在通知中,通过argNames参数来指定:

@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
}

3. 代理机制

Spring AOP用JDK动态代理或者CGLIB来为给定的目标对象创建代理。(无论你怎么选择,JDK动态代理都是首选)

如果目标对象实现了至少一个接口,那么JDK动态代理将会被使用。

目标对象实现的所有接口都会被代理。

如果目标对象没有实现任何接口,那么使用CGLIB代理。

如果你强制用CGLIB代理,那么下面这些问题你需要注意:

  • final方法不能被通知,因为它们无法被覆盖
  • Spring 3.2中不需要再引入CGLIB,因为它已经包含在org.springframework中了
  • Spring 4.0代理类的构造方法不能被调用两次以上

为了强制使用CGLIB代理,需要在<aop:config>中的proxy-target-class属性设置为true

<aop:config proxy-target-class="true">
<!-- other beans defined here... -->
</aop:config>

当使用@AspectJ自动代理的时候强制使用CGLIB代理,需要将<aop:aspectj-autoproxy>的proxy-target-class属性设置为true

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

3.1. 理解AOP代理

Spring AOP是基于代理的。这一点极其重要。

考虑下面的代码片段

public class SimplePojo implements Pojo {

    public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
} public void bar() {
// some logic...
}
}

如果你调用一个对象中的一个方法,并且是这个对象直接调用这个方法,那么下图所示。

public class Main {

    public static void main(String[] args) {

        Pojo pojo = new SimplePojo();

        // this is a direct method call on the 'pojo' reference
pojo.foo();
}
}

如果引用对象有一个代理,那么事情变得不一样了。请考虑下面的代码片段。

public class Main {

    public static void main(String[] args) {

        ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice()); Pojo pojo = (Pojo) factory.getProxy(); // this is a method call on the proxy!
pojo.foo();
}
}

上面的代码中,为引用对象生成了一个代理。这就意味着在引用对象上的方法调用会传到代理上的调用。

有一点需要注意,调用同一个类中的方法时,被调用的那个方法不会被代理。也就是说调用foo()的时候是拦截不到bar()的。

Example

package foo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order; @Aspect
public class ProfilingAspect { @Around("methodsToBeProfiled()")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
StopWatch sw = new StopWatch(getClass().getSimpleName());
try {
sw.start(pjp.getSignature().getName());
return pjp.proceed();
} finally {
sw.stop();
System.out.println(sw.prettyPrint());
}
} @Pointcut("execution(public * foo..*.*(..))")
public void methodsToBeProfiled(){}
}
     @Pointcut("@within(com.cjs.log.annotation.SystemControllerLog) " +
"|| @within(com.cjs.log.annotation.SystemRpcLog) " +
"|| @within(com.cjs.log.annotation.SystemServiceLog)")
public void pointcut() { }

面向切面编程 ( Aspect Oriented Programming with Spring )的更多相关文章

  1. 关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-简介

    本文翻译自Spring.NET官方文档Version 1.3.2. 受限于个人知识水平,有些地方翻译可能不准确,但是我还是希望我的这些微薄的努力能为他人提供帮助. 侵删. 简介 Aspect-Orie ...

  2. 关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-通知(Advice)API

    本文翻译自Spring.NET官方文档Version 1.3.2. 受限于个人知识水平,有些地方翻译可能不准确,但是我还是希望我的这些微薄的努力能为他人提供帮助. 侵删. 让我们看看 Spring.N ...

  3. 关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-使用工厂创建代理(Using the ProxyFactoryObject to create AOP proxies)

    本文翻译自Spring.NET官方文档Version 1.3.2. 受限于个人知识水平,有些地方翻译可能不准确,但是我还是希望我的这些微薄的努力能为他人提供帮助. 侵删. 如果你正在为你的业务模型使用 ...

  4. 关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-切入点(pointcut)API

    本文翻译自Spring.NET官方文档Version 1.3.2. 受限于个人知识水平,有些地方翻译可能不准确,但是我还是希望我的这些微薄的努力能为他人提供帮助. 侵删. 让我们看看 Spring.N ...

  5. 关于面向切面编程Aspect Oriented Programming(AOP)

    最近学到spring ,出来了一个新概念,面向切面编程,下面做个笔记,引自百度百科. Aspect Oriented Programming(AOP),面向切面编程,是一个比较热门的话题.AOP主要实 ...

  6. javascript 高阶函数 实现 AOP 面向切面编程 Aspect Oriented Programming

    AOP的主要作用是吧一些跟核心业务逻辑模块无关的功能 -日志统计, 安全控制, 异常处理- 抽离出来, 再通过"动态织入"的方式掺入业务逻辑模块中. 这里通过扩展Function. ...

  7. [Spring] Aspect Oriented Programming with Spring | AOP | 切面 | 切点

    使用Spring面向切面编程 1.介绍 AOP是OOP的补充,提供了另一种关于程序结构的思路. OOP的模块化的关键单位是 类 . AOP的则是aspect切面. AOP 将程序的逻辑分成独立的块(叫 ...

  8. 程序员笔记|Spring IoC、面向切面编程、事务管理等Spring基本概念详解

    一.Spring IoC 1.1 重要概念 1)控制反转(Inversion of control) 控制反转是一种通过描述(在java中通过xml或者注解)并通过第三方去产生或获取特定对象的方式. ...

  9. Spring之 Aspect Oriented Programming with Spring

    1. Concepts Aspect-Oriented Programming (AOP) complements OOP by providing another way of thinking a ...

随机推荐

  1. Kali Linux安装字典StarDict

     Kali Linux安装字典StarDictStartDict是国外知名的字典框架,也可以加入国内翻译工具的字典.Kali Linux软件源提供该字典框架.用户需要安装qstardict软件包和词库 ...

  2. java int数组任何数之间间隔不能对于指定数,内付极速排序

    public static void main(String[] args) { int []arr = {300,310, 210,313,334,360,255,233,275,274,410,5 ...

  3. 关于各种工具输入参数中"-"和"--"

    关于各种工具输入参数中"-"和"--" 写个随笔记录下来 一直搞不懂,为啥在使用很多工具的时候,他的参数要加的"-"数量不一样呢? 如果输入 ...

  4. BZOJ5304 : [Haoi2018]字串覆盖

    离线处理所有询问. 对于$r-l\leq 50$的情况: 按照串长从$1$到$51$分别把所有子串按照第一位字符为第一关键字,上一次排序结果为第二关键字进行$O(n)$基数排序. 同理也可以用上一次比 ...

  5. centos7安装tomcat8.5

    1.下载 tomcat Linux 版本 tomcat 官网下载地址:http://tomcat.apache.org/download-80.cgi 百度云盘链接:链接: https://pan.b ...

  6. PeopleSoft OLE Automation error in Workbooks.Open: ObjectDoMethod: Microsoft Excel 不能访问文件

    os: WinServer 2012 R2 64位 问题描述:PeopleSoft Web端运行AE 报上图错误,AD工具直接Test正常 解决方案: 运行> dcomcnfg 这将打开组件服务 ...

  7. cf 1142 C

    忽然觉得这个题很有必要写题解. 移项 y-x^2 = bx+c 那么其实就是找有多少条直线上方没有点 所以就是一个上凸壳有多少条直线/点. 绝妙啊!!!! 太妙了啊!!!! 神乎其技卧槽!!! (我是 ...

  8. Ubuntu16.04安装之后连不上无线网?有可能是Realtek rtl8822be的原因

    原以为昨天已基本写完在接触到Ubuntu以来遇到的所有问题了... 没想到今天去看有关ROS的资料时,居然无意间又看到了之前遇到的一个巨坑:安装完Ubuntu16.04之后,无线网用不了,根本无法连接 ...

  9. 28 ArcMap 运行特别慢怎么办

    小编电脑配置如下: , 虽然不是太好吧,但还是满足ArcMap运行的要求的,但不知道为什么,就是很慢,终于在无意中,发现了一个位置,取消勾选以后,ArcMap变的快很多,亲测有效 取消后台处理后,Ar ...

  10. Java内存模型锦集

    [内存操作与内存屏障] 内存模型操作: lock(锁定) : 作用与主内存的变量, 它把一个变量标识为一条线程独占的状态 unlock(解锁) : 作用于主内存变量, 它把一个处于锁定状态的变量释放出 ...