详解@PointCut及Spring AOP

发布时间:2025-12-10 11:44:32 浏览次数:26

一、@PointCut 详解案例

案例gitee地址:https://gitee.com/jsxlliar/spring-aop.git.
target
用来表示目标对象,即需要通过aop来增强的对象。

proxy
代理对象,target通过aop增强之后生成的代理对象。

AspectJ
AspectJ是一个面向切面的框架,和spring中的aop可以集成在一起使用,通过Aspectj提供的一些功能实现aop代理变得非常方便。

AspectJ使用步骤
1.创建一个@Aspect标注类
2.@Aspect标注的类中,通过@Pointcut定义切入点
3.@Aspect标注的类中,通过AspectJ提供的一些通知相关的注解定义通知

4.使用AspectJProxyFactory结合@Ascpect标注的类,来生成代理对象

案例一:AspectJ使用

pom依赖

<properties><spring.version>5.3.12</spring.version></properties><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>${spring.version}</version></dependency></dependencies> public class Service1 {public void m1(){System.out.println("我是 m1 方法");}public void m2(){System.out.println(1/0);System.out.println("我是 m2 方法");}} @Aspectpublic class Aspect1 {@Pointcut("execution(* com.jsxl.pointcut1.Service1.*(..))")public void pointcut1(){}@Before(value = "pointcut1()")public void before(JoinPoint joinPoint) {//输出连接点的信息System.out.println("前置通知," + joinPoint);}@AfterThrowing(value = "pointcut1()", throwing = "e")public void afterThrowing(JoinPoint joinPoint, Exception e) {//发生异常之后输出异常信息System.out.println(joinPoint + ",发生异常:" + e.getMessage());}} public class AopTest1 {public static void main(String[] args) {try {//对应目标对象Service1 target = new Service1();//创建AspectJProxyFactory对象AspectJProxyFactory proxyFactory = new AspectJProxyFactory();//设置被代理的目标对象proxyFactory.setTarget(target);//设置标注了@Aspect注解的类proxyFactory.addAspect(Aspect1.class);//生成代理对象Service1 proxy = proxyFactory.getProxy();//使用代理对象proxy.m1();proxy.m2();} catch (Exception e) {}}} execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
  • 其中带 ?号的 modifiers-pattern?,declaring-type-pattern?,hrows-pattern?是可选项
  • ret-type-pattern,name-pattern, parameters-pattern是必选项
  • modifier-pattern? 修饰符匹配,如public 表示匹配公有方法
  • ret-type-pattern 返回值匹配,* 表示任何返回值,全路径的类名等
  • declaring-type-pattern? 类路径匹配
  • name-pattern 方法名匹配,* 代表所有,set*,代表以set开头的所有方法
  • (param-pattern) 参数匹配,指定方法参数(声明的类型),(…)代表所有参数,(*,String)代表第一个参数为任何值,第二个为String类型,(…,String)代表最后一个参数是String类型
  • throws-pattern? 异常类型匹配
    举例
表达式匹配
public *.*(..)任何公共方法
* com.jsxl..IPointcut.*()com.jsxl包及所有子包下IPointcut接口中的任何无参方法
* com.jsxl..*.*(..)com.jsxl包及所有子包类中的任何方法(参数不限)
* com.jsxl..IPointcut.()com.jsxl包及所有子包下IPointcut接口中的任何只有一个参数的方法
* com.jsxl..IPointcut+.*()com.jsxl包及所有子包下IPointcut接口及子类型的任何无参方法
* Service.*(String)Service中只有1个参数且参数类型是String的方法
* Service.*(*,String)Service中只有2个参数且第二个参数类型是String的方法
* Sereice.*(..,tring)Service中最后1个参数类型是String的方法

AspectJ类型匹配的通配符:

  • * :匹配任何数量字符
  • ..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数(0个或者多个参数)
  • +:匹配指定类型及其子类型;仅能作为后缀放在类型模式后边

案例二:within

用法
within(类型表达式):目标对象target的类型是否和within中指定的类型匹配

匹配原则

target.getClass().equals(within表达式中指定的类型) public class S1 {public void m1() {System.out.println("我是m1");}public void m2() {System.out.println("我是m2");}} public class S2 extends S1 {@Overridepublic void m2() {super.m2();}public void m3() {System.out.println("我是m3");}} @Aspectpublic class Aspect2 {@Pointcut("within(S1)")// @Pointcut("within(S1+)")// @Pointcut("within(S2)")public void pc() {}@Before("pc()")public void beforeAdvice(JoinPoint joinpoint) {System.out.println(joinpoint);}} public class AopTest2 {public static void main(String[] args) {S2 target = new S2();AspectJProxyFactory proxyFactory = new AspectJProxyFactory();proxyFactory.setTarget(target);proxyFactory.addAspect(Aspect2.class);S2 proxy = proxyFactory.getProxy();proxy.m1();proxy.m2();proxy.m3();}}

@Pointcut(“within(S1)”)

@Pointcut(“within(S1+)”)

@Pointcut(“within(S2)”)

案例三:this

用法
this(类型全限定名):通过aop创建的代理对象的类型是否和this中指定的类型匹配;注意判断的目标是代理对象;this中使用的表达式必须是类型全限定名,不支持通配符。

匹配原则
如:this(x),则代理对象proxy满足下面条件时会匹配

x.getClass().isAssignableFrom(proxy.getClass()); interface I1 {void m1();}public class S3 implements I1 {@Overridepublic void m1() {System.out.println("我是m1");}} @Aspectpublic class Aspect3 {//匹配proxy是S3类型的所有方法@Pointcut("this(S3)")public void pc() {}@Before("pc()")public void beforeAdvice(JoinPoint joinpoint) {System.out.println(joinpoint);}} public class AopTest3 {public static void main(String[] args) {S3 target = new S3();AspectJProxyFactory proxyFactory = new AspectJProxyFactory();proxyFactory.setTarget(target);//获取目标对象上的接口列表Class<?>[] allInterfaces = ClassUtils.getAllInterfaces(target);//设置需要代理的接口proxyFactory.setInterfaces(allInterfaces);proxyFactory.addAspect(Aspect3.class);//设置cglib代理对象// proxyFactory.setProxyTargetClass(true);//获取代理对象Object proxy = proxyFactory.getProxy();//调用代理对象的方法((I1) proxy).m1();System.out.println("proxy是否是jdk动态代理对象:" + AopUtils.isJdkDynamicProxy(proxy));System.out.println("proxy是否是cglib代理对象:" + AopUtils.isCglibProxy(proxy));//判断代理对象是否是Service3类型的System.out.println(S3.class.isAssignableFrom(proxy.getClass()));}}
  • execution:用于匹配方法执行的连接点
  • within:用于匹配指定类型内的方法执行
  • this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也* 类型匹配
  • target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配
  • args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
  • @within:用于匹配所以持有指定注解类型内的方法
  • @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
  • @args:用于匹配当前执行的方法传入的参数持有指定注解的执行
  • @annotation:用于匹配当前执行方法持有指定注解的方法
  • bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法
表达式标签判断的对象判断规则(x:指表达式中指定的类型)
withintarget对象target.getClass().equals(表达式中指定的类型)
thisproxy对象x.getClass().isAssignableFrom(proxy.getClass());
targettarget对象x.getClass().isAssignableFrom(target.getClass());

二、AOP 案例

AOP(aspect-oriented programming)
什么是面向切面编程
面向切面编程是一种编程范式,试图解决横切关注点(cross-cutting concerns)的问题。面向切面编程(AOP)是对面向对象编程(OOP)的一种补充,它提供了一种不同的方式去思考程序的结构。
在 OOP 中最小的单元是类(class),而在 AOP 中最小的单元是切面(aspect)。

更通俗地讲就是,AOP 有助于我们将不同但是有必要的重复性代码重构为不同的模块。这么做的好处是,我们可以将这些重复性代码集中管理起来复用,而不是每次都要重复写一遍。

这种方法的好处是,代码将会变得更易于维护,从而将业务逻辑从杂乱的代码中脱离出来,专注于业务逻辑代码的开发。我们将这些不同的功能划分到不同的切面中。

一个切面是对杂乱地散落在各个类中的横切关注点的模块化。比如,集中日志记录或事务管理就是最好的例子。

方法名功能
Signature getSignature();*获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息*
Object[] getArgs();获取传入目标方法的参数对象
Object getTarget();获取被代理的对象
Object getThis();获取代理对象

Role 类

public class Role {private String name;public Role(String name) { this.name = name; }public String getName() { return name; }public void setName(String name) { this.name = name; }} public interface RoleService {void print(Role role);} @Componentpublic class RoleServiceImp implements RoleService {//连接点public void print(Role role) {System.out.println(role.getName());}}

方案一

@Aspectpublic class RoleAspect1 {@Before("execution(* com.jsxl.service.impl.RoleServiceImp.print()) && args(role ,sort)")public void before(Role role,int sort) {System.out.println(role.getName()+"Before传参");System.out.println("进入方法before...");}@After("execution(* com.jsxl.service.impl.RoleServiceImp.print())")public void after() {System.out.println("进入方法after...");}@AfterReturning("execution(* com.jsxl.service.impl.RoleServiceImp.print())")public void afterReturning() {System.out.println("进入方法afterReturning...");}@AfterThrowing("execution(* com.jsxl.service.impl.RoleServiceImp.print())")public void afterThrowing() {System.out.println("afterThrowing...");}}

方案二:切点

@Aspect//类需要使用@Aspect进行标注public class RoleAspect {//"+"表示RoleServiceImp的所有子类;defaultImpl 表示默认需要添加的新的类@DeclareParents(value = "com.jsxl.service.impl.RoleServiceImp+",defaultImpl = RoleVerifierImp.class)public RoleVerifier roleVerifier;//定义了一个切入点,可以匹配RoleServiceImp中所有方法@Pointcut("execution(* com.jsxl.service.impl.RoleServiceImp.*(..))")// @Pointcut("target(com.jsxl.service.impl.RoleServiceImp)")public void printCut() {}//@Before定义了一个前置增强,对切入点中的所有方法有效@Before("printCut() && args(role)")//args(role)传参public void before(JoinPoint joinPoint, Role role) {System.out.println("Before传入参数修改前name"+role.getName());role.setName("李四");System.out.println("Before传入参数修改后name"+role.getName());System.out.println("前置通知"+joinPoint);}//@After定义了一个后置增强,对切入点中的所有方法有效@After("printCut()")public void after() {System.out.println("进入方法after...");}//@AfterReturning定义了一个正常返回增强,对切入点中的所有方法有效@AfterReturning("printCut()")public void afterReturning() {System.out.println("进入方法afterReturning...");}//@AfterThrowing定义了一个异常返回增强,对切入点中的所有方法有效@AfterThrowing("printCut()")public void afterThrowing() {System.out.println("afterThrowing...");}//@AfterThrowing定义了一个环绕增强,对切入点中的所有方法有效@Around("printCut()")public void aroundfunc(ProceedingJoinPoint func) {System.out.println("执行环绕方法");try {func.proceed();} catch (Throwable e) {e.printStackTrace();}System.out.println("环绕方法执行完毕");}}

通过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:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.3.xsd"><beans><!--声明自动为spring容器中那些配置@aspect切面的bean创建代理,织入切面。--><aop:aspectj-autoproxy proxy-target-class="true"/><bean id="roleAspect" class="com.jsxl.aspect.RoleAspect"/><bean id="roleService" class="com.jsxl.service.impl.RoleServiceImp"/></beans></beans>

通过注解配置

@Configuration@EnableAspectJAutoProxy//自动代理@ComponentScan("com.jsxl")public class AopConfig {@Beanpublic RoleAspect1 getRoleAspect1(){ return new RoleAspect1(); }@Beanpublic RoleAspect getRoleAspect(){return new RoleAspect();}} public interface RoleVerifier {public abstract boolean verify(Role role);} public class RoleVerifierImp implements RoleVerifier {@Overridepublic boolean verify(Role role) {return role != null;}} public class SpringAopTest {//public static void main(String[] args) {//ApplicationContext context1 = new AnnotationConfigApplicationContext(AopConfig.class);//ApplicationContext context2 =new ClassPathXmlApplicationContext("applicationContext.xml");//RoleService bean = context2.getBean(RoleService.class);//Role role = new Role("张三");//bean.print(role);//role=null;//System.out.println("-----------------------------");//bean.print(role);//}public static void main(String[] args) {ApplicationContext context1 = new AnnotationConfigApplicationContext(AopConfig.class);ApplicationContext context2 =new ClassPathXmlApplicationContext("applicationContext.xml");RoleService bean = context2.getBean(RoleService.class);RoleVerifier roleVerifier = (RoleVerifier)bean;Role role = new Role("张三");if (roleVerifier.verify(role)){bean.print(role);}role=null;System.out.println("-----------------------------");if (roleVerifier.verify(role)) {bean.print(role);}}}

三、Aop应用

1.定义注解

定义注解检查是否登录

@Retention(RUNTIME)@Target({METHOD})public @interface CheckLogin {}

定义注解检查方法是否有权限

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface CheckActionRight {//要执行这个方法必须要有的权限String value();}

2.配置切面类(逻辑更具具体情况而定,以下逻辑仅供参考)

@Component@Aspectpublic class AuthAspect {@Autowiredprivate JWTConfig jwtConfig;@Autowiredprivate RoleService roleService;@Around("@annotation(com.jsxl.aop.anno.CheckLogin)")public Object beforeCheckLogin(ProceedingJoinPoint joinPoint) throws Throwable {// 获取请求上下文ServletRequestAttributes requestAttributes =(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// 获取请求requestHttpServletRequest request = requestAttributes.getRequest();// 获取请求头参数(X-Token)String token = request.getHeader("X-Token");if(StringUtils.isEmpty(token)==true){throw new AppException(ResponseEnum.HAS_NO_TOKEN);}// 解析(异常已做统一处理)LoginDTO loginDTO = jwtConfig.checkJwt(token);request.setAttribute("roleid",loginDTO.getRoleid());request.setAttribute("id",loginDTO.getId());// 让目标方法执行return joinPoint.proceed();}@Around("@annotation(com.jsxl.aop.anno.CheckActionRight)")@ApiOperation("获取权限")public Object beforeCheckRight(ProceedingJoinPoint joinPoint) throws Throwable {ServletRequestAttributes requestAttributes =(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();String token = request.getHeader("X-Token");if(StringUtils.isEmpty(token)==true){throw new AppException(ResponseEnum.HAS_NO_TOKEN);}LoginDTO loginDTO = jwtConfig.checkJwt(token);// 从角色id 获得这个角色拥有的所有行为权限码的集合List<String> codes = roleService.findRigthCodeByRoleid(loginDTO.getRoleid());// 获得切入点方法需要的权限码 记录再注解里面// joinPoint.getSignature()用来获取代理类和被代理类的信息。MethodSignature signature = (MethodSignature)joinPoint.getSignature();// 从切点上获取目标方法Method method = signature.getMethod();CheckActionRight annotation = method.getDeclaredAnnotation(CheckActionRight.class);String code = annotation.value();// 判断集合中是否包含权限码if (codes.contains(code)==false){throw new AppException(ResponseEnum.HAS_NO_RIGHT);}//调用目标方法return joinPoint.proceed();}}

3.Controller调用

@RestController@CrossOrigin@RequestMapping("/admin/role")@Api(description = "角色接口")public class AdminRoleController {@Autowiredprivate RoleService roleService;@GetMapping("/menus")@CheckLogin@ApiOperation("根据角色id找到对应的权限菜单")public R findRightsByRoleidForMenu(HttpServletRequest request) {Object roleid = request.getAttribute("roleid");List<MenuDTO> menus = roleService.findRightByRoleidForMenu(Integer.parseInt(roleid.toString()));return new R(ResponseEnum.SUCCESS, menus);}}

判断是否拥有该权限

@RestController@CrossOrigin@RequestMapping("/admin/right")@Api(description = "菜单那权限和动作权限接口")public class AdminRightController {@Autowiredprivate RightService rightService;@GetMapping("/search/check")@CheckActionRight("TEST_SEARCH")@ApiOperation("判断该用户是否有查询权限")public R CheckSearch() {return new R(ResponseEnum.SUCCESS, null);}@GetMapping("/audit/check")@CheckActionRight(value = "TEST_AUDIT")@ApiOperation("判断该用户是否有审批权限")public R CheckAaudit() {return new R(ResponseEnum.SUCCESS, null);}}

4.小结

ProceedingJoinPoint对象
ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中,
添加了
Object proceed() throws Throwable //执行目标方法
Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法

需要做网站?需要网络推广?欢迎咨询客户经理 13272073477