切面实现了横切关注点(跨越多个应用对象的逻辑)的模块化:
在AOP术语中,切面的工作被成为通知。通知定义了切面是什么以及何时使用。
Spring切面可以应用5中类型的通知:
连接点是在应用执行过程中能够插入切面的一个点。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
切点的定义会匹配通知所要织入的一个或多个连接点。通常使用明确的类和方法名称来指定这些切点,或是利用正则表达式定义匹配的类和方法名称模式来指定这些切点。
切点用于准确定位应该在什么地方应用切面的通知。
切面是通知和切点的结合。通知和切点共同定义了关于切面的全部内容——它是什么,在何时和何处完成其他功能。
引入允许我们向现有的类添加新方法或属性,通过通知类,从而可以在无需修改现有的类的情况下,让它们具有新的行为和状态。
参见
织入是将切面应用到目标对象来创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中(注入前置、后置通知等)。
在目标对象的生命周期里有多个点可以进行织入:
Spring提供了4中各具特色的AOP支持:
Spring通知是Java编写 —— Spring所创建的通知都是用标准的Java类编写的;
Spring在运行期通知对象 —— Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入Spring AOP的切面;
Spring只支持方法连接点 —— Spring基于动态代理,所以Spring只支持方法连接点,不支持构造器和字段连接点;
Spring AOP中,需要使用AspectJ的切点表达式语言来定义切点。
Spring仅支持AspectJ切点指示器的一个子集:
AspectJ指示器 | 描述 |
execution | 用于匹配方法执行的连接点 |
within | 用于匹配指定类型内的方法执行 |
this | 用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配 |
target | 用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配 |
args | 用于匹配当前执行的方法传入的参数为指定类型的执行方法 |
@within | 用于匹配持有指定注解类型内的方法 |
@target | 用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解 |
@args | 用于匹配当前执行的方法传入的参数持有指定注解的执行 |
@annotation | 用于匹配当前执行方法持有指定注解的方法 |
bean | Spring AOP扩展的,用于匹配特定名称的Bean对象的执行方法 |
reference pointcut | 表示引用其他命名切入点 |
在Spring中尝试使用其他AspectJ指示器时,将会抛出IllegalArgumentException异常。
http://sishuok.com/forum/posts/list/281.html
Spring提供了声明式切面的选择。
Spring的AOP配置元素简化了基于POJO切面的声明:
AOP配置元素 | 功能 |
---|---|
定义一个AOP通知器 | |
定义一个AOP后置通知(不考虑被通知的方法是否执行成功) | |
定义一个AOP返回后通知 | |
定义一个AOP抛出后通知 | |
定义一个AOP环绕通知 | |
定义一个切面 | |
定义一个AOP前置通知 | |
顶层的AOP元素。大多数 |
|
定义一个切点 | |
为被通知的对象引入额外的接口,并透明地实现 |
XML Bean配置文件:
package com.springtest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.springtest.AdvisedObj;
import com.springtest.AdvisingObj;
public class SpringCoreTest{
public static void main(String[] args){
ApplicationContext ctx =
new FileSystemXmlApplicationContext("//Users/... .../beans_config.xml");
AdvisedObj advisedObj = (AdvisedObj)ctx.getBean("advised");
advisedObj.perform();
}
}
package com.springtest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AdvisedObj {
private static final Logger logger = LoggerFactory.getLogger(AdvisedObj.class);
public void perform(){
logger.info("AdvisedObj perform().");
}
}
package com.springtest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AdvisingObj {
private static final Logger logger = LoggerFactory.getLogger(AdvisingObj.class);
public void beforeExecute(){
logger.info("AdvisingObj beforeExcecute().");
}
public void afterExecute(){
logger.info("AdvisingObj afterExecute().");
}
}
00:52:24.350 [main] INFO com.springtest.AdvisingObj - AdvisingObj beforeExcecute().
00:52:24.367 [main] INFO com.springtest.AdvisedObj - AdvisedObj perform().
00:52:24.367 [main] INFO com.springtest.AdvisingObj - AdvisingObj afterExecute().
在所有的通知元素中,pointcut属性定义了通知所应用的切点。pointcut属性的值是使用AspectJ切点表达式语法所定义的切点。
使用
参考下面新的通知方法,它使用了ProceedingJoinPoint作为方法的参数。这个对象能让我们在通知里调用被通知方法。通知方法可以完成任何它所需要做的事情,如果希望把控制转给被通知的方法时,可以调用ProceedingJoinPoint的proceed方法。
必须调用proceed方法,如果忘记,通知将会阻止被通知的方法的调用。还可以在通知里多次调用被通知的方法。
示例:
package com.springtest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AdvisingObj {
private static final Logger logger = LoggerFactory.getLogger(AdvisingObj.class);
public void beforeExecute(){
logger.info("AdvisingObj beforeExcecute().");
}
public void afterExecute(){
logger.info("AdvisingObj afterExecute().");
}
public void roundExecute(ProceedingJoinPoint joinpoint){
try{
logger.info("AdvisingObj before roundExecute().");
joinpoint.proceed();
logger.info("AdvisingObj after roundExecute().");
}catch(Throwable t){
}
}
}
01:36:00.808 [main] INFO com.springtest.AdvisingObj - AdvisingObj before roundExecute().
01:36:00.825 [main] INFO com.springtest.AdvisedObj - AdvisedObj perform().
01:36:00.825 [main] INFO com.springtest.AdvisingObj - AdvisingObj after roundExecute().
有时候通知并不仅仅是对方法进行简单包装,还需要校验传递给方法的参数值,这时候为通知传递参数就非常有用了。
通过配置实现将被通知方法的参数传递给通知,如下指定String参数,然后在args参数中标识了将parameter作为参数。
示例:
package com.springtest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.springtest.AdvisedObj;
import com.springtest.AdvisingObj;
public class SpringCoreTest{
public static void main(String[] args){
ApplicationContext ctx =
new FileSystemXmlApplicationContext("//Users/... .../beans_config.xml");
AdvisedObj advisedObj = (AdvisedObj)ctx.getBean("advised");
advisedObj.perform("TestParameter");
}
}
package com.springtest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AdvisedObj {
private static final Logger logger = LoggerFactory.getLogger(AdvisedObj.class);
public void perform(String parameter){
logger.info("AdvisedObj perform(), parameter is {}.", parameter);
}
}
package com.springtest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AdvisingObj {
private static final Logger logger = LoggerFactory.getLogger(AdvisingObj.class);
public void beforeExecute(){
logger.info("AdvisingObj beforeExcecute().");
}
public void afterExecute(){
logger.info("AdvisingObj afterExecute().");
}
public void roundExecute(ProceedingJoinPoint joinpoint){
try{
logger.info("AdvisingObj before roundExecute().");
joinpoint.proceed();
logger.info("AdvisingObj after roundExecute().");
}catch(Throwable t){
}
}
public void parameterExecute(String parameter){
logger.info("AdvisingObj parameterExecute(), parameter is {}.",parameter);
}
}
利用被称为引入的AOP概念,切面也可以为Spring Bean添加新方法。
利用
示例:
添加一个新的接口及实现类:
package com.springtest;
public interface ExtendedInterface {
void extendedExecute();
}
package com.springtest;
import com.springtest.ExtendedInterface;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExtendedObj implements ExtendedInterface{
private static final Logger logger = LoggerFactory.getLogger(ExtendedObj.class);
public void extendedExecute(){
logger.info("ExtendedObj extendedExecute().");
}
}
package com.springtest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.springtest.AdvisedObj;
import com.springtest.AdvisingObj;
import com.springtest.ExtendedInterface;;
public class SpringCoreTest{
public static void main(String[] args){
ApplicationContext ctx =
new FileSystemXmlApplicationContext("//Users/wongrobin/all/projects/java/javaweb/ssh/SpringCoreTest/beans_config.xml");
//AdvisedObj advisedObj = (AdvisedObj)ctx.getBean("advised");
//advisedObj.perform("TestParameter");
ExtendedInterface advisedObj = (ExtendedInterface)ctx.getBean("advised");
advisedObj.extendedExecute();
}
}
使用注解来创建切面是AspectJ5所引入的关键特性。AspectJ面向注解的模型可以非常简单地通过少量注解把任意类转换为切面(不需要使用Java语言扩展)。这种新特性通常称为@AspectJ。
示例:
package com.springtest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AspectJAnnotationAdvisedObj{
private static final Logger logger = LoggerFactory.getLogger(AspectJAnnotationAdvisedObj.class);
public void perform(){
logger.info("AspectJAnnotationAdvisedObj perform().");
}
}
package com.springtest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AspectJAnnotationAdvisingObj {
private static final Logger logger = LoggerFactory.getLogger(AspectJAnnotationAdvisingObj.class);
@Pointcut("execution(* com.springtest.AspectJAnnotationAdvisedObj.perform(..))")
public void performance() {
}
@Before("performance()")
public void beforeExecute(){
logger.info("AspectJAnnotationAdvisingObj beforeExcecute().");
}
@AfterReturning("performance()")
public void afterExecute(){
logger.info("AspectJAnnotationAdvisingObj afterExecute().");
}
}
@Before注解标识是前置通知方法,@AfterReturning注解标识是后置通知方法。
performance()切点的名称作为参数的值赋给了所有的通知注解,以这种方式来标识每一个通知方法应该应用在哪里。
package com.springtest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.springtest.AspectJAnnotationAdvisedObj;
import com.springtest.AspectJAnnotationAdvisingObj;
public class AspectJAnnotationTest {
public static void main(String[] args){
ApplicationContext ctx =
new FileSystemXmlApplicationContext("//Users/wongrobin/all/projects/java/javaweb/ssh/SpringCoreTest/annotationaspectj_config.xml");
AspectJAnnotationAdvisedObj advisedObj = (AspectJAnnotationAdvisedObj)ctx.getBean("advised");
advisedObj.perform();
}
}
使用@Around注解可以创建环绕通知;被环绕通知的方法必须接受一个ProceedingJoinPoint对象作为方法入参,并在对象上调用proceed()方法。
示例:
package com.springtest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
@Aspect
public class AspectJAnnotationAdvisingObj {
private static final Logger logger = LoggerFactory.getLogger(AspectJAnnotationAdvisingObj.class);
@Pointcut("execution(* com.springtest.AspectJAnnotationAdvisedObj.perform(..))")
public void performance() {
}
/*
@Before("performance()")
public void beforeExecute(){
logger.info("AspectJAnnotationAdvisingObj beforeExcecute().");
}
@AfterReturning("performance()")
public void afterExecute(){
logger.info("AspectJAnnotationAdvisingObj afterExecute().");
}
*/
@Around("performance()")
public void roundExecute(ProceedingJoinPoint joinpoint){
try{
logger.info("AspectJAnnotationAdvisingObj before roundExecute().");
joinpoint.proceed();
logger.info("AspectJAnnotationAdvisingObj after roundExecute().");
}catch(Throwable t){
}
}
}
02:44:04.540 [main] INFO c.s.AspectJAnnotationAdvisingObj - AspectJAnnotationAdvisingObj before roundExecute().
02:44:04.562 [main] INFO c.s.AspectJAnnotationAdvisedObj - AspectJAnnotationAdvisedObj perform().
02:44:04.562 [main] INFO c.s.AspectJAnnotationAdvisingObj - AspectJAnnotationAdvisingObj after roundExecute().
使用@AspectJ注解为通知传递参数,与Spring基于XML的切面声明没有太大区别。
示例:
package com.springtest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AspectJAnnotationAdvisedObj{
private static final Logger logger = LoggerFactory.getLogger(AspectJAnnotationAdvisedObj.class);
public void perform(String parameter){
logger.info("AspectJAnnotationAdvisedObj perform(), parameter is {}.", parameter);
}
}
package com.springtest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
@Aspect
public class AspectJAnnotationAdvisingObj {
private static final Logger logger = LoggerFactory.getLogger(AspectJAnnotationAdvisingObj.class);
@Pointcut("execution(* com.springtest.AspectJAnnotationAdvisedObj.perform(String)) && args(parameter)")
public void performance(String parameter) {
}
@Before("performance(parameter)")
public void beforeExecute(String parameter){
logger.info("AspectJAnnotationAdvisingObj beforeExcecute(), parameter is {}.", parameter);
}
@AfterReturning("performance(parameter)")
public void afterExecute(String parameter){
logger.info("AspectJAnnotationAdvisingObj afterExecute(), parameter is {}.", parameter);
}
/*
@Around("performance()")
public void roundExecute(ProceedingJoinPoint joinpoint){
try{
logger.info("AspectJAnnotationAdvisingObj before roundExecute().");
joinpoint.proceed();
logger.info("AspectJAnnotationAdvisingObj after roundExecute().");
}catch(Throwable t){
}
}
*/
}
package com.springtest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.springtest.AspectJAnnotationAdvisedObj;
import com.springtest.AspectJAnnotationAdvisingObj;
public class AspectJAnnotationTest {
public static void main(String[] args){
ApplicationContext ctx =
new FileSystemXmlApplicationContext("//Users/wongrobin/all/projects/java/javaweb/ssh/SpringCoreTest/annotationaspectj_config.xml");
AspectJAnnotationAdvisedObj advisedObj = (AspectJAnnotationAdvisedObj)ctx.getBean("advised");
advisedObj.perform("Parameter1");
}
}
03:03:19.837 [main] INFO c.s.AspectJAnnotationAdvisingObj - AspectJAnnotationAdvisingObj beforeExcecute(), parameter is Parameter1.
03:03:19.856 [main] INFO c.s.AspectJAnnotationAdvisedObj - AspectJAnnotationAdvisedObj perform(), parameter is Parameter1.
03:03:19.856 [main] INFO c.s.AspectJAnnotationAdvisingObj - AspectJAnnotationAdvisingObj afterExecute(), parameter is Parameter1.
使用基于注解的AOP为已有的Bean引入接口。等价于
示例:使用上一节的ExtendInterface和ExtendedObj:
package com.springtest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.DeclareParents;
@Aspect
public class AspectJAnnotationAdvisingObj {
private static final Logger logger = LoggerFactory.getLogger(AspectJAnnotationAdvisingObj.class);
@DeclareParents(
value="com.springtest.AspectJAnnotationAdvisedObj+",
defaultImpl=ExtendedObj.class)
public static ExtendedInterface extended;
/*
@Pointcut("execution(* com.springtest.AspectJAnnotationAdvisedObj.perform(String)) && args(parameter)")
public void performance(String parameter) {
}
@Before("performance(parameter)")
public void beforeExecute(String parameter){
logger.info("AspectJAnnotationAdvisingObj beforeExcecute(), parameter is {}.", parameter);
}
@AfterReturning("performance(parameter)")
public void afterExecute(String parameter){
logger.info("AspectJAnnotationAdvisingObj afterExecute(), parameter is {}.", parameter);
}
@Around("performance()")
public void roundExecute(ProceedingJoinPoint joinpoint){
try{
logger.info("AspectJAnnotationAdvisingObj before roundExecute().");
joinpoint.proceed();
logger.info("AspectJAnnotationAdvisingObj after roundExecute().");
}catch(Throwable t){
}
}
*/
}
package com.springtest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.springtest.AspectJAnnotationAdvisedObj;
import com.springtest.AspectJAnnotationAdvisingObj;
import com.springtest.ExtendedInterface;;
public class AspectJAnnotationTest {
public static void main(String[] args){
ApplicationContext ctx =
new FileSystemXmlApplicationContext("//Users/wongrobin/all/projects/java/javaweb/ssh/SpringCoreTest/annotationaspectj_config.xml");
/*
AspectJAnnotationAdvisedObj advisedObj = (AspectJAnnotationAdvisedObj)ctx.getBean("advised");
advisedObj.perform("Parameter1");
*/
ExtendedInterface advisedObj = (ExtendedInterface)ctx.getBean("advised");
advisedObj.extendedExecute();
}
}
03:16:30.784 [main] INFO com.springtest.ExtendedObj - ExtendedObj extendedExecute().
(该注解类AspectJAnnotationAdvisingObj需要声明为Spring应用上下文中的一个Bean。)
AspectJ提供了Spring AOP所不能支持的许多类型的切点。