7. AOP 面向切面编程
SpringAOP : 批量对Spring容器中的Bean的方法做增强,并且这种增强不会与原来方法中的代码耦合
7.1 快速入门
需求: 要求所有类在调用前都输出 :方法被调用了
7.1.1 添加依赖
7.1.2
开启组件扫描创建切面类 在类上加上 @Component和@Aspect
@Component @Aspect public class MyAspect { }
使用@PointCut注解指定要增强的方法
@Pointcut("execution(* com.sangeng.service..*.*(..))") public void pt(){ }
方法上加上before
@Before("pt()") public void methodbefore(){ System.out.println("方法被调用了"); }
<br/>
7.2 核心概念
-
JointPoint 可以被增强到的点,在Spring中,这些点指的是方法
-
PointCut (切入点)指被增强的连接点(方法)
-
Advice(通知) 具体增强的代码
-
Target(目标对象) 被增强的对象
-
Aspect(切面) 切入点和通知的结合
-
Proxy(代理)一个类被AOP增强后,就产生一个结果代理类
<br/>
7.3 切点表达式
确定对哪些方法进行增强
execution(* com.sangeng.service.*.*(..)) 表示com.sangeng.service包下任意类,方法名任意,参数列表任意,返回值类型任意 execution(* com.sangeng.service..*.*(..)) 表示com.sangeng.service包及其子包下任意类,方法名任意,参数列表任意,返回值类型任意 execution(* com.sangeng.service.*.*()) 表示com.sangeng.service包下任意类,方法名任意,要求方法不能有参数,返回值类型任意 execution(* com.sangeng.service.*.delete*(..)) 表示com.sangeng.service包下任意类,要求方法不能有参数,返回值类型任意,方法名要求已delete开头
7.4 切点函数@annotation
首先自定义注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface InvokeLog { }
在切面类中
@Component @Aspect public class MyAspect { @Pointcut("@annotation(com.sangeng.aspect.InvokeLog)") public void pt(){ System.out.println("方法被调用了"); } }
在需要增强的个方法上
@InvokeLog public void deleteAll(){ System.out.println("PhoneService中deleteAll的核心代码"); }
<br/>
7.5 通知
-
@before 前置通知 目标方法执行前
-
@AfterReturning 返回后通知,在目标方法执行后执行,如果出现异常不会执行
-
@After 后置通知 在目标方法执行后执行,无论是否有异常都会执行
-
@AfterThrowing 异常通知 在目标方法抛出异常后执行
-
@Around 环绕通知 围绕着目标方法执行 可以进行全方位的增强
<br/>
7.6 获取被增强方法的信息
@Before("pt()") public void before(JoinPoint joinPoint){ MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 被增强方法的方法签名 signature.getName(); // 被增强方法的方法名 signature.getReturnType(); // 返回值类型 Method method = signature.getMethod(); // 获取方法对象 method.invoke(); // 执行目标方法 }
案例
@Component @Aspect public class PrintLogAspect { //对哪些方法增强 @Pointcut("execution(* com.sangeng.service..*.*(..))") public void pt(){} //怎么增强 @Before("pt()") public void printLog(JoinPoint joinPoint){ //输出 被增强的方法所在的类名 方法名 调用时传入的参数 joinPoint.getSignature().getName() joinPoint.getArgs() MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //类名 String className = signature.getDeclaringTypeName(); //方法名 String methodName = signature.getName(); //调用时传入的参数 Object[] args = joinPoint.getArgs(); System.out.println(className+"=="+methodName+"======"+ Arrays.toString(args)); } }
<br/>
获取异常对象和返回值返回值
@AfterReturning(value = "pt()",returning = "ret") // returning属性制定了把目标方法的返回值赋值给下面方法的参数ret public void afterReturning(JoinPoint joinPoint,Object ret){ System.out.println("afterReturning"); }
异常对象
@AfterThrowing(value = "pt()",throwing = "e") public void afterThrowing(JoinPoint joinPoint,Throwable e){ String message = e.getMessage(); System.out.println("afterThrowing"); }
环绕通知获取所有信息
@Around("pt()") public Object around(ProceedingJoinPoint pjp){ //获取参数 Object[] args = pjp.getArgs(); MethodSignature signature = (MethodSignature) pjp.getSignature(); // 方法签名 Object target = pjp.getTarget(); // 被增强的对象 Object ret = null; try { ret = pjp.proceed();// 相当于目标方法的执行 // ret就是被增强方法的返回值 System.out.println(ret); } catch (Throwable throwable) { throwable.printStackTrace(); // 异常对象 System.out.println(throwable.getMessage()); } return ret; }
7.7 应用案例
@Controller public class AIController /*implements IAIContoller*/{ //AI自动回答 public String getAnswer(String question){ //AI核心代码 价值10个亿 String str = question.replace("吗", ""); str = str.replace("?","!"); return str; } //AI算命 @Crypt public String fortuneTelling(String name){ System.out.println(name); //AI算命核心代码 String[] strs = {"女犯伤官把夫克,旱地莲花栽不活,不是吃上两家饭,也要刷上三家锅。","一朵鲜花头上戴,一年四季也不开,一心想要花开时,采花之人没到来。","此命生来脾气暴,上来一阵双脚跳,对你脾气啥都好,经常与人吵和闹。"}; int index = name.hashCode() % 3; return strs[index]; } }
自定义注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Crypt { }
切面类
@Component @Aspect public class CryptAspect { //确定切点 @Pointcut("@annotation(com.sangeng.aspect.Crypt)") public void pt(){ } }
定义通知
@Component @Aspect public class CryptAspect { //确定切点 @Pointcut("@annotation(com.sangeng.aspect.Crypt)") public void pt(){ } //定义通知 @Around("pt()") public Object crypt(ProceedingJoinPoint pjp) { //获取去目标方法调用时的参数 Object[] args = pjp.getArgs(); //对参数进行解密 解密后传入目标方法执行 String arg = (String) args[0]; String s = CryptUtil.AESdecode(arg);//解密 args[0] = s; Object proceed = null; String ret = null; try { proceed = pjp.proceed(args);//目标方法调用 //目标方法执行后需要获取到返回值 ret = (String) proceed; //对返回值加密后进行真正的返回 ret = CryptUtil.AESencode(ret); } catch (Throwable throwable) { throwable.printStackTrace(); } return ret; } }
7.8 多切面顺序问题
多个切面的情况,可能要控制切面的顺序注解配置 : 在切面类上加上@Order注解控制顺序xml排至 : 通过在配置文件中的配置顺序配置
注解配置
自定义注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Crypt { }
切面1
@Component @Aspect @Order(1) public class CryptAspect { //确定切点 @Pointcut("@annotation(com.sangeng.aspect.Crypt)") public void pt(){ } //定义通知 @Around("pt()") public Object crypt(ProceedingJoinPoint pjp) { //获取去目标方法调用时的参数 Object[] args = pjp.getArgs(); //对参数进行解密 解密后传入目标方法执行 String arg = (String) args[0]; String s = CryptUtil.AESdecode(arg);//解密 args[0] = s; Object proceed = null; String ret = null; try { proceed = pjp.proceed(args);//目标方法调用 //目标方法执行后需要获取到返回值 ret = (String) proceed; //对返回值加密后进行真正的返回 ret = CryptUtil.AESencode(ret); } catch (Throwable throwable) { throwable.printStackTrace(); } return ret; } }
切面2
@Component @Aspect @Order(2) public class APrintLogAspect { //对哪些方法增强 @Pointcut("execution(* com.sangeng.controller..*.*(..))") public void pt(){} //怎么增强 @Before("pt()") public void printLog(JoinPoint joinPoint){ //输出 被增强的方法所在的类名 方法名 调用时传入的参数 joinPoint.getSignature().getName() joinPoint.getArgs() MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //类名 String className = signature.getDeclaringTypeName(); //方法名 String methodName = signature.getName(); //调用时传入的参数 Object[] args = joinPoint.getArgs(); System.out.println(className+"=="+methodName+"======"+ Arrays.toString(args)); } }
优先级 1 > 2 > 3······
7.9 AOP原理-动态代理
JDK动态代理
public static void main(String[] args) { AIControllerImpl aiController = new AIControllerImpl(); // String answer = aiController.getAnswer("三连了吗?"); // System.out.println(answer); //使用动态代理增强getAnswer方法 //1.JDK动态代理 // 获取类加载器 ClassLoader cl = Demo.class.getClassLoader(); // 被代理类所实现接口的字节码对象数组 Class<?>[] interfaces = AIControllerImpl.class.getInterfaces(); AIController proxy = (AIController) Proxy.newProxyInstance(cl, interfaces, new InvocationHandler() { // 使用代理对象的方法时 会调用到invoke public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // proxy 是代理对象, method 是当前被调用的方法封装的Method对象, args 是调用方法时传入的参数 // 调用被代理对象的对应方法 // 判断 当前调用的是否是getAnswer方法 if(method.getName().equals("getAnswer")){ System.out.println("增强"); } Object ret = method.invoke(aiController, args); return ret; } }); String answer = proxy.getAnswer("三连了吗?"); System.out.println(proxy.fortuneTelling("张三")); // System.out.println(answer); }
Cglib动态代理
public static void main(String[] args) { Enhancer enhancer = new Enhancer(); //设置父类的字节码对象 enhancer.setSuperclass(AIControllerImpl.class); enhancer.setCallback(new MethodInterceptor() { // 使用代理对象执行方法是都会调用到intercept方法 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 判断当前调用的方法是不是getAnswer方法 如果是进行增强 if ("getAnswer".equals(method.getName())){ System.out.println("被增强了"); } //调用父类中对应的方法 Object ret = methodProxy.invokeSuper(o, objects); return ret; } }); //生成代理对象 AIControllerImpl proxy = (AIControllerImpl) enhancer.create(); System.out.println(proxy.getAnswer("你好吗?")); // System.out.println(proxy.fortuneTelling("你好吗?")); }
JDK的动态代理要求被代理的类必须实现接口,生成的代理对象相当于是被代理对象的兄弟
Cglib的动态代理不要求被代理的类要实现接口,生成的代理对象相当于被代理类的子类对象
Spring的AOP默认是JDK的动态代理,如果是用不了JDK的动态代理才会去用Cglib的动态代理
8. Spring声明式事务
8.1 注解实现声明式事务
转账案例
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED) public void transfer(Integer outId, Integer inId, Double money) { //增加 accoutDao.updateMoney(inId,money); //减少 accoutDao.updateMoney(outId,-money); }
事务传播行为 propagation
propagation = Propagation.REQUIRES_NEW
外层方法有事务,内层也新建事务,外层没有事务同样新建
隔离级别ioslation
isolation = Isolation.READ_COMMITTED
只读事务
@Transactional(readOnly = true) public void log() { System.out.println("打印日志"); int i = 1/0; }