什么是AOP
说起AOP,就让我想起了OOP。
AOP:Aspect Oriented Programming,面向切面编程
OOP:Object Oriented Programming,面向对象编程
我们都知道面向对象编程,是面向对象语言的核心。其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。OOP=对象+类+继承+多态+消息,其中核心概念是类和对象。
AOP,是通过预编译技术和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,它是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果,同时提高程序的可重用性和开发的效率。
简单来说,AOP就是把我们重复的代码抽取出来,在需要执行的时候,使用动态代理技术,在不修改源码的基础上,对已有的方法进行增强。说到这里,就不得不说动态代理。
动态代理
作用:不改变源码的基础上,对已有方法进行增强。它是AOP思想的实现技术
实现方式:
- 基于接口的动态代理
- 要求:被代理类至少实现一个接口,implements object。
- 提供者:JDK官方
- 涉及的类:Proxy
- 创建代理对象的方法:Proxy.newProxyInstance(ClassLoader, Class[], InvocationHandler)
- 参数的含义:
- ClassLoader:类加载器,和被代理对象使用相同的类加载器。一般都是固定写法:object.getClass().getClassLoader()
- Class[]:字节码数组,被代理类实现的接口,要求代理对象和被代理对象具有相同的行为。一般也是固定写法:object.getClass().getInterfaces()
- InvocationHandler:它是一个接口,就是用于提供增强代码的。一般是写一个该接口的实现类,该实现类可以是匿名内部类,也可以不是。
- 它的含义就是:如何代理。此处的代码只能是谁用谁提供。
- 设计模式:策略模式
- 使用要求:数据已经存在,接口中的参数就是提供的数据;目的明确,最终的结果就是对方法的增强,至于增强的内容,并不关注达成目标的过程就是策略。在dbUtils中的ResultSetHandler就是策略模式的具体应用。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 模拟一个剧组 * @author zhuow * */ public class Client { public static void main(String[] args) { final Actor actor = new Actor(); /* * actor.basicAct(100); actor.dangerAct(500); */ IActor proxyActor = (IActor) Proxy.newProxyInstance( actor.getClass().getClassLoader(), actor.getClass().getInterfaces(), new InvocationHandler() { /** * 执行被代理对象的任何方法都会经过该方法,该方法具有拦截的功能 * 方法的参数: * Object proxy:代理对象的引用。不一定每次都会用。 * Method method:当前执行的方法 * Object[] args:当前执行方法所需的参数 * 返回值:当前执行方法的返回值 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; // 1.取出方法中的参数 Float money = (Float)args[0]; // 2.判断当前执行的是什么方法 if ("basicAct".equals(method.getName())) { if (money > 10000) { // 3.执行方法 result = method.invoke(actor, money/2); } } else if ("dangerAct".equals(method.getName())) { if (money > 50000) { result = method.invoke(actor, money/2); } } return result; } }); proxyActor.basicAct(20000); proxyActor.dangerAct(60000); } }
- 基于子类的动态代理
- 要求:被代理类不能是最终类,不能被final修饰
- 提供者:第三方CGLib
- 涉及的类:Enhancer
- 创建代理对象的方法:create(Class,Callback)
- 参数的含义:
- Class:被代理对象的字节码
- Callback:如何代理。作用和InvocationHandler的作用相同。它也是一个接口,我们一般使用该接口的子接口MethodInterceptor。在使用时,创建该接口的实现类,可以是匿名接口类。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * 模拟一个剧组 * @author zhuow * */ public class Client { public static void main(String[] args) { final Actor actor = new Actor(); /* * actor.basicAct(100); actor.dangerAct(500); */ Actor proxyActor = (Actor) Enhancer.create(actor.getClass(), new MethodInterceptor() { /** * 执行被代理对象的任何方法,都会经过该方法。它和基于接口动态代理的invoke方法的作用相同 * * @param obj * @param method * @param args * @param proxy 当前执行方法的代理对象,一般不用。 * @return * @throws Throwable */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { Object result = null; // 1.取出方法中的参数 Float money = (Float)args[0]; // 2.判断当前执行的是什么方法 if ("basicAct".equals(method.getName())) { if (money > 10000) { // 3.执行方法 result = method.invoke(actor, money/2); } } else if ("dangerAct".equals(method.getName())) { if (money > 50000) { result = method.invoke(actor, money/2); } } return result; } }); proxyActor.basicAct(20000); proxyActor.dangerAct(60000); } }
Spring中的AOP
在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理方式。
AOP相关术语
- Joint point(连接点):
所谓连接点就是指那些被拦截到的点,即被代理接口类中的方法。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。 - Pointcut(切入点):
所谓切入点,就是指我们要对哪些Jointpoint进行拦截的定义,即拦截到的方法中,进行增强的方法就是切入点。 - Advice(通知/增强):
所谓通知是指拦截到Jointpoint之后要做的事情,也就是相较原方法,增强的部分就是通知。而通知类型有前置通知、后置通知、异常通知、最终通知、环绕通知。按照try{}catch{}finally{}来说,在invoke原方法之前就是前置通知,invoke原方法之后就是后置通知,catch中的是异常通知,finally中的是最终通知,而整个重载的invoke方法则是环绕通知,在环绕通知中有明确的invoke切入点方法调用。 - Introduction(引介):
引介是一种特殊的通知,在不修改代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field。一般不用。 - Target object(目标对象):
代理的目标对象,即被代理对象。 - Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程;spring采用动态代理织入,而Aspect采用编译期织入或类装载期织入。即加入增强代码,构成完整业务逻辑的过程。 - Proxy(代理):
一个类被AOP织入增强后,就产生一个结果代理类 - Aspect(切面):
是切人点和通知(引介)的结合
学习spring AOP要明确的事
- 开发阶段
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求
把共用代码抽取出来,制作成通知:开发阶段最后做。
在配置文件中,声明切入点与通知间的关系,即切面。 - 运行阶段
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,在根据通知的类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
AOP实现
这里只说注解实现,如果对xml配置感兴趣,可以查看官方文档。
- @EnableAspectJAutoProxy
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
- @Aspect
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
- @Pointcut
@Pointcut("execution(* transfer(..))") // 切入点表达式
private void anyOldTransfer() {} // 切入点签名,将此签名代入下面的通知声明中,会更加方便。
Spring AOP支持在切入点中使用的以下AspectJ切入点指示符(PCD)表达式:
execute:用于匹配方法执行的连接点。这是主要切入点使用Spring AOP时要使用的标识符。
inside:限制匹配某些类型内的连接点(方法的声明类型匹配)。
this:限制匹配的连接点,其中bean的引用是给定类型的实例。
target:限制匹配连接点,其中目标对象(代理的应用程序对象)是给定类型的实例。
args:限制匹配连接点,其中参数是给定类型的实例。
@target:限制匹配连接点,其中执行对象的类具有给定类型的注解。
@args:限制匹配连接点,传递的实际参数的运行时类型具有给定类型的注解。
@within:将匹配限制为具有给定注释(执行具有给定注释的类型中声明的方法)。
@annotation:限制匹配项连接点,连接点的主题具有特定的注解。
- @Before
前置通知
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
- @AfterReturning
后置通知
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample{
@AfterReturning(pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
- @AfterThrowing
异常通知
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample{
@AfterThrowing(pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
- @After
最终通知
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample{
@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
}
- @Around
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed(); //执行原逻辑
// stop stopwatch
return retVal;
}
}