一、AOP的概念
1、Aspect Oriented Programming 面向切面编程
使用范围:
交叉业务:非核心功能,又散落在到处;而且还是不得不处理的功能
2、特点
(1)、程序员只需要关注核心业务,交叉业务将交给切面
(2)、由于使用动态代理模式,核心类和切面之间是没有直接的耦合关系
3、常用概念
切面:又被称为方面,指的是交叉业务,通常:1个交叉业务,对应1个切面
切入点:满足切面切入条件的点,就是切入点
连接点:当切面正式进入到某一个切入时,该切入点就是连接点
通知:又被称为增强,实际上就是切面的代码(前置通知,后置通知,后置返回通知,后置异常通知,环绕通知)
目标对象:需要被切面进行增强的对象
代理对象:使用JDK代理或CGLIB代理,针对目标对象创建出来的代理实例!
织入:AOP切面应用到切入点的过程,就是织入
二、使用方式
step1:导入相关的jar包
<!-- 导入第3方的切面相关Jar包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
<scope>runtime</scope>
</dependency>
step2:创建切面对象
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
/**
* 事务切面
* @Aspect 将该Java类声明为一个切面
*
*/
@Aspect
@Component
public class TransactionAspect {
}
step3: 定义条件表达式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
/**
* 定义条件表达式
*/
@Pointcut("execution(* com.woniuxy.stall.*mag.service.impl.*ServiceImpl.save(..))")
private void pointcut01(){}
@Pointcut("execution(* com.woniuxy.stall.*mag.service.impl.*ServiceImpl.update(..))")
private void pointcut02(){}
@Pointcut("execution(* com.woniuxy.stall.*mag.service.impl.*ServiceImpl.delete(..))")
private void pointcut03(){}
step4:添加通知
(1)前置通知
/**
* 前置通知
* 作用:获得SqlSession
* @param joinPoint 连接点
*/
@Before("pointcut01() || pointcut02() || pointcut03()")
public void beforeAdvice(JoinPoint joinPoint){
System.out.println("前置通知,我运行在目标方法执行之前!");
Object obj = joinPoint.getTarget();//得到目标对象
System.out.println(obj);
String name = joinPoint.getSignature().getName();//得到目标方法
System.out.println(name);
Object[] args = joinPoint.getArgs();
System.out.println(Arrays.toString(args));//得到目标参数
}
(2)后置返回通知:执行在目标方法成功执行之后
/**
* 后置返回通知
* @param joinPoint
* @param res
*/
@AfterReturning(value = "pointcut01() || pointcut02() || pointcut03()",returning = "res")
public void afterReturnAdvice(JoinPoint joinPoint,Object res){
System.out.println("我是后置返回通知,我执行在目标方法正常执行之后!");
System.out.println(res);
}
(3)后置异常通知 执行在目标方法抛出异常之后
/**
* 后置异常通知
* @param joinPoint
* @param res 异常
*/
@AfterThrowing(value="pointcut01() || pointcut02() || pointcut03()",throwing = "res")
public void afterThrowsAdvice(JoinPoint joinPoint,Exception res){
System.out.println("我是后置异常通知,我执行在目标方法抛出异常之后!");
System.out.println(res);
}
(4) 后置通知
/**
* 后置通知
* @param joinPoint
*/
@After("pointcut01() || pointcut02() || pointcut03()")
public void afterAdvice(JoinPoint joinPoint){
System.out.println("我是后置通知,不管目标方法能否正常执行,我都执行!");
}
(5)环绕通知 风险高,这玩意相当于:@Before + @After 能用@Before + @After搞定的事情,一定不要用@Around
/**
* 环绕通知
* @param pjp
* @return
* @throws Throwable
*/
@Around("pointcut01() || pointcut02() || pointcut03()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("我是环绕通知,我最NB!属于无法无天的……");
Object[] args = pjp.getArgs();//原始的参数
args[0] = null;
Object result = pjp.proceed(args);//调用目标方法(如果不写,目标方法将不执行)
return result;
}
完整代码
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.Arrays;
/**
* 事务切面
* @Aspect 将该Java类声明为一个切面
*
*/
@Aspect
@Component
public class TransactionAspect {
/**
* 定义条件表达式
* execution(* com.woniuxy.stall.*mag.service.impl.*ServiceImpl.*(..)) 最常见的表达式
*/
@Pointcut("execution(* com.woniuxy.stall.*mag.service.impl.*ServiceImpl.save(..))")
private void pointcut01(){}
@Pointcut("execution(* com.woniuxy.stall.*mag.service.impl.*ServiceImpl.update(..))")
private void pointcut02(){}
@Pointcut("execution(* com.woniuxy.stall.*mag.service.impl.*ServiceImpl.delete(..))")
private void pointcut03(){}
/**
* 前置通知
* 作用:获得SqlSession
* @param joinPoint 连接点
*/
@Before("pointcut01() || pointcut02() || pointcut03()")
public void beforeAdvice(JoinPoint joinPoint){
System.out.println("前置通知,我运行在目标方法执行之前!");
Object obj = joinPoint.getTarget();//得到目标对象
System.out.println(obj);
String name = joinPoint.getSignature().getName();//得到目标方法
System.out.println(name);
Object[] args = joinPoint.getArgs();
System.out.println(Arrays.toString(args));//得到目标参数
}
/**
* 后置返回通知
* @param joinPoint
* @param res
*/
@AfterReturning(value = "pointcut01() || pointcut02() || pointcut03()",returning = "res")
public void afterReturnAdvice(JoinPoint joinPoint,Object res){
System.out.println("我是后置返回通知,我执行在目标方法正常执行之后!");
System.out.println(res);
}
/**
* 后置异常通知
* @param joinPoint
* @param res 异常
*/
@AfterThrowing(value="pointcut01() || pointcut02() || pointcut03()",throwing = "res")
public void afterThrowsAdvice(JoinPoint joinPoint,Exception res){
System.out.println("我是后置异常通知,我执行在目标方法抛出异常之后!");
System.out.println(res);
}
/**
* 后置通知
* @param joinPoint
*/
@After("pointcut01() || pointcut02() || pointcut03()")
public void afterAdvice(JoinPoint joinPoint){
System.out.println("我是后置通知,不管目标方法能否正常执行,我都执行!");
}
/**
* 环绕通知
* @param pjp
* @return
* @throws Throwable
*/
@Around("pointcut01() || pointcut02() || pointcut03()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("我是环绕通知,我最NB!属于无法无天的……");
Object[] args = pjp.getArgs();//原始的参数
args[0] = null;
Object result = pjp.proceed(args);//调用目标方法(如果不写,目标方法将不执行)
return result;
}
}
六、注解作为切入点
import java.lang.annotation.*;
/**
* 自定义一个日志注解
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
/**
* 菜单的名称
* @return
*/
String menuName() default "";
/**
* 操作类型:CUD
* @return
*/
String optType() default "";
}
step5:开启代理支持
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启容器的自动扫描-->
<context:component-scan base-package="com.woniuxy.stall"></context:component-scan>
<!--开启AOP的动态代理功能-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
三、注解作为切入点
import java.lang.annotation.*;
/**
* 自定义一个日志注解
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
/**
* 菜单的名称
* @return
*/
String menuName() default "";
/**
* 操作类型:CUD
* @return
*/
String optType() default "";
}
操作日志通知
import com.woniuxy.stall.anno.MyLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 操作日志
*/
@Aspect
@Component
public class OptLogAspect {
@AfterReturning(value = "@annotation(log)",returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result,MyLog log){
System.out.println("模块的名称:" + log.menuName());
System.out.println("操作的类型:"+log.optType());
System.out.println("操作的数据:" + Arrays.toString(joinPoint.getArgs()));
}
}