参考代码下载github:https://github.com/changwensir/java-ee/tree/master/spring4
一、AOP简介
•
AOP(Aspect-Oriented Programming,
面向切面编程
):
是一种新的方法论
,
是对传统
OOP(Object-OrientedProgramming,
面向对象编程
)
的补充
.
•
AOP
的主要编程对象是
切面
(aspect),
而
切面模块化横切关注点
.
•
在应用
AOP
编程时
,
仍然
需要
定义
公共功能
,
但可以明确的定义这个功能在哪里
,
以什么方式应用
,
并且不必修改受影响的类
.
这样一来
横切关注点就被模块化到特殊的对象
(
切面
)
里
.
•
AOP
的好处
:
–
每个事物逻辑位于一个位置
,
代码不分散
,
便于维护和升级
–
业务模块更简洁
,
只包含核心业务代码
.
AOP 术语
•
切面
(Aspect):
横切关注点
(
跨越应用程序多个模块的功能
)
被模块化的特殊对象,把横切关注点的代码抽象到切面的类中
•
通知
(Advice):
切面必须要完成的工作,就是功能对应的方法
•
目标
(Target):
被通知的对象
•
代理
(Proxy):
向目标对象应用通知之后创建的对象
•
连接点(
J
oinpoint
):
程序
执行的某个特定位置
:如类某个方法调用前、调用后、方法抛出异常后等
。
连接点
由两个信息确定:方法表示的程序执行点;相对点表示的方位
。例如
ArithmethicCalculator#add
()
方法执行
前的连接点,执行点为
ArithmethicCalculator#add
()
;方位为该方法执行前的
位置
•
切点(
pointcut
):
每个
类都拥有多个连接点
:例如
ArithmethicCalculator
的所有方法实际上都是连接点,即
连接点是程序类中
客观存在
的事务
。
AOP
通过切点定位到特定的连接点
。类比
:连接点相当于数据库中的记录,切点相当于查询条件
。切点和连接点不是一对一的关系,一个切点匹配多个
连接点,
切点通过
org.springframework.aop.Pointcut
接口进行描述,它使用类和方法作为连接点的
查询条件
。
二、AspectJ
•
AspectJ
:
Java
社区里最完整最流行的
AOP
框架
.
•
在
Spring2.0
以上版本
中
,
可以
使用基于
AspectJ
注解或基于
XML
配置的
AOP
1).在 Spring中启用AspectJ注解支持
•
要在
Spring
应用中使用
AspectJ
注解
,
必须在
classpath
下包含
AspectJ
类库
:
aopalliance.jar
、
aspectj.weaver.jar
和
spring-aspects.jar
•
将
aop
Schema
添加到
<beans>
根元素中
.
•
要
在
SpringIOC
容器中启用
AspectJ
注解支持
,
只要
在
Bean
配置文件中定义一个空的
XML
元素
<
aop:aspectj-autoproxy
>
•
当
SpringIOC
容器侦测到
Bean
配置文件中的
<
aop:aspectj-autoproxy
>
元素时
,
会自动为与
AspectJ
切面匹配的
Bean
创建代理
.
2).用 AspectJ注解声明切面
2).用 AspectJ注解声明切面
•
要在
Spring
中声明
AspectJ
切面
,
只需要在
IOC
容器中将切面声明为
Bean
实例
.
当在
SpringIOC
容器中初始化
AspectJ
切面之后
,Spring IOC
容器就会为那些与
AspectJ
切面相匹配的
Bean
创建代理
.
•
在
AspectJ
注解中
,
切面只是一个带有
@Aspect
注解的
Java
类
.
•
通知是标注有某种注解的简单的
Java
方法
.
•
AspectJ
支持
5
种类型的通知注解
:
–
@Before:
前置通知
,
在方法执行之前执行
–
@After:
后置通知
,
在方法执行之后执行
–
@
AfterRunning
:
返回通知
,
在方法返回结果之后
执行
–
@
AfterThrowing
:
异常通知
,
在方法抛出异常之后
–
@Around:
环绕通知
,
围绕着方法执行
3).利用方法签名编写AspectJ切入点表达式
•
最典型的切入点表达式时根据方法的签名来匹配各种方法
:
–
execution
*
com.atguigu.spring.ArithmeticCalculator
.
*
(..):
匹配
ArithmeticCalculator
中声明的所有方法
,
第一个 * 代表任意修饰符及任意返回值
.
第二个 * 代表任意方法
.
..
匹配任意数量的参数
.
若目标类与接口与该切面在同一个包中
,
可以省略包名
.
–
execution
public
* ArithmeticCalculator.*(..):
匹配
ArithmeticCalculator
接口的
所有公有方法
.
–
execution
public
double ArithmeticCalculator.*(..):
匹配
ArithmeticCalculator
中
返回
double
类型数值的方法
–
execution
public
double ArithmeticCalculator.*(
double
,..):
匹配第一个参数为
double
类型的方法
,..
匹配任意数量任意类型的参数
–
execution
public
double ArithmeticCalculator.*(
double
,
double
):
匹配参数类型为
double,double
类型的方法
.
<?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:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="Spring4_AOP.aopAnnotation"/> <!--使AspectJ注解起作用:自动为匹配的类生成代理对象--> <aop:aspectj-autoproxy/> </beans>ArithmeticCalculatorImpl在参见一篇博客 http://blog.csdn.net/ochangwen/article/details/52557459
所以的类都放个这个目录下Spring4_AOP.aopAnnotation
日志切面
* 4. 编写切面类(把横切关注点的代码抽象到切面的类中):
* 4.1 一个一般的 Java 类
* 4.2 在其中添加要额外实现的功能.
*
* 5. 配置切面
* 5.1 切面必须是 IOC 中的 bean: 实际添加了 @Component 注解
* 5.2 声明是一个切面: 添加 @Aspect
* 5.3 声明通知: 即额外加入功能对应的方法!!.
@Aspect @Component public class LoggingAspect { /** * 重用切入点定义 * 定义一个方法,用于声明切入点表达式,一般地,该方法中再不需要添入其它的代码 */ @Pointcut("execution(* Spring4_AOP.aopAnnotation.*.*(..))") public void declareJoinPointerExpression() {} //1、前置通知: 在目标方法开始之前执行(就是要告诉该方法要在哪个类哪个方法前执行) //@Before("execution(public int Spring4_AOP.aopAnnotation.*.*(int ,int))") @Before("declareJoinPointerExpression()") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); Object [] args = joinPoint.getArgs(); System.out.println("The method " + methodName + " begins with " + Arrays.asList(args)); } //2、后置通知:在目标方法执行后(无论是否发生异常),执行的通知 //注意,在后置通知中还不能访问目标执行的结果!!!,执行结果需要到返回通知里访问 //@After("execution(* Spring4_AOP.aopAnnotation.*.*(..))") @After("declareJoinPointerExpression()") public void afterMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " ends"); } //无论连接点是正常返回还是抛出异常, 后置通知都会执行. 如果只想在连接点返回的时候记录日志, 应使用返回通知代替后置通知. //3、返回通知:在方法正常结束后执行的代码,返回通知是可以访问到方法的返回值的!!! //@AfterReturning(pointcut = "execution(* Spring4_AOP.aopAnnotation.*.*(..))", returning = "result") @AfterReturning(value = "declareJoinPointerExpression()", returning = "result") public void afterReturning(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " AfterReturning ends with " + result); } //4、异常通知:在目标方法出现异常 时会执行的代码,可以访问到异常对象:且可以!!指定在出现特定异常时在执行通知!!,如果是修改为nullPointerException里,只有空指针异常才会执行 // @AfterThrowing(pointcut = "execution(* Spring4_AOP.aopAnnotation.*.*(..))", throwing = "except") @AfterThrowing(value = "declareJoinPointerExpression())", throwing = "except") public void afterThrowing(JoinPoint joinPoint, Exception except){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " occurs exception " + except); } /** * 5、环绕通知 需要携带 ProceedingJoinPoint 类型的参数. * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法. * 且环绕通知必须有返回值, 返回值即为目标方法的返回值 */ // @Around("execution(* Spring4_AOP.aopAnnotation.*.*(..))") @Around("declareJoinPointerExpression()") public Object aroundMethod(ProceedingJoinPoint pjd){ Object result = null; String methodName = pjd.getSignature().getName(); try { //前置通知 System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs())); //执行目标方法 result = pjd.proceed(); //返回通知 System.out.println("The method " + methodName + " ends with " + result); } catch (Throwable e) { //异常通知 System.out.println("The method " + methodName + " occurs exception:" + e); throw new RuntimeException(e); } //后置通知 System.out.println("The method " + methodName + " ends"); return result; } }注意:
•
在
AspectJ
中
,
切入点表达式可以通过操作符
&&,||, !
结合起来
.
@Pointcut("execution(* Spring4_AOP.aopAnnotation.*.add(..)) ||execution(* Spring4_AOP.aopAnnotation.*.sub(..))") public void declareJoinPointerExpression() {}返回通知:• 无论连接点是正常返回还是抛出异常 , 后置通知都会执行 . 如果只想在连接点返回的时候记录日志 , 应使用返回通知代替后置通知 .
在返回通知中访问连接点的返回值
•
在返回通知中
,
只要将
returning
属性添加到
@
AfterReturning
注解中
,
就可以访问连接点的返回值
.
该属性的值即为用来传入返回值的参数名称
.
•
必须在通知方法的签名中添加一个
同名参数
.
在运行时
,Spring AOP
会通过这个参数传递返回值
.
•
原始的
切点
表达式需要出现在
pointcut
属性中
2).异常通知
•
只在连接点抛出异常时才执行异常通知
•
将
throwing
属性添加到
@
AfterThrowing
注解中
,
也可以访问连接点抛出的异常
.
Throwable
是所有错误和异常类的超类
.
所以在异常通知方法可以捕获到任何错误和异常
.
如果只对某种特殊的异常类型感兴趣
,
可以将参数声明为其他异常的参数类型
.
然后通知就只在抛出这个类型及其子类的异常时才被执行
3).环绕通知
•
环绕通知是所有通知类型中功能最为强大的
,
能够全面地控制连接点
.
甚至
可以控制是否执行连接点
.
•
对于环绕通知来说
,
连接点的参数类型必须是
ProceedingJoinPoint
.
它是
JoinPoint
的子接口
,
允许控制何时执行
,
是否执行连接点
.
•
在环绕通知中需要明确调用
ProceedingJoinPoint
的
proceed()
方法来执行被代理的方法
.
如果忘记这样做就会导致通知被执行了
,
但目标方法没有被执行
.
•
注意
:
环绕通知的方法需要返回目标方法执行之后的结果
,
即调用
joinPoint.proceed
();
的返回值
,
否则会出现空指针异常
@Test public void testAOPAnnotation() { //1、创建Spring的IOC的容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("Spring4_AOP/applicationContext-aop.xml"); //2、从IOC容器中获取bean的实例 ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator"); //3、使用bean int result = arithmeticCalculator.div(3, 1); System.out.println("result:" + result); }
2-1.指定切面的优先级
•
在同一个连接点上应用不止一个切面时
,
除非明确指定
,
否则它们的优先级是不确定的
.
•
切面的优先级可以通过实现
Ordered
接口或利用
@Order
注解指定
.
•
实现
Ordered
接口
,
getOrder
()
方法的返回值越小
,
优先级越高,切面出的越在前面
•
若使用
@Order
注解
,
序号出现在注解中
写一个验证切面
@Order(1) @Aspect @Component public class ValidationAspect { @Before("execution(public int Spring4_AOP.aopAnnotation.*.*(int ,int))") public void validateArgs(JoinPoint joinPoint){ System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs())); } }日志切面,使用上面的那个类,只增加Order
@Order(2) @Aspect @Component public class LoggingAspect { ...... }
2-3.重用切入点定义
•
在编写
AspectJ
切面时
,
可以直接在通知注解中书写切入点表达式
.
但同一个切点表达式可能会在多个通知中重复出现
.
•
在
AspectJ
切面中
,
可以
通过
@
Pointcut
注解将一个切入点声明成
简单的方法
.
切入点的方法体通常是空的
,
因为将切入点定义与应用程序逻辑混在一起是不合理的
.
•
切入点方法的访问控制符同时也控制着这个切入点的可见性
.
如果切入点要在多个切面*用
,
最好将它们集中在一个公共的类中
.
在这种情况下
,
它们必须被声明为
public.
在引入这个切入点时
,
必须将类名也包括在内
.
如果类没有与这个切面放在同一个包中
,
还必须包含包名
.
•
其他通知可以通过方法名称引入该切入点
.
三 、用基于 XML 的配置声明切面
•
除了使用
AspectJ
注解声明切面
,Spring
也支持在
Bean
配置文件中声明切面
.
这种声明是通过
aop
schema
中的
XML
元素完成的
.
•
正常情况下
,
基于注解的声明要优先于基于
XML
的声明
.
通过
AspectJ
注解
,
切面可以与
AspectJ
兼容
,
而基于
XML
的配置则是
Spring
专有的
.
由于
AspectJ
得到越来越多的
AOP
框架支持
,
所以以注解风格编写的切面将会有更多重用的机会
.
1).基于 XML---- 声明切面
•
当使用
XML
声明切面时
,
需要在
<beans>
根元素中导入
aop
Schema
•
在
Bean
配置文件中
,
所有的
SpringAOP
配置都必须定义在
<
aop:config
>
元素内部
.
对于每个切面而言
,
都要创建一个
<
aop:aspect
>
元素来为具体的切面实现引用后端
Bean
实例
.
•
切面
Bean
必须有一个标示符
,
供
<
aop:aspect
>
元素引用
2).基于 XML---- 声明切入点
•
切入点使用
<
aop:pointcut
>
元素声明
•
切入点必须定义在
<
aop:aspect
>
元素下
,
或者直接定义在
<
aop:config
>
元素下
.
–
定义在
<
aop:aspect
>
元素下
:
只对当前切面有效
–
定义在
<
aop:config
>
元素下
:
对所有切面都有效
•
基于
XML
的
AOP
配置不允许在切入点表达式中用名称引用其他切入点
.
3).基于 XML---- 声明通知
•
在
aop
Schema
中
,
每种通知类型都对应一个特定的
XML
元素
.
•
通知元素需要使用
<
pointcut
-ref>
来引用切入点
,
或用
<
pointcut
>
直接嵌入切入点表达式
. method
属性指定切面类中通知方法的名称
.
4).声明引入
•
可以利用
<aop:declare-parents>
元素在切面内部声明引入
ArithmeticCalulator2与上面ArithmeticCalulator接口内容 一样
public class ArithmeticCalculatorImplXML implements ArithmeticCalculator2 { public int add(int i, int j) { return i + j; } public int sub(int i, int j) { return i - j; } public int mul(int i, int j) { return i * j; } public int div(int i, int j) { return i / j; } }
public class LoggingAspectXML { public void beforeMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); Object [] args = joinPoint.getArgs(); System.out.println("The method " + methodName + " begins with " + Arrays.asList(args)); } public void afterMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " ends"); } public void afterReturning(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " ends with " + result); } public void afterThrowing(JoinPoint joinPoint, Exception e){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " occurs excetion:" + e); } }
public class ValidationAspectXML { public void validateArgs(JoinPoint joinPoint){ System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs())); } }
<?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 --> <bean id="arithmeticCalculatorXML" class="Spring4_AOP.aopXML.ArithmeticCalculatorImplXML"> </bean> <!-- 配置切面的 bean. --> <bean id="loggingAspect" class="Spring4_AOP.aopXML.LoggingAspectXML"> </bean> <bean id="validationAspect" class="Spring4_AOP.aopXML.ValidationAspectXML"> </bean> <!-- 配置 AOP --> <aop:config> <!-- 配置切点表达式 --> <aop:pointcut id="pointcut" expression="execution(* Spring4_AOP.aopXML.ArithmeticCalculatorImplXML.*(int ,int))"/> <!-- 配置切面及通知 --> <aop:aspect ref="loggingAspect" order="2"> <aop:before method="beforeMethod" pointcut-ref="pointcut"/> <aop:after method="afterMethod" pointcut-ref="pointcut"/> <aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointcut"/> <aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pointcut"/> </aop:aspect> <aop:aspect ref="validationAspect" order="1"> <aop:before method="validateArgs" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
@Test public void testXML() { //1、创建Spring的IOC的容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("Spring4_AOP/applicationContext-xml.xml"); //2、从IOC容器中获取bean的实例 ArithmeticCalculatorImplXML arithmeticCalculator = (ArithmeticCalculatorImplXML) ctx.getBean("arithmeticCalculatorXML"); //3、使用bean int result = arithmeticCalculator.add(3, 3); System.out.println("result:" + result); arithmeticCalculator.div(10, 0); }