Spring AOP 的深入了解
AOP(Aspect Oriented Programming)面向切面编程,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低。
主要意图:
将日志记录、性能统计、安全控制、事务处理、异常处理代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
aop相关术语
-
Target(目标):被增强的对象。UserDaoImpl
-
Proxy(代理对象):被应用了增强后,产生一个代理对象。
-
JoinPoint(连接点):指的是可以被拦截到的点。增删改查这些方法都可以被增强,这些方法都是连接点。add()、delete()、update()、findAll()方法
-
PointCut(切入点/切点):指的是真正被拦截到的点。对save方法进行增强(做权限校验),save方法称为切入点。*pointCut1、pointCut2上面注解的表达式对于的类方法
-
Advice(通知):拦截后要做的事情。对save方法要进行权限校验,权限校验的方法称为是通知。before()、afterReturning()、around()方法拦截后做的事情就是通知
-
Weaving(织入):将Advice应用到Target的过程。将权限校验应用到UserDaoImpl的save方法的过程。
-
Aspect(切面):切入点和通知的组合
SpringBoot+AOP
在SpringBoot中使用AOP很简单,免去了很多xml的配置,直接使用注解的方式,SpringBoot默认开启aop自动代理。
项目需要的依赖:
<dependencies>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
</dependencies>
UserDao接口类
public interface UserDao {
String add();
String delete();
String update();
String findAll();
}
UserDaoImpl实现类
@Service
public class UserDaoImpl implements UserDao {
@Override
public String add() {
return "add User";
}
@Override
public String delete() {
System.out.println("环绕中要执行的业务");
return "del User";
}
@Override
public String update() {
return "update User";
}
@Override
public String findAll() {
return "find All User";
}
}
Aspect切面类
@Aspect
@Component
@Slf4j
public class MyAspect {
@Pointcut("execution(public * com.jiuyue.test.dao..*.add(..))")
void pointCut1(){}
@Pointcut("execution(public * com.jiuyue.test.dao..*.delete(..))")
void pointCut2(){}
@Before("pointCut1()")
void before(JoinPoint joinPoint){
log.info("前置通知###########################");
log.info("joinPoint:{}",joinPoint);
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
log.info("URL:{}",request.getRequestURL());//获取请求URL
log.info("IP:{}",request.getRemoteAddr());//获取请求的IP地址
}
@AfterReturning(value = "pointCut1()",returning = "ret")
void afterReturning(Object ret){
log.info("后置通知###########################");
log.info("returnParam:{}",ret);
}
@Around("pointCut2()")
void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("前置通知########################### by around");
Object ret = "hello";
//ret = proceedingJoinPoint.proceed();
log.info("返回参数:{}",ret);
log.info("后置通知########################### by around");
}
}
在@Around通知中,如果proceedingJoinPoint.proceed()没执行,则方法被拦截,不会执行目标对象的方法。
SpringBoot使用xml配置文件方式
切面类
package jiuyue.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class LogAspect {
/**
* 前置通知
*/
public void beforeLog(JoinPoint joinPoint){
System.out.println("========目标方法("+joinPoint.getSignature().getName()+")执行之前记录日记=======");
}
/**
* 后置通知
*/
public void afterLog(JoinPoint joinPoint){
System.out.println("========目标方法执行("+joinPoint.getSignature().getName()+")之后记录日记=======");
}
/**
* 环绕通知
* @param proceedingJoinPoint
*/
public void aroundLog(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("=== 环绕通知,目标方法执行之前 ===");
try {
proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("=== 环绕通知,目标方法执行之后 ===");
}
/**
* 后置返回通知
*/
public void returningLog(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("目标方法名==>" + methodName +
" 返回值==> " + result);
}
/**
* 后置异常通知
*/
public void throwingLog(JoinPoint joinPoint, Exception ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("目标方法名==>" + methodName +
" 异常==>" + ex);
}
}
applicationContext.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<bean id="userService" class="jiuyue.aop.service.ipml.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="jiuyue.aop.dao.impl.UserDaoImpl"/>
<!-- 切面类的bean -->
<bean id="logAspectBean" class="jiuyue.aop.aspect.LogAspect"/>
<aop:config><!--多个切面的切入点相同则提取出来-->
<aop:pointcut id="logPointcut" expression="execution(* jiuyue.aop.dao.*..*.*(..)) or execution(* jiuyue.aop.service.*..*.*(..)) "/>
<aop:aspect id="logAspect" ref="logAspectBean">
<!--前置通知-->
<aop:before method="beforeLog" pointcut-ref="logPointcut" />
<!--后置通知-->
<aop:after method="afterLog" pointcut-ref="logPointcut"/>
<!--环绕通知-->
<!--<aop:around method="aroundLog" pointcut-ref="logPointcut"/>-->
<!--后置返回通知,目标方法返回时执行,可获取目标方法的返回值,returning指定返回值参数名-->
<aop:after-returning method="returningLog" pointcut-ref="logPointcut" returning="result"/>
<!--后置异常通知,目标方法抛出异常时执行,throwing指定异常的参数名-->
<aop:after-throwing method="throwingLog" pointcut-ref="logPointcut" throwing="ex"/>
</aop:aspect>
</aop:config>
</beans>