AOP
1.1 AOP理解
OOP (Object Oriented Programming) 面向对象编程
AOP (Aspect Oritented Programming) 面向切面编程)
两者不是替代的关系而是互补的,AOP主要就是在不改变原本代码功能的情况下去新增功能
在spring中AOP很重要,可以理解为,AB业务互不影响且要去实现C的业务,但是我们需要在执行A业务的时候,在C业务功能的基础上,让A业务完成想要的功能,让B业务完成B业务需要的更强大的功能,前提就是C业务是基础业务是不能被更改的,不然就要影响其他业务。
所以在这个时候,AOP就做出了一个动作,A业务在C业务的想要添加的方法前面砍断,添加一个新的A自己的业务上去后,再将重新组合好的业务给织入原来的C业务方法那里去,继续执行C业务后面的功能。B业务一样的。
1.2 AOP实现
1.2.1 注解实现
- 添加依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.18</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.6</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.18</version>
</dependency>
- 修改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: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.xs">
<!-- 注解实现:-->
<!-- 开启注解:注解是需要开启的-->
<!-- <context:annotation-config></context:annotation-config>-->
<!-- 扫描注解:扫描注解的话,是辉自动开启注解的,所以上面开启注解语句就可以不要了-->
<context:component-scan base-package="com.xiaowang">
</context:component-scan>
<!-- 加载配置文件到spring里面,这样就可以去获取/置文件中的数据了-->
<!-- classpath:就是加载resource类路径下面的配置文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!--
开启动态代理
proxy-target-class:
如果为true那就是类代理,也就是jdk实现动态代理
如果是false那就是cglib实现动态代理
默认是false
-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
</beans>
最主要的就是加上了:
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xs
注意:使用AOP的话是需要开启动态代理的哈,proxy-target-class的值true代表jdk实现,false代表cglib实现
- 创建切面类
创建切面类,将其放入spring中,我们这里来通过测试类中testJDK方法来演示,在UserServiceImpl中的public User login(String username, String password) { }这里进行切入:先看一下未切入之前的输出结果:
开始创建切入类
//放入spring中
@Component
@Aspect //这个标签是标记这个类是一个切面
public class PetAspect {
/**
* 标记再哪里什么时候执行切入
* @param joinPoint
*/
// execution:里面写的就是切入点,切入点有很多种语法,下面这个是最精准的切入点,直接定位到了具体的方法,切入点的语法有很多,下面一一介绍
@Before("execution(public com.xiaowang.entity.User com.xiaowang.PetService.impl.PetServiceImpl.login(java.lang.String,java.lang.String))")
//想将下面这个方法切入到测试类中testJDK()方法的前面执行
public void before(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
//切入面中,是可以获取很多被拦截的东西,
System.out.println("before之前执行");
System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
System.out.println("代理对象为:"+joinPoint.getThis());
System.out.println("目标对象为:"+joinPoint.getTarget());
}
}
执行结果:
我们可以看到,就在login方法的前面就添加进去了我们的对象的方法,我们拦截的东西也可以看到了,这里同时还有多种类似于@before的注解,其功能就是在这个方法切入点的哪一个位置进行切入,例如还有:
/**
* 标记再哪里什么时候执行切入,
* @befor 代表在标记位置的前面切入
* @param joinPoint
*/
// execution:里面写的就是切入点,切入点有很多种语法,下面这个是最精准的切入点,直接定位到了具体的方法,切入点的语法有很多,下面一一介绍
@Before("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
//想将下面这个方法切入到测试类中testJDK()方法的前面执行
public void before(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
//切入面中,是可以获取很多被拦截的东西,
System.out.println("before之前执行");
System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
System.out.println("代理对象为:"+joinPoint.getThis());
System.out.println("目标对象为:"+joinPoint.getTarget());
}
/**
* @After 在切入点的后面切入方法功能进去
* 相当于finally执行,就是必备执行
* @param joinPoint
*/
@After("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
//想将下面这个方法切入到测试类中testJDK()方法的前面执行
public void after(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
//切入面中,是可以获取很多被拦截的东西,
System.out.println("after执行");
System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
System.out.println("代理对象为:"+joinPoint.getThis());
System.out.println("目标对象为:"+joinPoint.getTarget());
}
/**
* @AfterReturning 相当于正常执行,就是拦截方法正确执行后就可以执行
* @param joinPoint
*/
@AfterReturning("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
//想将下面这个方法切入到测试类中testJDK()方法的前面执行
public void afterReturning(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
//切入面中,是可以获取很多被拦截的东西,
System.out.println("afterReturning执行");
System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
System.out.println("代理对象为:"+joinPoint.getThis());
System.out.println("目标对象为:"+joinPoint.getTarget());
}
/**
* 拦截的语句那里抛出异常后被执行
* @param joinPoint
*/
@AfterThrowing("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
//想将下面这个方法切入到测试类中testJDK()方法的前面执行
public void afterThrowing(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
//切入面中,是可以获取很多被拦截的东西,
System.out.println("afterThrowing执行");
System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
System.out.println("代理对象为:"+joinPoint.getThis());
System.out.println("目标对象为:"+joinPoint.getTarget());
}
①拦截方法正常执行的时候结果:
②拦截方法有异常的时候结果:
如果使用AOP去管理事务的话,那我们就可以在before里面写事务的开启,在afterReturning中提交事务,在afterThrowing中去回滚事务,
所以在@afterThrowing这里的话就可以再加一点属性,去设置哪些地方需要回滚,哪些地方不需要回滚
/**
* 拦截的语句那里抛出异常后被执行,相当于catch
* @param joinPoint
*/
@AfterThrowing(value = "execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))",throwing = "e")
//想将下面这个方法切入到测试类中testJDK()方法的前面执行
public void afterThrowing(JoinPoint joinPoint,Exception e){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
//切入面中,是可以获取很多被拦截的东西,
System.out.println("afterThrowing执行");
System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
System.out.println("代理对象为:"+joinPoint.getThis());
System.out.println("目标对象为:"+joinPoint.getTarget());
//事务
System.out.println("抛出异常进行回滚"+e.getMessage());
}
独特的环绕执行:
/**
* 环绕执行,就像过滤器一样,再dofilt前面执行,后面执行
* @param proceedingJoinPoint
* @return
*/
@Around(value = "execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
//想将下面这个方法切入到测试类中testJDK()方法的前面执行
public Object around(ProceedingJoinPoint proceedingJoinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
//切入面中,是可以获取很多被拦截的东西,
System.out.println("around之前执行");
Object proceed = proceedingJoinPoint.proceed();//代理的方法执行
System.out.println("around之后执行");
return proceed;
}
1.2.2 xml实现
使用xml实现当然前期的依赖呀、配置文件的头部那些基本的都是要有哈,和注解实现的前面一样,区别就在于对切面类的编写的时候,切入点不用写在注解上了,而是写在xml文件里了,也就是删掉or注释掉切面类中的注解呀
//放入spring中
@Component
@Aspect //这个标签是标记这个类是一个切面
public class PetAspect {
/**
* 标记再哪里什么时候执行切入,
* @befor 代表在标记位置的前面切入
* @param joinPoint
*/
// execution:里面写的就是切入点,切入点有很多种语法,下面这个是最精准的切入点,直接定位到了具体的方法,切入点的语法有很多,下面一一介绍
// @Before("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
//想将下面这个方法切入到测试类中testJDK()方法的前面执行
public void before(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
//切入面中,是可以获取很多被拦截的东西,
System.out.println("before之前执行");
System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
System.out.println("代理对象为:"+joinPoint.getThis());
System.out.println("目标对象为:"+joinPoint.getTarget());
}
/**
* @After 在切入点的后面切入方法功能进去
* 相当于finally执行,就是必备执行
* @param joinPoint
*/
// @After("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
//想将下面这个方法切入到测试类中testJDK()方法的前面执行
public void after(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
//切入面中,是可以获取很多被拦截的东西,
System.out.println("after执行");
System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
System.out.println("代理对象为:"+joinPoint.getThis());
System.out.println("目标对象为:"+joinPoint.getTarget());
}
/**
* @AfterReturning 相当于正常执行,就是拦截方法正确执行后就可以执行
* @param joinPoint
*/
// @AfterReturning("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
//想将下面这个方法切入到测试类中testJDK()方法的前面执行
public void afterReturning(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
//切入面中,是可以获取很多被拦截的东西,
System.out.println("afterReturning执行");
System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
System.out.println("代理对象为:"+joinPoint.getThis());
System.out.println("目标对象为:"+joinPoint.getTarget());
}
/**
* 拦截的语句那里抛出异常后被执行,相当于catch
* @param joinPoint
*/
// @AfterThrowing(value = "execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))",throwing = "e")
//想将下面这个方法切入到测试类中testJDK()方法的前面执行
public void afterThrowing(JoinPoint joinPoint,Exception e){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
//切入面中,是可以获取很多被拦截的东西,
System.out.println("afterThrowing执行");
System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
System.out.println("代理对象为:"+joinPoint.getThis());
System.out.println("目标对象为:"+joinPoint.getTarget());
//事务
System.out.println("抛出异常进行回滚"+e.getMessage());
}
/* *//**
* 环绕执行,就像过滤器一样,再dofilt前面执行,后面执行
* @param proceedingJoinPoint
* @return
*//*
//@Around(value = "execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
//想将下面这个方法切入到测试类中testJDK()方法的前面执行
public Object around(ProceedingJoinPoint proceedingJoinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
//切入面中,是可以获取很多被拦截的东西,
System.out.println("around之前执行");
Object proceed = proceedingJoinPoint.proceed();//代理的方法执行
System.out.println("around之后执行");
return proceed;
}
*/
}
注意:我们在切面类上面加了@Aspect 这个注解后,就代表着这是一个切面了,就不用在xml中去通过<aop:aspect ref=“userAspect”>去定义这个类未切面类,两种方法二选一即可。
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: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:annotation-config></context:annotation-config>-->
<!-- 扫描注解:扫描注解的话,是辉自动开启注解的,所以上面开启注解语句就可以不要了-->
<context:component-scan base-package="com.xiaowang"> </context:component-scan>
<!-- 加载配置文件到spring里面,这样就可以去获取/置文件中的数据了-->
<!-- classpath:就是加载resource类路径下面的配置文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 装配注解,也就是将各个注解进行关联起来-->
<!--
开启动态代理
proxy-target-class:
如果为true那就是类代理,也就是jdk实现动态代理
如果是false那就是cglib实现动态代理
默认是false
-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
<!-- 使用xml去实现aop-->
<aop:config>
<!-- 定义切面信息,如果切面类上使用了@Aspect注解,就可以不用定义这个切面类信息,但是切面点啥的还是要配置-->
<aop:aspect ref="petAspect">
<!-- 定义切面点-->
<aop:pointcut id="add" expression="execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))"/>
<!-- 实现切面类的增强功能-->
<aop:before method="before" pointcut-ref="add"/>
<aop:after-returning method="afterReturning" pointcut-ref="add"/>
<aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="add"/>
<aop:after method="after" pointcut-ref="add"/>
</aop:aspect>
</aop:config>
</beans>
1.2.3 切入点语法
直接精准到一个方法上面去
execution( public com.zlt.entity.User com.zlt.service.impl.UserServiceImpl.login(java.lang.String,java.lang.String))
任意权限修饰符
execution( com.zlt.entity.User com.zlt.service.impl.UserServiceImpl.login(java.lang.String,java.lang.String))
无返回类型
execution( void com.zlt.service.impl.UserServiceImpl.login(java.lang.String,java.lang.String))
有返回类型
execution( !void com.zlt.service.impl.UserServiceImpl.login(java.lang.String,java.lang.String))
任意返回类型
execution( * com.zlt.service.impl.UserServiceImpl.login(java.lang.String,java.lang.String))
任意参数
execution( * com.zlt.service.impl.UserServiceImpl.login(..))
类中的任意方法
execution( * com.zlt.service.impl.UserServiceImpl.*(..))
类中以指定内容开头的方法
execution( * com.zlt.service.impl.UserServiceImpl.select*(..))
包中的任意类的任意方法不包含子包下面的类
execution( * com.zlt.service.impl.*.*(..))
包中及其下的任意类的任意方法
execution( * com.zlt.service..*.*(..))
1.3 AOP应用场景
- 事务管理的时候(before里开启事务、afterThowning回滚事务,after提交事务)
- 获取某个功能的执行时间(before设置开始时间、after设置结束时间)
- …等等