这是Spring Aop的第二篇,案例代码很详解,可以查看https://gitee.com/haimama/java-study/tree/master/spring-aop-demo。 阅读前,建议先阅读 Spring Aop 详解一

切入点表达式

execution

execution表达式是到方法级别,具体构成查看上一篇文章中 Spring Aop 详解一

完全不限制(不可用,报错)

正常情况下,我们可以写出这样不会编译报错的表达式,但是执行效果就呵呵了。不知道是不是规避这种不限制的规则。也不知道是不是我写错了,有搞明白的师兄可以指点一下。

@Before("execution(* *..*(..))")

限制到包的所有方法

@Before("execution(* demo.aop.service.*(..))") //demo.aop.service包
@Before("execution(* demo.aop.service..*(..))") //demo.aop.service包及子包
@Before("execution(* demo.aop.service..save*(..))")//demo.aop.service 包及子包中sava开头的方法

参数限制

@Before("execution(* demo.aop.service..*()) ")//demo.aop.service 包及子包中 不需要参数的方法  如save()方法

//demo.aop.service 包及子包中参数列表为 (int,String) 的方法,如save(int age, String name)方法
@Before("execution(* demo.aop.service..*(int,String)) ") //限制方法参数时,如果参数是复杂类型参数,需要将这个类型写完整:java.util.List
@Before("execution(* demo.aop.service..*(java.util.List)) ")

且或非

//表达式可以用 '&', '||' 和 '!'  做合并
//包含 demo.aop.controller 下所有类的所有方法 且 不包含 demo.aop.controller.PassportController的所有方法
@Before("execution(* demo.aop.controller..*(..)) " +
" && !execution(* demo.aop.controller.PassportController.*(..))"
+ " || execution(* demo.aop.service..*(..))"
)

within

within表示确切到类的所有方法,表达式只表达到类级别

//所有controller包里所有的类的方法
@Before("within(demo.aop.controller.*)") //所有controller包,及子包 里所有的类的方法
@Before("within(demo.aop.controller..*)") //within 确切到具体的类WithinTestController
@Before("within(demo.aop.controller.WithinTestController)")

bean

//通过bean制定 spring容器中的名为beanTestController的bean
//spring中的bean名字会默认小写首字母,或者显示的命名 @RestController("beanTestController")
@Before("bean(beanTestController)")

参数引用 args

如果我们在通知中需要获取到被切入方法(连接点)的参数,那么我们可以通过args表达式来引用。

引用

//参数引用,讲切入点的参数引入到通知里面去
args这里除了引用连接点中的参数之外,还有限制的作用,也就是它只会匹配拥有(String name, Integer age)参数的连接点
@Before("execution(* demo.aop.controller..*.*(..)) && args( name, age)")
public void cut2(String name, Integer age) {
log.info("参数引用,讲切入点的参数引入到通知里面去");
log.info(name);
log.info(age.toString());
}

限制

//限制参数列表的类型为 ( ),切入没有参数的方法
@Before("execution(* demo.aop.controller..*.*(..)) && args( )")
public void cut() {
log.info("限制参数列表的类型为 ()");
} //限制参数列表的类型为 ( String, Integer)
@Before("execution(* demo.aop.controller..*.*(..)) && args( String, Integer)")
public void cut1() {
log.info("限制参数列表的类型为 ( String, Integer)");
}

引入(Introduction)

上一篇中概念中提到了引入,具体的实现我们来看一个案例

  • 切面类DeclareParentsAspect.java
package demo.aop.aspect;

import demo.aop.introduction.CommonParent;
import demo.aop.introduction.CommonParentImpl;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component; @Aspect
@Component
@Slf4j
public class DeclareParentsAspect { //demo.spring.aop.service..* 代表demo.spring.aop.service包和子包下的所有类
//这样声明了service包中的类都会引入一个CommonParent父接口,并用CommonParentImpl实现,形成一个代理对象commonParent
@DeclareParents(value="demo.aop.service..*", defaultImpl= CommonParentImpl.class)
private CommonParent parent; @Before("execution (* demo.aop.service.UserService.*(..)) && this(commonParent)")
public void beforeUserService(CommonParent commonParent) { log.info(commonParent.getClass().toString());
commonParent.doSomething();
} }
  • 具体引入的接口的实现CommonParent.java CommonParentImpl.java
package demo.aop.introduction;

public interface CommonParent {
public void doSomething();
} package demo.aop.introduction; public class CommonParentImpl implements CommonParent {
@Override
public void doSomething() {
log.info("doSomething");
}
}
  • 测试接口 DeclareParentsTestController.java
package demo.aop.controller;

import demo.aop.service.RoleService;
import demo.aop.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
@Slf4j
public class DeclareParentsTestController { @Autowired
UserService userService; @Autowired
RoleService roleService; @GetMapping("/declare")
public String test(){
userService.save();
return "ok";
} }

访问http://localhost:8080/declare 运行结果如下

2020-10-21 10:22:53.645   demo.aop.aspect.DeclareParentsAspect     : class demo.aop.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$58f5d27e
2020-10-21 10:22:53.645 demo.aop.introduction.CommonParentImpl : 引入(Introduction)测试 doSomething

this

表达式中的this在前文 https://www.cnblogs.com/mxjhaima/p/13834298.html#切入点表达式 。这里和args类似,但这里是直接把service包中的代理类引入进来,我们输出了 commonParent的类型是demo.aop.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$58f5d27e代理对象,并且这个代理对象通过代理得到了doSomething()方法。

@DeclareParents

该注解传入一个类似切入点表达式的表达式,让所有的demo.aop.service中的接口的spring bean都成为了代理类了。

接下来我们证明所有的的service都已经成了代理类了。我们修改一下测试接口如下

package demo.aop.controller;

import demo.aop.service.RoleService;
import demo.aop.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
@Slf4j
public class DeclareParentsTestController { @Autowired
UserService userService; @Autowired
RoleService roleService; @GetMapping("/declare")
public String test(){
userService.save();
return "ok";
} @GetMapping("/declare2")
public String test2(){
log.info(roleService.getClass().toString());
CommonParent commonParent=(CommonParent)roleService;
commonParent.doSomething();
roleService.save();
return "ok";
}
}

访问http://localhost:8080/declare2 运行结果如下

2020-10-21 11:04:04.976   d.a.c.DeclareParentsTestController       : class demo.aop.service.impl.RoleServiceImpl$$EnhancerBySpringCGLIB$$e316d514
2020-10-21 11:04:04.977 demo.aop.introduction.CommonParentImpl : 引入(Introduction)测试 doSomething

解读如下

  • execution (* demo.aop.service.UserService.*(..))使得beforeUserService通知不会再roleService的save方法执行。
  • @DeclareParents(value="demo.aop.service..*", defaultImpl= CommonParentImpl.class)会使得 roleService 成为一个引入了CommonParent接口实现的 代理对象
  • 因为roleService是代理对象(划重点),并且是实现了CommonParent接口,所以能够类型转换再调用doSomething()方法

目标对象引用 target

接着在DeclareParentsAspect.java中添加如下方法

@Before("execution (* demo.aop.service..*.*(..)) && target(o)")
public void beforeUserService(Object o) {
log.info(o.getClass().toString()); }

访问http://localhost:8080/declare2 运行结果如下

2020-10-21 11:20:45.329   demo.aop.aspect.DeclareParentsAspect     : class demo.aop.service.impl.RoleServiceImpl

通过target表达式,我们可以引用得到目标对象目标对象就是被代理的对象,也就是未被切入,也没被代理的对象(官方文档:这个对象永远是一个被代理(proxied)对象)。

连接点对象(JoinPoint)

在前文中我们使用了环绕通知,而环绕通知中用到了连接点ProceedingJoinPoint,ProceedingJoinPoint是JoinPoint的子类。其他的通知我们可以使用JoinPoint来引入。

  • 切面 JoinPointAspect.java
package demo.aop.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component; import java.util.Arrays; @Aspect
@Component
public class JoinPointAspect { //JoinPoint 接口提供了一系列有用的方法,
// 比如 getArgs()(返回方法参数)、
// getThis()(返回代理对象)、
// getTarget()(返回目标对象)、
// getSignature()(返回正在被通知的方法相关信息)和 toString() (打印出正在被通知的方法的有用信息)
@Before("execution (* demo.aop.controller.JoinPointTestController.before(..))")
public void jp3(JoinPoint point) {
System.out.println(Arrays.toString(point.getArgs()));
System.out.println(point.getThis().getClass());
System.out.println(point.getTarget().getClass());
} }
  • 测试接口 JoinPointTestController.java
package demo.aop.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
public class JoinPointTestController { @GetMapping("/join/point/Before")
public String before(String name){
return "ok";
}
}

下文预告

  • 通知优先级
  • @ControllerAdvice 实现统一错误处理

本文完整代码参考 https://gitee.com/haimama/java-study/tree/master/spring-aop-demo

spring aop翻译文档http://shouce.jb51.net/spring/aop.html

Spring Aop 详解二的更多相关文章

  1. spring AOP详解二

    AOP实例(通过Proxy代理模式) Spring AOP使用纯java实现,不需要专门的编译过程和类装载器,它在运行期间通过代理方式向目标类织入增强代码,它更侧重于提供一种和Spring IoC容器 ...

  2. 【转载】Spring AOP详解 、 JDK动态代理、CGLib动态代理

    Spring AOP详解 . JDK动态代理.CGLib动态代理  原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html AOP是Aspec ...

  3. Spring AOP详解(转载)所需要的包

    上一篇文章中,<Spring Aop详解(转载)>里的代码都可以运行,只是包比较多,中间缺少了几个相应的包,根据报错,几经百度搜索,终于补全了所有包. 截图如下: 在主测试类里面,有人怀疑 ...

  4. Spring AOP详解及简单应用

    Spring AOP详解   一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...

  5. 转:Spring AOP详解

    转:Spring AOP详解 一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...

  6. [Spring学习笔记 5 ] Spring AOP 详解1

    知识点回顾:一.IOC容器---DI依赖注入:setter注入(属性注入)/构造子注入/字段注入(注解 )/接口注入 out Spring IOC容器的使用: A.完全使用XML文件来配置容器所要管理 ...

  7. Spring系列(四):Spring AOP详解

    一.AOP是什么 AOP(面向切面编程),可以说是一种编程思想,其中的Spring AOP和AspectJ都是现实了这种编程思想.相对OOP(面向过程编程)来说,提供了另外一种编程方式,对于OOP过程 ...

  8. Spring Aop 详解一

    Aop 是一个编程思想,最初是一个理论,最后落地成了很多的技术实现. 我们写一个系统,都希望尽量少写点儿重复的东西.而很多时候呢,又不得不写一些重复的东西.比如访问某些方法的权限,执行某些方法性能的日 ...

  9. Spring AOP详解 、 JDK动态代理、CGLib动态代理

    AOP是Aspect Oriented Programing的简称,面向切面编程.AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理以及日志记录.AOP将这些分散在各个业务逻辑中的代码 ...

随机推荐

  1. [LeetCode]739. 每日温度(单调栈)

    题目 根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数.如果之后都不会升高,请在该位置用 0 来代替. 例如,给定一个列表 temperatures ...

  2. leetcode560题解【前缀和+哈希】

    leetcode560.和为K的子数组 题目链接 算法 前缀和+哈希 时间复杂度O(n). 在解决这道题前需要先清楚,一个和为k的子数组即为一对前缀和的差值. 1.我们假设有这么一个子数组[i,j]满 ...

  3. SpringBoot框架:通过AOP和自定义注解完成druid连接池的动态数据源切换(三)

    一.引入依赖 引入数据库连接池的依赖--druid和面向切面编程的依赖--aop,如下所示: <!-- druid --> <dependency> <groupId&g ...

  4. ansible-doc到底有多好用,助你玩转各种模块

    #使用ansible-doc:查看各种模块的帮助 #命令格式: ansible-doc -l #列出所有的模块列表 ansible-doc -s 模块名 #查看指定模块的参数 ansible-doc ...

  5. redis 的基本使用

    Linux上查看redis是否运行 ps -ef | grep redis  还能查看到Linux的安装路径. set  rediskey  redisvalue  在redis里面存储了一个名称为r ...

  6. spring的AspectJ基于XML和注解(前置、后置、环绕、抛出异常、最终通知)

    1.概念 (1)AspectJ是一个基于Java语言的AOP框架 (2)Spring2.0以后新增了对AspectJ切入点表达式的支持 (3)AspectJ是AspectJ1.5的新增功能,通过JDK ...

  7. PHP判断是否是微信浏览器访问的方法

    PHP判断是否是微信浏览器访问的方法 PHP判断是否是微信浏览器访问的方法 都是干货,微信开发可能需要用到,留着日后COPY. public function isWeichatBrowser() { ...

  8. 微信小程序-实现文字跑马灯-wepy

    百度蛮多例子的,但是代码太长懒得看了 前言 要实现跑马灯主要就是获得判断开始定界和结束定界, 1.9.3新增的wxml操作接口 就可以拿到节点长宽等属性,当然你也可以直接用 文字数量 * 文字大小(注 ...

  9. Linux下安装ZooKeeper-3.5.6

    下载 官网下载地址是https://www.apache.org/dyn/closer.cgi/zookeeper,下载apache-zookeeper-3.5.6-bin.tar.gz.   sta ...

  10. spring-boot-route(五)整合Swagger生成接口文档

    目前,大多数公司都采用了前后端分离的开发模式,为了解决前后端人员的沟通问题,后端人员在开发接口的时候会选择使用swagger2来生成对应的接口文档,swagger2提供了强大的页面调试功能,这样可以有 ...