Spring提供了4种类型的AOP支持
(1)基于代理的经典Spring AOP; 书中介绍说经典比较笨重和复杂,所以不介绍,有兴趣以后可以看看
(2)纯POJO切面;
(3)@AspectJ注解驱动的切面;
(4)注入式AspectJ切面(适用于Spring各版本)
AOP的术语
通知:切面的工作被称为通知
连接点:连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。
切点:定义了哪些连接点会得到通知,有助于缩小切面所通知的连接点的范围,定义了通知在何处工作
切面:通知和切点共同定义了切面的全部内容----它是什么,在何时何处完成其功能
织入:把切面应用到目标对象并创建新的代理对象的过程。一共有3个时期可以织入
- 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
- 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-timeweaving,LTW)就支持以这种方式织入切面。
- 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的
重点为加粗的时期。这是我们所用到的方式!!!!换图片来表示的话就是:代理类会拦截目标对象的方法,执行通知。然后再把调用转发给目标对象。
稍微了解了Spring aop,接下来开始编写
(1)切点
(2)通知
编写切点
关于切点,我们必须使用Aspectj的切点表达式语言来定义
AspectJ指示器 | 描 述 |
arg() | 限制连接点匹配参数为指定类型的执行方法 |
@args() | 限制连接点匹配参数由指定注解标注的执行方法 |
execution() | 用于匹配是连接点的执行方法 |
this() | 限制连接点匹配AOP代理的bean引用为指定类型的类 |
target | 限制连接点匹配目标对象为指定类型的类 |
@target() | 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类 型的注解 |
within() | 限制连接点匹配指定的类型 |
@within() | 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方 法定义在由指定的注解所标注的类里) |
@annotation | 限定匹配带有指定注解的连接点 |
截图提看一下切点怎么编写
切点编完了,接下来声明通知
声明通知
通知一共有5种,AspectJ提供了五个注解来定义通知
注 解 | 通 知 |
@After | 通知方法会在目标方法返回或抛出异常后调用 |
@AfterReturning | 通知方法会在目标方法返回后调用 |
@AfterThrowing | 通知方法会在目标方法抛出异常后调用 |
@Around | 通知方法会将目标方法封装起来 |
@Before | 通知方法会在目标方法调用之前执行 |
示例代码
接口:该接口的aspectStart将会被切面拦截
package com.spring.chapter4.springaop;
public interface BeNotified {
void aspectStart();
}
接口实现
package com.spring.chapter4.springaop;
import org.springframework.stereotype.Component;
@Component
public class BeNotifiedImpl implements BeNotified {
public void aspectStart() {
System.out.println("主体方法");
}
}
配置类
@EnableAspectJAtuoProxy:中启用AspectJ注解的自动代理
package com.spring.chapter4.springaop;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AspectConfig {
}
切面类:
@Aspect定义切面类,并要为这个切面类创建bean
package com.spring.chapter4.springaop;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AspectClass {
@Before("execution(* com.spring.chapter4.springaop.BeNotified.aspectStart(..))")
public void beforeNotice(){
System.out.println("主体方法运行之前");
}
@AfterReturning("execution(* com.spring.chapter4.springaop.BeNotified.aspectStart(..))")
public void fterReturningNotice(){
System.out.println("主体方法运行之后");
}
@AfterThrowing("execution(* com.spring.chapter4.springaop.BeNotified.aspectStart(..))")
public void aafterThrowingNotice(){
System.out.println("主体方法运行抛出一异常后");
}
}
测试:
package com.spring.chapter4.springaop;
import org.aspectj.lang.annotation.AfterThrowing;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AspectConfig.class)
public class AspectTest {
@Autowired
private BeNotified beNotified;
@Test
public void aspectTest(){
beNotified.aspectStart();
}
}
测试结果
主体方法运行之前
主体方法
主体方法运行之后
修改实现类,使其抛出一个异常
package com.spring.chapter4.springaop;
import org.springframework.stereotype.Component;
@Component
public class BeNotifiedImpl implements BeNotified {
public void aspectStart() {
System.out.println("主体方法");
throw new RuntimeException();
}
}
测试结果
WARNING: All illegal access operations will be denied in a future release
主体方法运行之前
主体方法
主体方法运行抛出一异常后
java.lang.RuntimeException
at com.spring.chapter4.springaop.BeNotifiedImpl.aspectStart(BeNotifiedImpl.java:9)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
XML方式:
接口:
package com.spring.chapter4.xmlaspect;
public interface BeNotified {
void aspectStart();
void aspectStart(int b);
}
实现
package com.spring.chapter4.xmlaspect;
import org.springframework.stereotype.Component;
public class BeNotifiedImpl implements BeNotified {
public void aspectStart() {
System.out.println("xmlaspect+主体方法");
}
public void aspectStart(int b) {
}
}
切面类
package com.spring.chapter4.xmlaspect;
public class AspectClass {
public void beforeNotice(){
System.out.println("xmlaspect:主体方法运行之前");
}
public void fterReturningNotice(){
System.out.println("xmlaspect:主体方法运行之后");
}
public void aafterThrowingNotice(){
System.out.println("xmlaspect:主体方法运行抛出一异常后");
}
}
xml配置文件
<?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 id="beNotified" class="com.spring.chapter4.xmlaspect.BeNotifiedImpl"/>
<bean id="aspectClass" class="com.spring.chapter4.xmlaspect.AspectClass"/>
<aop:config>
<aop:aspect ref="aspectClass">
<aop:before
pointcut="execution(* com.spring.chapter4.xmlaspect.BeNotified.aspectStart(..))"
method="beforeNotice"/>
<aop:after-returning
pointcut="execution(* com.spring.chapter4.xmlaspect.BeNotified.aspectStart(..))"
method="fterReturningNotice"/>
<aop:after-throwing
pointcut="execution(* com.spring.chapter4.xmlaspect.BeNotified.aspectStart(..))"
method="aafterThrowingNotice"/>
</aop:aspect>
</aop:config>
</beans>
补充点一:使用@Pointcut注解声明频繁使用的切点表达式
通过@Pointcut把切点表达式赋值到一个方法种,其他注解通过引用这个方法就可以引用通用的表达式
package com.spring.chapter4.springaop;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
@Component
@Aspect
@EnableAspectJAutoProxy
public class AspectClass2 {
@Pointcut("execution(* com.spring.chapter4.springaop.BeNotified.aspectStart(..))")
public void aspectStart(){}
@Before("aspectStart()")
public void beforeNotice(){
System.out.println("主体方法运行之前2");
}
@AfterReturning("aspectStart()")
public void fterReturningNotice(){
System.out.println("主体方法运行之后2");
}
@AfterThrowing("aspectStart()")
public void aafterThrowingNotice(){
System.out.println("主体方法运行抛出一异常后2");
}
}
xml方式
<?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 id="beNotified" class="com.spring.chapter4.xmlaspect.BeNotifiedImpl"/>
<bean id="aspectClass" class="com.spring.chapter4.xmlaspect.AspectClass"/>
<aop:config>
<aop:aspect ref="aspectClass">
<aop:pointcut
id="aspectStart"
expression="execution(* com.spring.chapter4.xmlaspect.BeNotified.aspectStart(..))"/>
<aop:before
pointcut-ref="aspectStart"
method="beforeNotice"/>
<aop:after-returning
pointcut-ref="aspectStart"
method="fterReturningNotice"/>
<aop:after-throwing
pointcut-ref="aspectStart"
method="aafterThrowingNotice"/>
</aop:aspect>
</aop:config>
</beans>
补充点二:环绕通知
@Around相当于所有通知的集合
必须要有joinPoint.proceed(),将控制权转交到被通知方,不然会发生堵塞
package com.spring.chapter4.springaop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AspectClass3 {
@Pointcut("execution(* com.spring.chapter4.springaop.BeNotified.aspectStart(..))")
public void aspectStart(){}
@Around("aspectStart()")
public void around(ProceedingJoinPoint joinPoint){
try {
System.out.println("主体方法运行之前3");
joinPoint.proceed();//控制器转让,需要捕获Throable异常
System.out.println("主体方法运行之后3");
}catch (Throwable e){
System.out.println("主体方法运行抛出一异常后2");
}
}
}
xml方式:
切面类:无注解
package com.spring.chapter4.xmlaspect;
import org.aspectj.lang.ProceedingJoinPoint;
public class AspectAroundClass {
public void aroundAspect(ProceedingJoinPoint proceedingJoinPoint){
try{
System.out.println("xmlaspect:主体方法运行之前");
proceedingJoinPoint.proceed();
System.out.println("xmlaspect:主体方法运行之后");
}catch (Throwable e){
System.out.println("xmlaspect:主体方法运行抛出一异常后");
}
}
}
xml配置文件
<?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 id="beNotified" class="com.spring.chapter4.xmlaspect.BeNotifiedImpl"/>
<bean id="aspectClass" class="com.spring.chapter4.xmlaspect.AspectAroundClass"/>
<aop:config>
<aop:aspect ref="aspectClass">
<aop:pointcut
id="aspectStart"
expression="execution(* com.spring.chapter4.xmlaspect.BeNotified.aspectStart(..))"/>
<aop:around
pointcut-ref="aspectStart"
method="aroundAspect"/>
</aop:aspect>
</aop:config>
</beans>
补充三:切点的限制匹配
(1)within,限定类型
*.aspectStart(..)表示任何的aspectStart(..)方法都可以成为切点
exection最小粒度是方法
within最小粒度为类级别,通过BeNotfiedImpl 限定aspectStart为该类的方法,如果是其他类的方法,则不会出发通知
@Before("execution(* *.aspectStart(..)) && within(BeNotifiedImpl)")
也可以这么写,表示限制的类为com.spring.chapter4.springaop下的所有类,不包括下一级包的类
@Before("execution(* *.aspectStart(..)) && within(com.spring.chapter4.springaop.*)")
或者这么写,包含springaop下所有包种的类
@Before("execution(* *.aspectStart(..)) && within(com.spring.chapter4.springaop..*)")
(2)bean,限定特定的bean
限制只能通知bean id为testid 的aspectStart方法
@Before("execution(* *.aspectStart(..)) && bean(testid)")
也可以这样,通知除了bean id为testid以外的所有bean方法
@Before("execution(* *.aspectStart(..)) && !bean(testid)")
补充点四:处理通知中的参数
先来看下怎么声明参数以及传入到通知方法中
具体示例:
args()中的number,要与aspectStart的参数number对应
aspectStart中的number,要与方法中的nubmer对应
做到以上两点,就可以把被通知方法中参数,使用到通知方法中了
package com.spring.chapter4.springaop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AspectClass4 {
@Pointcut("execution(* com.spring.chapter4.springaop.BeNotified.aspectStart(int)) && args(number) && within(BeNotifiedImpl2)")
public void aspectStart(int number){}
@Around("aspectStart(number)")
public void around(ProceedingJoinPoint joinPoint,int number){
try {
System.out.println("主体方法运行之前4" + "携带的参数" + number);
joinPoint.proceed();//控制器转让,需要捕获Throable异常
System.out.println("主体方法运行之后4" + "携带的参数" + number);
}catch (Throwable e){
System.out.println("主体方法运行抛出一异常后4" + "携带的参数" + number);
}
}
}
xml方式
切面类
package com.spring.chapter4.xmlaspect;
public class AspectArgsClass {
public void beforeNotice(int number){
System.out.println("xmlaspect:主体方法运行之前" + number);
}
public void fterReturningNotice(int number){
System.out.println("xmlaspect:主体方法运行之后" + number);
}
public void aafterThrowingNotice(int number){
System.out.println("xmlaspect:主体方法运行抛出一异常后" + number);
}
}
配置文件:配置类,注意number名要和切面类的参数number名对应
<?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 id="beNotified" class="com.spring.chapter4.xmlaspect.BeNotifiedImpl"/>
<bean id="aspectArgsClass" class="com.spring.chapter4.xmlaspect.AspectArgsClass"/>
<aop:config>
<aop:aspect ref="aspectArgsClass">
<aop:pointcut
id="aspectStart"
expression="execution(* com.spring.chapter4.xmlaspect.BeNotified.aspectStart(int)) and args(number)"/>
<aop:before
pointcut-ref="aspectStart"
method="beforeNotice"/>
<aop:after-returning
pointcut-ref="aspectStart"
method="fterReturningNotice"/>
<aop:after-throwing
pointcut-ref="aspectStart"
method="aafterThrowingNotice"/>
</aop:aspect>
</aop:config>
</beans>