面向切面编程 ( Aspect Oriented Programming with Spring )
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 )的更多相关文章
- 关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-简介
本文翻译自Spring.NET官方文档Version 1.3.2. 受限于个人知识水平,有些地方翻译可能不准确,但是我还是希望我的这些微薄的努力能为他人提供帮助. 侵删. 简介 Aspect-Orie ...
- 关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-通知(Advice)API
本文翻译自Spring.NET官方文档Version 1.3.2. 受限于个人知识水平,有些地方翻译可能不准确,但是我还是希望我的这些微薄的努力能为他人提供帮助. 侵删. 让我们看看 Spring.N ...
- 关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-使用工厂创建代理(Using the ProxyFactoryObject to create AOP proxies)
本文翻译自Spring.NET官方文档Version 1.3.2. 受限于个人知识水平,有些地方翻译可能不准确,但是我还是希望我的这些微薄的努力能为他人提供帮助. 侵删. 如果你正在为你的业务模型使用 ...
- 关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-切入点(pointcut)API
本文翻译自Spring.NET官方文档Version 1.3.2. 受限于个人知识水平,有些地方翻译可能不准确,但是我还是希望我的这些微薄的努力能为他人提供帮助. 侵删. 让我们看看 Spring.N ...
- 关于面向切面编程Aspect Oriented Programming(AOP)
最近学到spring ,出来了一个新概念,面向切面编程,下面做个笔记,引自百度百科. Aspect Oriented Programming(AOP),面向切面编程,是一个比较热门的话题.AOP主要实 ...
- javascript 高阶函数 实现 AOP 面向切面编程 Aspect Oriented Programming
AOP的主要作用是吧一些跟核心业务逻辑模块无关的功能 -日志统计, 安全控制, 异常处理- 抽离出来, 再通过"动态织入"的方式掺入业务逻辑模块中. 这里通过扩展Function. ...
- [Spring] Aspect Oriented Programming with Spring | AOP | 切面 | 切点
使用Spring面向切面编程 1.介绍 AOP是OOP的补充,提供了另一种关于程序结构的思路. OOP的模块化的关键单位是 类 . AOP的则是aspect切面. AOP 将程序的逻辑分成独立的块(叫 ...
- 程序员笔记|Spring IoC、面向切面编程、事务管理等Spring基本概念详解
一.Spring IoC 1.1 重要概念 1)控制反转(Inversion of control) 控制反转是一种通过描述(在java中通过xml或者注解)并通过第三方去产生或获取特定对象的方式. ...
- Spring之 Aspect Oriented Programming with Spring
1. Concepts Aspect-Oriented Programming (AOP) complements OOP by providing another way of thinking a ...
随机推荐
- 2019 蓝桥杯省赛 A 组模拟赛(一)-忽明忽暗
走廊里有 nn 盏灯,编号依次为 1,2,3,...,n,由学校电路控制中心管理.初始时,所有灯都是关闭的.某黑客入侵了学校电路控制中心,黑客想让灯忽明忽暗,进行了 n 轮操作.第 i 轮操作,会让所 ...
- CentOs查看某个字符串在某个目录下的行数
如果你想在当前目录下 查找"hello,world!"字符串,可以这样: grep -rn "hello,world!" ./ ./ : 表示路径为当前目录. ...
- 解决Spring boot中读取属性配置文件出现中文乱码的问题
问题描述: 在配置文件application.properties中写了 server.port=8081 server.servlet.context-path=/boy name=张三 age=2 ...
- Falsy Bouncer 过滤数组假值
过滤数组假值 (真假美猴王) 删除数组中的所有假值. 在JavaScript中,假值有false.null.0."".undefined 和NaN. function bounce ...
- [Git]2018-10 解决git cmd中文乱码问题
2018年10月12日 莫名其妙出现cmd下git log中文乱码问题,显示一堆<E4><A8>之类的乱码.git bash却一切正常. 怀疑是Windows系统升级出现的不兼 ...
- 数据分析——matplotlib
基础 # coding=utf-8 import matplotlib.pyplot as pt import numpy as np from matplotlib import font_mana ...
- Flume+Kafka+Storm+Hbase+HDSF+Poi整合
Flume+Kafka+Storm+Hbase+HDSF+Poi整合 需求: 针对一个网站,我们需要根据用户的行为记录日志信息,分析对我们有用的数据. 举例:这个网站www.hongten.com(当 ...
- 限制输入字数JS
<tr> <th><b>说明内容:</b><span id="content">(500字以内)</span> ...
- python练习题目
1.查看本机安装python版本 2.用python打印"Hello World",给出源代码和执行结果 a.命令行窗口输出(前提:python程序加入PATH系统环境变量) b. ...
- python:os.path模块常用方法
os.path模块主要用于文件的属性获取,在编程中经常用到,以下是该模块的几种常用方法.更多的方法可以去查看官方文档:http://docs.python.org/library/os.path.ht ...