AOP(Aspect Oriented Programming)简介
AOP 指面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
AOP 是 OOP(Object Oriented Programming)的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
AOP 实现原理
AOP 底层通过动态代理实现
代理是一种常用的设计模式,通过一个对象 A 持有另一个对象 B ,可以使得 A 具有与 B 相同的行为,代替甚至增强 B 的工作
常用的动态代理技术有 JDK 代理和 cglib 代理,Spring 框架根据目标类是否实现接口决定采用哪种动态代理技术
JDK 代理
JDK 代理基于接口实现,代理的目标对象必须实现某一接口
目标对象接口
public interface TargetInterface {
public void say();
}
目标对象类
public class Target implements TargetInterface {
public void say() {
System.out.println("喵");
}
}
增强类
public class Advice {
public void before() {
System.out.println("前置增强...");
}
public void afterReturning() {
System.out.println("后置增强...");
}
}
代理测试
public class ProxyTest {
public static void main(String[] args) {
Target target = new Target();
Advice advice = new Advice();
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.before();
Object invoke = method.invoke(target, args);
advice.afterReturning();
return invoke;
}
});
proxy.say();
}
}
使用 Proxy.newProxyInstance() 方法创建一个动态代理对象,需要三个参数
- ClassLoader:目标对象的类加载器,通常使用 getClass().getClassLoader() 获取
- Class<?>[]:目标对象实现的全部接口,通常使用 getClass().getInterfaces() 获取
- InvocationHandler:这是一个接口,需要实现其 invoke() 方法
invoke() 方法中有三个参数
- Object proxy:代理的对象
- Method method:代理的方法
- Object[] args:代理方法需要的参数
在 invoke() 方法中,通过 method.invoke() 方法可以执行目标对象的相应方法,传入目标对象和方法参数,本质是反射,在 method.invoke() 前后还可以通过 advice 增强对象来对目标方法进行前置和后置增强
创建好的动态代理对象使用目标对象的接口接受
cglib 代理
cglib 代理基于父类实现,该功能并非 Java 原生内容,但已经集成到 Spring 框架中,所以无需导入相关依赖
目标对象类
public class Target {
public void say() {
System.out.println("喵");
}
}
增强类
public class Advice {
public void before() {
System.out.println("前置增强...");
}
public void afterReturning() {
System.out.println("后置增强...");
}
}
代理测试
public class ProxyTest {
public static void main(String[] args) {
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 o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
advice.before();
Object invoke = method.invoke(target, args);
advice.afterReturning();
return invoke;
}
});
Target proxy = (Target) enhancer.create();
proxy.say();
}
}
首先创建一个增强器 Enhancer
通过 setSuperclass 设置其父类,通过 setCallback 设置回调,传入方法拦截器 MethodInterceptor 并重写 intercept() 方法,在其中进行目标对象方法的执行和相应的增强,最后通过 create() 方法创建代理对象
AOP 相关概念
Spring 的 AOP 底层通过上述两种动态代理的方式完成目标对象方法的增强
Target:目标对象
Proxy:代理对象
JoinPoint:连接点,指可以被增强的方法
Pointcut:切入点,指某个具体被增强的方法
Advice:通知(增强),对目标对象方法的具体增强内容
Aspect:切面,指 Pointcut 和 Advice 的结合
Weaving:织入,指把目标对象与通知结合创建代理对象的过程
xml 实现 AOP
导入相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
创建目标类和切面类
public class Target {
public void say() {
System.out.println("喵");
}
}
public class MyAspect {
public void before() {
System.out.println("前置增强...");
}
public void afterReturning() {
System.out.println("后置增强...");
}
}
配置目标类和切面类的 bean
<bean id="target" class="Proxy.Target"/>
<bean id="myAspect" class="Proxy.MyAspect"/>
xml 中配置织入关系
命名空间中加入
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation 中加入
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
配置织入
<aop:config>
<aop:aspect ref="myAspect">
<aop:before method="before" pointcut="execution(public void Proxy.Target.say())"/>
<aop:after-returning method="afterReturning" pointcut="execution(public void Proxy.Target.say())"/>
</aop:aspect>
</aop:config>
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AOPTest {
@Autowired
private Target target;
@Test
public void test() {
target.say();
}
}
注解实现 AOP
创建目标类和切面类,并通过注解配置 bean
@Component("target")
public class Target {
public void say() {
System.out.println("喵");
}
}
@Component("myAspect")
public class MyAspect {
public void before() {
System.out.println("前置增强...");
}
public void afterReturning() {
System.out.println("后置增强...");
}
}
在切面类中通过注解配置织入关系
@Component("myAspect")
@Aspect
public class MyAspect {
@Before("execution(public void Proxy.Target.say())")
public void before() {
System.out.println("前置增强...");
}
@AfterReturning("execution(public void Proxy.Target.say())")
public void afterReturning() {
System.out.println("后置增强...");
}
}
配置组件扫描和 AOP 自动代理
<context:component-scan base-package="Proxy"/>
<aop:aspectj-autoproxy/>
通知类型
通知类型 | 标签 | 注解 | 说明 |
---|---|---|---|
前置通知 | <aop:before> | @Before | 指定增强方法在切入点之前执行 |
后置通知 | <aop:after-returning> | @AfterReturning | 指定增强方法在切入点之后执行 |
环绕通知 | <aop:around> | @Around | 指定增强方法在切入点之前和之后执行 |
异常抛出通知 | <aop:throwing> | @AfterThrowing | 指定增强方法在出现异常时执行 |
最终通知 | <aop:after> | @After | 指定增强方法在最终执行 |
对于环绕通知较为特殊,对于增强方法的编写,需如下格式
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强");
Object proceed = pjp.proceed();
System.out.println("环绕后增强");
return proceed;
}
ProceedingJoinPoint 可以理解为切入点,进行相应的织入后,执行目标方法相当于执行该方法,该方法中通过 ProceedingJoinPoint 的 preceed() 方法,执行目标方法,但在目标方法前后进行环绕增强,最终将目标方法的返回值,再次返回
切点表达式规则
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以用 * 代替表示任意,相当于通配符
- 包名和类名之间一个点.表示该包下的类,两个点..表示该包以及子包下的类
- 参数可以用两个点.. 表示任意个数任意类型的参数
切点表达式提取
xml 配置中抽取
<aop:config>
<aop:aspect ref="aspect">
<aop:pointcut id="myPointcut" expression="execution(public void Proxy.Target.say())"/>
<aop:before method="before" pointcut-ref="myPointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
通过 <aop:pointcut> 抽取切点表达式,在需要使用的地方通过 pointcut-ref 引用即可,该标签需放在 <aop:config> 标签内部
注解抽取
@Component("myAspect")
@Aspect
public class MyAspect {
@Before("MyAspect.pointcut()")
public void before() {
System.out.println("前置增强...");
}
@AfterReturning("pointcut()")
public void afterReturning() {
System.out.println("后置增强...");
}
@Pointcut("execution(public void Proxy.Target.say())")
public void pointcut() {}
}
通过 @pointcut 注解抽取切点表达式,在需要使用的地方通过方法名或类名.方法名引用即可