目录
概念
AOP 术语
1. 连接点(Jointpoint):
2. 切入点(Pointcut):
3. 通知(Advice):
4. 方面/切面(Aspect):
5. 引入(inter-type declaration):
6. 目标对象(Target Object):
7. 织入(Weaving):
8. AOP代理(AOP Proxy):
通知类型:
1. 前置通知(Before advice):
2. 后置通知(After returning advice):
3. 异常通知(After throwing advice):
4. 最终通知(After (finally) advice):
5. 环绕通知(Around Advice):
AOP 术语 图解 重要
AOP 核心概念 小总结
Spring AOP和 AspectJ 是什么关系 区别
AOP 的配置方式
XML 形式
AspectJ 注解
最后想手写一个 AOP 例子结束
参考文章
概念
AOP 的目的是为了解耦 其次是简化开发
AOP 是 Spring 的核心 面向切面编程
他是一套规范
通过预编译方式和运行期间动态代理实现程序的统一维护
核心概念 就是 将分散在各个业务逻辑代码中的相同的代码通过横向切割的方式抽取到一个独立的模块中
AOP 术语
首先让我们从一些重要的AOP概念和术语开始。
这些术语不是Spring特有的。
1. 连接点(Jointpoint):
表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点,在AOP中表示为在哪里干;
2. 切入点(Pointcut):
选择一组相关连接点的模式,即可以认为连接点的集合,Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,在AOP中表示为在哪里干的集合;
3. 通知(Advice):
在连接点上执行的行为,通知提供了在AOP中需要在切入点所选择的连接点处进行扩展现有行为的手段;包括前置通知(before advice)、后置通知(after advice)、环绕通知(around advice),在Spring中通过代理模式实现AOP,并通过拦截器模式以环绕连接点的拦截器链织入通知;在AOP中表示为干什么;
4. 方面/切面(Aspect):
横切关注点的模块化,比如上边提到的日志组件。可以认为是通知、引入和切入点的组合;在Spring中可以使用Schema和@AspectJ方式进行组织实现;在AOP中表示为在哪干和干什么集合;
5. 引入(inter-type declaration):
也称为内部类型声明,为已有的类添加额外新的字段或方法,Spring允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象), 在AOP中表示为干什么(引入什么);
6. 目标对象(Target Object):
需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为被通知对象;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象,在AOP中表示为对谁干;
7. 织入(Weaving):
把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。在AOP中表示为怎么实现的;
8. AOP代理(AOP Proxy):
AOP框架使用代理模式创建的对象,从而实现在连接点处插入通知(即应用切面),就是通过代理来对目标对象应用切面。在Spring中,AOP代理可以用JDK动态代理或CGLIB代理实现,而通过拦截器模型应用切面。在AOP中表示为怎么实现的一种典型方式;
通知类型:
1. 前置通知(Before advice):
在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
2. 后置通知(After returning advice):
在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
3. 异常通知(After throwing advice):
在方法抛出异常退出时执行的通知。
4. 最终通知(After (finally) advice):
当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
5. 环绕通知(Around Advice):
包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在JoinPoint上调用用于环绕通知的proceed()方法,就不会有调用的问题。
AOP 术语 图解 重要
AOP 核心概念 小总结
AOP 的核心是连接点
连接点是我们需要关注的程序拓展点
可能是类初始化 方法执行 方法调用 字段调用 异常处理等
Spring 支持的连接点是方法执行点
切入点是一系列连接点的集合,Spring默认使用AspectJ语法,在AOP中抽象表示为可以进行操作的集合
之后就是通知
通知就是我们在连接点上执行的行为
连接点 切入点 通知组合在一起 就是一个切面
把切面映入到其他应用程序或者对象上,创建一个被通知的对象,这些就是织入,Spring 在运行时完成织入 ,在 AOP 中表示为怎么实现的,实现方式
Spring AOP和 AspectJ 是什么关系 区别
AspectJ 是一个更加强大的 AOP 框架 是一个 AOP 标准
如果只是简单的业务 可以使用 AOP
AOP 一个重要的原则就是无侵入性
AspectJ 重要的是 一般在编译期进行 即静态织入
在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术。
Spring AOP更易用,AspectJ更强大
AOP 的配置方式
XML 形式
首先是业务逻辑
package aopByXml;
// 目标类 核心业务
public class AopDemoServiceImpl {
public void doMethod1() {
System.out.println("aopByXml.AopDemoServiceImpl.doMethod1()");
}
public String doMethod2() {
System.out.println("aopByXml.AopDemoServiceImpl.doMethod2()");
return "hello world";
}
public String doMethod3() throws Exception {
System.out.println("aopByXml.AopDemoServiceImpl.doMethod3()");
throw new Exception("some exception");
}
}
其次是切面类 我这边定义的方法都是通知
package aopByXml;
import org.aspectj.lang.ProceedingJoinPoint;
// 切面类
public class LogAspect {
/**
* 环绕通知.
*
* @param pjp pjp
* @return obj
* @throws Throwable exception
*/
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----------------------");
System.out.println("环绕通知: 进入方法");
Object o = pjp.proceed();
System.out.println("环绕通知: 退出方法");
return o;
}
/**
* 前置通知.
*/
public void doBefore() {
System.out.println("前置通知");
}
/**
* 后置通知.
*
* @param result return val
*/
public void doAfterReturning(String result) {
System.out.println("后置通知, 返回值: " + result);
}
/**
* 异常通知.
*
* @param e exception
*/
public void doAfterThrowing(Exception e) {
System.out.println("异常通知, 异常: " + e.getMessage());
}
/**
* 最终通知.
*/
public void doAfter() {
System.out.println("最终通知");
}
}
然后是 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"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<context:component-scan base-package="tech.pdai.springframework" />
<aop:aspectj-autoproxy/>
<!-- 目标类 -->
<bean id="demoService" class="tech.pdai.springframework.service.AopDemoServiceImpl">
<!-- configure properties of bean here as normal -->
</bean>
<!-- 切面 -->
<bean id="logAspect" class="tech.pdai.springframework.aspect.LogAspect">
<!-- configure properties of aspect here as normal -->
</bean>
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="logAspect">
<!-- 配置切入点 -->
<aop:pointcut id="pointCutMethod" expression="execution(* tech.pdai.springframework.service.*.*(..))"/>
<!-- 环绕通知 -->
<aop:around method="doAround" pointcut-ref="pointCutMethod"/>
<!-- 前置通知 -->
<aop:before method="doBefore" pointcut-ref="pointCutMethod"/>
<!-- 后置通知;returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
<aop:after-returning method="doAfterReturning" pointcut-ref="pointCutMethod" returning="result"/>
<!-- 异常通知:如果没有异常,将不会执行增强;throwing属性:用于设置通知第二个参数的的名称、类型-->
<aop:after-throwing method="doAfterThrowing" pointcut-ref="pointCutMethod" throwing="e"/>
<!-- 最终通知 -->
<aop:after method="doAfter" pointcut-ref="pointCutMethod"/>
</aop:aspect>
</aop:config>
<!-- more bean definitions for data access objects go here -->
</beans>
AspectJ 注解
XML 声明式存在不足 在配置文件里面写了太多繁琐的东西
下面是 AspectJ 提供的注解
Spring AOP的实现方式是动态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的;
如Java JDK的动态代理(Proxy,底层通过反射实现)
如 CGLIB的动态代理(底层通过继承实现)
Spring AOP采用的就是基于运行时增强的代理技术。
以 JDK 动态代理举例
接口 规则
package aopByAspectJJdk;
// 定义接口
public interface IJdkProxyService {
void doMethod1();
String doMethod2();
String doMethod3() throws Exception;
}
接口实现类 具体实现
package aopByAspectJJdk;
// 接口实现类
public class JdkProxyDemoServiceImpl implements IJdkProxyService{
@Override
public void doMethod1() {
}
@Override
public String doMethod2() {
return "";
}
@Override
public String doMethod3() throws Exception {
return "";
}
}
动态代理类 织入
package aopByAspectJJdk;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
/**
* @author pdai
*/
@EnableAspectJAutoProxy
@Component
@Aspect
public class LogAspect {
/**
* define point cut.
*/
@Pointcut("execution(* tech.pdai.springframework.service.*.*(..))")
private void pointCutMethod() {
}
/**
* 环绕通知.
*
* @param pjp pjp
* @return obj
* @throws Throwable exception
*/
@Around("pointCutMethod()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----------------------");
System.out.println("环绕通知: 进入方法");
Object o = pjp.proceed();
System.out.println("环绕通知: 退出方法");
return o;
}
/**
* 前置通知.
*/
@Before("pointCutMethod()")
public void doBefore() {
System.out.println("前置通知");
}
/**
* 后置通知.
*
* @param result return val
*/
@AfterReturning(pointcut = "pointCutMethod()", returning = "result")
public void doAfterReturning(String result) {
System.out.println("后置通知, 返回值: " + result);
}
/**
* 异常通知.
*
* @param e exception
*/
@AfterThrowing(pointcut = "pointCutMethod()", throwing = "e")
public void doAfterThrowing(Exception e) {
System.out.println("异常通知, 异常: " + e.getMessage());
}
/**
* 最终通知.
*/
@After("pointCutMethod()")
public void doAfter() {
System.out.println("最终通知");
}
}
最后想手写一个 AOP 例子结束
加减乘除的接口
public interface Calculator {
// 加法
int add(int i, int j);
// 减法
int sub(int i, int j);
// 乘法
int mul(int i, int j);
// 除法
int div(int i, int j);
}
具体实现逻辑
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部 result = " + result);
return result;
}
}
切面
import org.aspectj.lang.*;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(public int com.dc.esb.CalculatorImpl.*(..))")
public void pointCut() {
}
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:" + methodName + ",参数:" + args);
}
@After("execution(public int com.dc.esb.CalculatorImpl.*(..))")
public void afterMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->后置通知,方法名:" + methodName);
}
@AfterReturning(value = "execution(public int com.dc.esb.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:" + methodName + ",结果:" + result);
}
@AfterThrowing(value = "execution(public int com.dc.esb.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:" + methodName + ",异常:" + ex);
}
@Around("execution(public int com.dc.esb.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
Object result = null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
//目标对象(连接点)方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
} finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
}
return result;
}
}
参考文章
Spring基础 - Spring核心之面向切面编程(AOP) | Java 全栈知识体系