AOP
什么是AOP?
AOP是面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP的作用以及优势
- 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
- 优势:减少重复代码,提高开发效率,并且便于维护
AOP底层实现
实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
AOP的动态代理技术
常用的动态代理技术
- JDK代理:基于接口的动态代理技术
- cglib代理:基于父类的动态代理技术
JDK动态代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| Target target = new Target();
Advice advice = new Advice();
TargetInterface targetInterface = (TargetInterface)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { advice.before(); Object invoke = method.invoke(target, args);
advice.after(); return invoke;
} } );
target.save();
|
cglib动态代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| Target target = new Target();
Advice advice = new Advice();
Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Target.class); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
advice.before();; Object invoke = method.invoke(target, args); advice.after(); return invoke;
} });
Target proxy = (Target) enhancer.create();
proxy.save();
|
AOP相关概念
AOP的实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
AOP中相关的常用术语:
- Target(目标对象):代理的目标对象
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
- Joinpoint(连接点):可以被增强的方法,就叫做连接点
- Pointcut(切入点):真正要被增强的方法,就叫做切入点
- Advice(通知/增强):所谓通知就是指将拦截到的目标方法之后所要做的事情就是通知
- Aspect(切面):是切入点和通知的结合
- Weaving(织入):就是将切入点和通知结合到一起的过程就叫做织入。
AOP开发明确的事项
需要编写的内容
- 编写核心业务代码(目标类的目标方法)
- 编写切面类,切面类中有通知(增强功能方法)
- 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
AOP技术实现的内容
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
AOP底层使用哪种代理方式
在spring中,框架会根据目标类是否实现了接口来决定采用哪种代理方式。
知识要点
基于XML的AOP开发
快速入门
① 导入AOP相关坐标
②创建目标接口和目标类(内部有切点)
③创建切面类(内部有增强方法)
④将目标类和切面类的对象创建权交给Spring
⑤在applicationContext.xml中配置织入关系
⑥测试代码
导入AOP相关坐标
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.5.RELEASE</version> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
|
创建目标接口和目标类(内部切点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
public interface TargetInterface {
void method(); }
public class Target implements TargetInterface {
@Override public void method() { System.out.println("Target Running......."); } }
|
创建切面类(内部有增强方法)
1 2 3 4 5 6 7 8 9
| public class MyAspect {
public void before(){ System.out.println("前置代码增强......"); } }
|
将目标类和切面类的创建权交给Spring
1 2 3 4
| <bean id="target" class="com.yr.service.impl.Target"></bean> <bean id="myAspect" class="com.yr.utils.MyAspect"></bean>
|
在applicationContext.xml中配置织入关系
1 2 3 4 5 6 7 8 9 10 11
| <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/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 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
1 2 3 4 5 6 7
| <aop:config> <aop:aspect ref="myAspect"> <aop:before method="before" pointcut="execution(public void com.yr.service.impl.Target.method())"></aop:before> </aop:aspect> </aop:config>
|
测试代码
1 2 3 4 5 6 7 8 9 10 11 12
| @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class AopTest { @Autowired private TargetInterface targetInterface;
@Test public void test1(){ targetInterface.method(); } }
|
测试结果
XML配置AOP详解
切点表达式的写法
表达式语法:
1
| execution([修饰符] 返回值类型 包名.类名.方法名(参数))
|
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号* 代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
- 参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表
例如:
1 2 3 4 5
| execution(public void com.itheima.aop.Target.method()) execution(void com.itheima.aop.Target.*(..)) execution(* com.itheima.aop.*.*(..)) execution(* com.itheima.aop..*.*(..)) execution(* *..*.*(..))
|
通知的类型
通知的配置语法:
1
| <aop:通知类型 method="切面类中方法名" pointcut="切点表达式"></aop:通知类型>
|
名称 |
标签 |
说明 |
前置通知 |
< aop:before > |
用于配置前置通知,指定增强方法在切入点方法之前执行 |
后置通知 |
< aop:after-returning > |
用于配置后置通知,指定增强的方法在切入点方法之后执行 |
环绕通知 |
< aop:around > |
用于配置环绕通知,指定增强的方法在切入点之前和之后都执行 |
异常抛出通知 |
< aop:throwing > |
用于配置异常抛出通知,指定增强的方法在出现异常时执行 |
最终通知 |
< aop:after > |
用于配置最终通知,无论增强方式执行是否有异常都会执行 |
配置代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <aop:before method="before" pointcut="execution(public void com.aop.service.impl.Target.method())"></aop:before>
<aop:after-returning method="afterReturning" pointcut="execution(* com.aop.service.impl.*.*(..))"></aop:after-returning>
<aop:around method="around"pointcut="execution(*com.aop.service.impl.*.*(..))"></aop:around>
<aop:after-throwing method="afterReturning" pointcut="execution(* com.aop.service.impl.*.*(..))"></aop:after-throwing>
<aop:after method="after" pointcut="execution(* com.aop.service.impl.*.*(..))"></aop:after>
|
切点表达式的抽取
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用pointcut-ref属性代替pointcut属性来引用抽取后的切点表达式。
1 2 3 4 5 6 7
| <aop:config> <aop:aspect ref="myAspect"> <aop:ponitcut id="myPointcut" expression="execution(* com.itheima.aop.*.*(..))"/> <aop:before method="before" ponitcut-ref="myPointcut"></aop:before> </aop:aspect> </aop:config>
|
知识要点
基于注解的AOP开发
快速入门
基于注解的aop开发步骤:
- 创建目标接口和目标类(内部有切点)
- 创建切面类(内部有增强方法)
- 将目标类和切面类的对象创建权交给spring
- 在切面类中使用注解配置织入关系
- 在配置文件中开启组件扫描和AOP的自动代理
- 测试
注解配置AOP详解
注解通知的类型
通知的配置语法:@通知注解(“切点表达式”)
名称 |
注解 |
说明 |
前置通知 |
@Before |
用于配置前置通知。指定增强的方法在切入点方法之前执行 |
后置通知 |
@AfterReturning |
用于配置后置通知。指定增强的方法在切入点方法之后执行 |
环绕通知 |
@Around |
用于配置环绕通知。指定增强的方法在切入点之前和之后都执行 |
异常抛出通知 |
@AfterThrowing |
用于配置异常抛出通知。指定增强的方法在出现异常时执行 |
最终通知 |
@After |
用于配置最终通知。无论增强方式执行是否有异常都会执行 |
切点表达式的抽取
同xml配置aop一样,我们可以将切点表达式抽取。抽取的方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在增强注解中进行引用。具体如下:
1 2 3 4 5 6 7 8 9 10 11
| @Component("myAspect") @Aspect public class MyAspect{ @Before("MyAspect.myPoint()") public void before(){ System.out.println("前置代码增强.....") } @Pointcut("execution(* com.ithemima.aop.*.*(..))") public void myPoint(){} }
|
知识要点
- 注解aop开发步骤
- 使用@Aspect标注切面类
- 使用@通知注解标注通知方法
- 在配置文件中配置aop自动代理< aop:aspectj-autoproxy />