AOP基本概念
1、什么是AOP
- AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
- 在不改变原有的逻辑的基础上,增加一些额外的功能。代理也是这个功能,读写分离也能用aop来做。
AOP底层原理
1、AOP底层使用动态代理
两种情况动态代理
第一种 有接口情况,使用JDK动态代理
第二种 没有接口情况,使用CGLIB动态代理
创建子类的代理对象,增强类的方法
2、AOP(JDK动态代理)
1、使用JDK动态代理,使用Proxy类里面的方法创建
a、调用newProxyInstance 方法
static Object | newProxyInstance (ClassLoader loader, Class <?>[] interfaces, InvocationHandler h) Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler. |
---|---|
loader :类加载器
interfaces :增强方法所在的类,这个类实现的接口,支持多个接口
h: 实现这个接口InvocationHandler , 创建代理对象
2、JDK实现动态代理代码
package com.wujing.spring5.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @ClassName: JDkProxy
* @Description: 测试代理
* @Author liujiexin
* @Date 2021/9/8 11:04 下午
*/
public class JDkProxy {
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
dao.add(10, 20);
}
}
class UserDaoProxy implements InvocationHandler{
private Object object;
public UserDaoProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法之前 ");
Object invoke = method.invoke(object, args);
System.out.println("方法之后");
return invoke;
}
}
package com.wujing.spring5.aop;
/**
* @ClassName: UserDao
* @Description: 测试动态代理
* @Author liujiexin
* @Date 2021/9/8 10:44 下午
*/
public interface UserDao {
public int add(int a , int b);
}
package com.wujing.spring5.aop;
/**
* @ClassName: UserImpl
* @Description: 实现类
* @Author liujiexin
* @Date 2021/9/8 10:45 下午
*/
public class UserDaoImpl implements UserDao
{
@Override
public int add(int a, int b) {
return a+b;
}
}
AOP 术语
(1)切面(Aspect)
切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。 Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
@Component @Aspect public class LogAspect { }
可以简单地认为, 使用 @Aspect 注解的类就是切面
(2) 目标对象(Target)
目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。
(3) 连接点(JoinPoint)
程序执行过程中明确的点,如方法的调用或特定的异常被抛出。连接点由两个信息确定:
- 方法(表示程序执行点,即在哪个目标方法)
- 相对点(表示方位,即目标方法的什么位置,比如调用前,后等)
简单来说,连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。
@Before(“pointcut()”)
public void log(JoinPoint joinPoint) {
//这个JoinPoint参数就是连接点
}
(4) 切入点(PointCut)
切入点是对连接点进行拦截的条件定义。切入点表达式如何和连接点匹配是AOP的核心,Spring缺省使用AspectJ切入点语法。
一般认为,所有的方法都可以认为是连接点,但是我们并不希望在所有的方法上都添加通知,而切入点的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配连接点,给满足规则的连接点添加通知。
@Pointcut(“execution( com.remcarpediem.test.aop.service..(..))”) public void pointcut() { }
上边切入点的匹配规则是com.remcarpediem.test.aop.service包下的所有类的所有函数。
(5) 通知(Advice)
通知是指拦截到连接点之后要执行的代码,包括了“around”、“before”和“after”等不同类型的通知。Spring AOP框架以拦截器来实现通知模型,并维护一个以连接点为中心的拦截器链。
// @Before说明这是一个前置通知,log函数中是要前置执行的代码,JoinPoint是连接点,
@Before(“pointcut()”) public void log(JoinPoint joinPoint) {
}
(6) 织入(Weaving)
织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。
(7) 增强器(Adviser)
Advisor是切面的另外一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。Advisor由切入点和Advice组成。
Advisor这个概念来自于Spring对AOP的支撑,在AspectJ中是没有等价的概念的。Advisor就像是一个小的自包含的切面,这个切面只有一个通知。切面自身通过一个Bean表示,并且必须实现一个默认接口。
// AbstractPointcutAdvisor是默认接口
public class LogAdvisor extends AbstractPointcutAdvisor {
private Advice advice; // Advice
private Pointcut pointcut; // 切入点
@PostConstruct
public void init() {
// AnnotationMatchingPointcut是依据修饰类和方法的注解进行拦截的切入点。
this.pointcut = new AnnotationMatchingPointcut((Class) null, Log.class);
// 通知
this.advice = new LogMethodInterceptor();
}
}
AOP操作
1、导入jar包
spring-aspects.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.7.2.RELEASE.jar
2、切入点表达式
切入点表达式作用:
语法结构
execution([权限修饰符][放回类型][类全路径][方法名称][参数列表])
1、execution(): 表达式主体。
2、第一个号:表示返回类型, 号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个号:表示类名,号表示所有的类。
5、(..):最后这个星号表示方法名,号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
AOP 操作(Aspectj注解)
1、创建类,在类里面自定义方法
2、创建增强类(添加增强逻辑)
3、进行通知的配置
1、在spring配置文件中,开启注解扫描
2、使用注解创建User和UserProxy 对象
3、在增强类上面添加注解@Aspect
package com.wujing.spring5.aop.anno;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @ClassName: UserProxy
* @Description: user 增强类
* @Author liujiexin
* @Date 2021/9/14 9:00 上午
*/
@Component
@Aspect
public class UserProxy {
public void before(){
System.out.println("before");
}
}
4、在spring配置文件中开启生成代理对象
<?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:context="http://www.springframework.org/schema/context"
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/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 ">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.wujing.spring5.aop" ></context:component-scan>
<!-- 开启aspectj生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
5、在增强类,配置不同类型的通知,
4、通知类型
spring5.2.9 版本
正常执行顺序
环绕通知 之前
before
测试aop 注解 add
AfterReturning
After
环绕通知 之后
异常 执行顺序
环绕通知 之前
before
AfterThrowing
After
官方文档 https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introduction-defn
Spring AOP includes the following types of advice:
- Before advice: Advice that runs before a join point but that does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).
- After returning advice: Advice to be run after a join point completes normally (for example, if a method returns without throwing an exception).
- After throwing advice: Advice to be run if a method exits by throwing an exception.
- After (finally) advice: Advice to be run regardless of the means by which a join point exits (normal or exceptional return).
- Around advice: Advice that surrounds a join point such as a method invocation. This is the most powerful kind of advice. Around advice can perform custom behavior before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception.
package com.wujing.spring5.aop.anno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @ClassName: UserProxy
* @Description: user 增强类
* @Author liujiexin
* @Date 2021/9/14 9:00 上午
*/
@Component
@Aspect
public class UserProxy {
/**
* @Description: 前置增强
* @Before 表示前置增强
* @Param:
* @return: void
* @Author: liujiexin
* @Date: 2021/9/15 8:44 上午
*/
@Before(value = "execution(* com.wujing.spring5.aop.anno.User.add(..))")
public void before(){
System.out.println("before");
}
/**
* @Description: 返回通知,方法返回结果后执行。
* @Param:
* @return: void
* @Author: liujiexin
* @Date: 2021/9/15 8:44 上午
*/
@AfterReturning(value = "execution(* com.wujing.spring5.aop.anno.User.add(..))")
public void afterReturning(){
System.out.println("AfterReturning");
}
/**
* @Description: 后置增强,在调用之后执行(最终通知)
* @Param:
* @return: void
* @Author: liujiexin
* @Date: 2021/9/15 8:44 上午
*/
@After(value = "execution(* com.wujing.spring5.aop.anno.User.add(..))")
public void after(){
System.out.println("After");
}
/**
* @Description: 异常通知
* @Param:
* @return: void
* @Author: liujiexin
* @Date: 2021/9/15 8:44 上午
*/
@AfterThrowing(value = "execution(* com.wujing.spring5.aop.anno.User.add(..))")
public void afterThrowing(){
System.out.println("AfterThrowing");
}
/**
* @Description: 环绕通知,围绕方法执行。
* @Param:
* @return: void
* @Author: liujiexin
* @Date: 2021/9/15 8:44 上午
*/
@Around(value = "execution(* com.wujing.spring5.aop.anno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知 之前 ");
proceedingJoinPoint.proceed();
System.out.println("环绕通知 之后 ");
}
}
5、相同切入点抽取
@Pointcut(value = "execution(* com.wujing.spring5.aop.anno.User.add(..))")
public void pointcut(){
}
/**
* @Description: 前置增强
* @Before 表示前置增强
* @Param:
* @return: void
* @Author: liujiexin
* @Date: 2021/9/15 8:44 上午
*/
@Before(value = "pointcut()")
public void before(){
System.out.println("before");
}
6、多个增强类,添加优先级
@Order(10) , 数字越小,优先级越高
�