摘要:基于注解@Aspect实现Spring AOP切面编程。

基于注解@Aspect实现Spring AOP

  Spring AOP使用的是动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象在特定的切点做了增强处理,包含了目标对象的全部方法,并且回调原对象的方法。

     

  Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类实现一个接口,她的核心是InvocationHandler接口和Proxy类。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类。注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

  如果想要实现拦截private方法,可以使用原生 AspectJ 编译时织入。Spring使用纯Spring AOP(只能拦截public/protected/包)都是无法被拦截private方法的,因为子类无法覆盖;包级别能被拦截的原因是,如果子类和父类在同一个包中是能覆盖的。cglib动态代理可以拦截 public/protected/包级别方法(即这些方法都是能代理的)。因为AOP底层是动态代理,jdk动态代理是代理接口,私有方法必然不会存在在接口里,所以就不会被拦截;cglib是生成被代理类的子类,private的方法照样不会出现在子类里,也不能被拦截。关于Spring AOP的更多基本概念请参考《Spring AOP 面向切面编程之AOP是什么》。

  AspectJ是一个面向切面的框架,它扩展了java语言,定义了AOP语法,能够在编译期提供代码的织入。这里创建一个基于注解@Aspect的demo,实现AOP切面。定义测试API getUserById:

    @Autowired
private UserService userService; @GetMapping(value ="/getUserById", produces = "application/json; charset=utf-8")
public User getUserById(Long userId) {
User user = userService.getUserById(userId);
return user;
}

  编写实现类:

package com.eg.wiener.service;

import com.eg.wiener.dto.User;

public interface UserService {
User getUserById(Long userId);
}  === 我是分割线 ===  package com.eg.wiener.service.impl; import com.eg.wiener.dto.User;
import com.eg.wiener.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; import java.util.UUID; @Service
public class UserServiceImpl implements UserService {
private static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); @Override
public User getUserById(Long userId) {
logger.info("--------**测试bean增强**-----------");
User user = new User();
user.setId(userId);
user.setAddress("测试地址是 " + UUID.randomUUID().toString());
logger.info("类信息: {}", user.getClass());
return user;
}
}

  在切面UserServiceAspect中使用@Pointcut注解声明切点表达式,命名为userPointcut(),然后在各类通知类型中引用此切点表达式。

package com.eg.wiener.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; @Aspect
@Component
public class UserServiceAspect {
private static Logger logger = LoggerFactory.getLogger(UserServiceAspect.class);
private final String POINT_CUT = "execution(* com.eg.wiener.service.UserService.getUserById(..))"; @Pointcut(POINT_CUT)
public void userPointcut() {
} @Before("userPointcut()")
public void beforeTest() {
System.out.println("before ...");
} @After("userPointcut()")
public void afterTest() {
System.out.println("After ...");
} @AfterReturning("userPointcut()")
public void afterReturningTest() {
System.out.println("AfterReturning ...");
} @Around("userPointcut()")
public Object aroundTest(ProceedingJoinPoint pj) {
long start = System.currentTimeMillis();
Object obj = new Object();
try {
System.out.println("Around start ...");
obj = pj.proceed(pj.getArgs());
System.out.println("Around end ...耗时:" +
(System.currentTimeMillis() - start));
} catch (Exception e) {
logger.error("------ e -----,", e);
} catch (Throwable throwable) {
logger.error("----- throwable ------,", throwable);
}
return obj;
}
}

  POINT_CUT具体到了函数getUserById,表示切入点就是userService中的函数getUserById,不拦截其它接口。org.aspectj.lang.ProceedingJoinPoint为JoinPoint的子类,重点看一下其中的如下两个方法:

public Object proceed() throws Throwable;

public Object proceed(Object[] args) throws Throwable;

执行proceed() 意为调用下一个advice或者执行目标方法,返回结果为目标方法返回值,因此,如有必要,可以在这里修改方法的返回值。proceed(Object[] args)中的args为目标方法的参数,可以通过修改参数改变方法入参。

  在编写环绕通知的时候,如果被增强函数有返回值,我们需要返回执行结果;否则,API无法返回我们的业务数据,导致竹篮打水一场空。启动项目,请求getUserById,控制台打印结果如下:

Around start ...

before ...

2021-02-17 10:55:21.259 INFO 9000 --- [nio-8087-exec-4] c.e.wiener.service.impl.UserServiceImpl : --------测试bean增强-----------

2021-02-17 10:55:21.259 INFO 9000 --- [nio-8087-exec-4] c.e.wiener.service.impl.UserServiceImpl : 类信息: class com.eg.wiener.dto.User

AfterReturning ...

After ...

Around end ...耗时:1

  可以看到五种通知类型和具体实现类的执行顺序为:around、before、实现类、afterReturning、after和around。

  使用环绕通知监控API执行时间。在环绕通知中记录API执行完毕所耗费的时间,可以方便的监控和统计哪些API性能低,需要重构,以保证系统稳健运行。

  既然AOP已经生效了,那么,问题来了,究竟AspectJ是如何在没有修改UserServiceImpl类的情况下为UserServiceImpl类增加新功能的呢?这是注解@AspectJ在内存中临时为方法生成一个AOP对象,这个AOP对象在特定的切点做了增强处理,包含了目标对象的全部方法,并且回调原对象的方法,即运行的就是经过增强之后的AOP对象。

小结

  AspectJ在编译时就增强了目标对象,Spring AOP的动态代理则是在每次运行时动态的增强,生成AOP代理对象,区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

Reference

基于注解@Aspect实现Spring AOP的更多相关文章

  1. Spring Aop(七)——基于XML配置的Spring Aop

    转发:https://www.iteye.com/blog/elim-2396043 7 基于XML配置的Spring AOP 基于XML配置的Spring AOP需要引入AOP配置的Schema,然 ...

  2. 基于 Annotation 拦截的 Spring AOP 权限验证方法

    基于 Annotation 拦截的 Spring AOP 权限验证方法 转自:http://www.ibm.com/developerworks/cn/java/j-lo-springaopfilte ...

  3. Spring7——开发基于注解形式的spring

    开发基于注解形式的spring SpringIOC容器的2种形式: (1)xml配置文件:applicationContext.xml; 存bean:<bean> 取bean: Appli ...

  4. 使用Spring框架入门四:基于注解的方式的AOP的使用

    一.简述 前面讲了基于XML配置的方式实现AOP,本文简单讲讲基于注解的方式实现. 基于注解的方式实现前,要先在xml配置中通过配置aop:aspectj-autoproxy来启用注解方式注入. &l ...

  5. 基于XML配置的spring aop增强配置和使用

    在我的另一篇文章中(http://www.cnblogs.com/anivia/p/5687346.html),通过一个例子介绍了基于注解配置spring增强的方式,那么这篇文章,只是简单的说明,如何 ...

  6. SSH深度历险(十) AOP原理及相关概念学习+AspectJ注解方式配置spring AOP

    AOP(Aspect Oriented Programming),是面向切面编程的技术.AOP基于IoC基础,是对OOP的有益补充. AOP之所以能得到广泛应用,主要是因为它将应用系统拆分分了2个部分 ...

  7. 基于注解方式@AspectJ的AOP

    启用对@AspectJ的支持 Spring默认不支持@AspectJ风格的切面声明,为了支持需要使用如下配置: <aop:aspectj-autoproxy/> 这样Spring就能发现@ ...

  8. 自定义注解实现(spring aop)

    1.基本概念 1.1 aop 即面向切面编程,优点是耦合性低,能使业务处理和切面处理分开开发,扩展和修改方面,当引入了注解方式时,使用起来更加方便. 1.2 应用场景 打日志.分析代码执行时间.权限控 ...

  9. 使用 Spring 2.5 基于注解驱动的 Spring MVC

    http://www.ibm.com/developerworks/cn/java/j-lo-spring25-mvc/ 概述 继 Spring 2.0 对 Spring MVC 进行重大升级后,Sp ...

  10. 使用 Spring 2.5 基于注解驱动的 Spring MVC--转

    概述 继 Spring 2.0 对 Spring MVC 进行重大升级后,Spring 2.5 又为 Spring MVC 引入了注解驱动功能.现在你无须让 Controller 继承任何接口,无需在 ...

随机推荐

  1. php如何处理表单和数据库字段不匹配

    // 假设从表单接收到的数据 $formData = [ 'first_name' => $_POST['first_name'], 'last_name' => $_POST['last ...

  2. 近1000 star,Forest 1.5.0 正式版发布

    简介 Forest是一个高层的.极简的轻量级HTTP调用API框架. 相比于直接使用Httpclient您不再用写一大堆重复的代码了,而是像调用本地方法一样去发送HTTP请求. 不需要调用HTTP底层 ...

  3. CICFlowMeter 使用方法

    前言 因实验需要提取流量特征,就找到了这个较为著名的流量特征提取工具 CICFlowMeter .例如 CIC-IDS-2017 数据集就是通过这个工具提取而来. 网络上的教程众说纷纭,但我始终是无法 ...

  4. cypress 在 typescript 项目中报错找不到 'tslib'

    原文链接:https://blog.jijian.link/2020-08-11/cypress-typescript-cannot-find-module-tslib/ cypress 在 type ...

  5. docker也一直发展

    docker也一直发展.但本答案仅仅作为向新人介绍docker的入门文章的话,在大方向上还是没问题的.本文仅仅是个docker入门介绍文章,用比较宏大的叙事来描述docker的面貌,如果想了解更细节或 ...

  6. 实现领域驱动设计 - 使用ABP框架 - 领域逻辑 & 应用逻辑

    领域逻辑 & 应用逻辑 如前所述,领域驱动设计中的业务逻辑分为两部分(层):领域逻辑和应用逻辑: 领域逻辑由系统的核心领域规则组成,应用逻辑实现应用特定的用例 虽然定义很明确,但实现起来可能并 ...

  7. 【SpringCloud】Consul服务注册与发现

    Consul服务注册与发现 Consul简介 是什么 https://www.consul.io/intro/index.html Consul是一开源的分布式服务发现和配置管理系统,由HashiCo ...

  8. study Rust-8【使用结构体的方法】

    1.方法 与函数类似:它们使用 fn 关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码.2.不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 tra ...

  9. vscode安装离线插件autopep8

    商店 从上面的链接进去,在visual studio code一栏开始搜索,我要的是autopep8,所以搜索得到的是这样的: 点进去后,是这个界面,然后我是离线下载,要的是拓展包,所以是下面操作 下 ...

  10. 云备份技术解析:云容灾 CT-CDR 关键技术介绍

    本文分享自天翼云开发者社区<云备份技术解析:云容灾 CT-CDR 关键技术介绍>,作者:沈****军 1.CDP+存储快照,实现秒级RPO (1)CDP技术:云容灾CT-CDR(Cloud ...