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. centos7.x 安装 fastDFS

    环境准备 使用的系统软件 名称 说明 centos 7.x libfatscommon FastDFS分离出的一些公用函数包 FastDFS FastDFS本体 fastdfs-nginx-modul ...

  2. TCP/IP详解 卷一学习笔记(转载)

    https://blog.csdn.net/cpcpcp123/article/details/51259498

  3. Alpha冲刺(4/10)——2019.4.26

    作业描述 课程 软件工程1916|W(福州大学) 团队名称 修!咻咻! 作业要求 项目Alpha冲刺(团队) 团队目标 切实可行的计算机协会维修预约平台 开发工具 Eclipse 团队信息 队员学号 ...

  4. Mysql学习笔记03

    Mysql 的视图 1  view  在查询中,我们经常把查询结果当成临时表来看, view 是什么? View 可以看成一张虚拟的表,是表通过某种运算得到的有一个投影. 2 如何创建视图? 创建视图 ...

  5. Python 判断文件后缀

    方法1, str的endswith方法: ims_path='data/market1501/Market-1501-v15.09.15/bounding_box_test/12312.jpg' im ...

  6. 起泡排序(Bubble sort)

    局部有序和整体有序 在由一组整数组成的序列A[0, n-1]中,满足 $ A[i - 1] \leq A[i] $ 的相邻元素称为顺序的:否则是逆序的. 扫描交换 由有序序列的特征,我们可以通过不断改 ...

  7. 关于eclipse使用thymeleaf时,提示标签不显示及后续问题的解方法

    因为thymeleaf 使用快捷键提示,不提示标签信息. 在使用网上说的的install new software安装插件的时候 报错: Unable to read repository at ht ...

  8. java testng框架的windows自动化-自动运行testng程序上篇

    本文旨在让读者简单了解testng的自动运行 怎么说呢,在网上已经有了各个前辈进行代码演示以及分享,我力争说到点子上 接上文,之前讲的大部分是juint的自动化代码运行,从未涉及到testng,但是在 ...

  9. Ubuntu14.04下如何配置固定IP

    首先用root用户登陆,然后输入你root的密码.如下图:   然后编辑interfaces文件,该文件位于/etc/network/下,执行如下命令: vim /etc/network/interf ...

  10. [CF364D]Ghd

    [CF364D]Ghd 题目大意: 有\(n(n\le10^6)\)个数\(A_{1\sim n}(A_i\le10^{12})\),从中选取\(\lceil\frac n2\rceil\)个数,使得 ...