摘要:基于注解@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. 【ABAQUS模态动力学】Composite&abaqus 预应力模态分析&输出单元刚度矩阵

    参考 连接器振动脱落_abaqus重启动分析_显式隐式切换_插拔力预应力模态_TeeSim天深科技 Abaqus预应力模态分析 附Abaqus 分析用户手册材料卷下载- 技术邻 Natural fre ...

  2. 华为云windows server 2008 迁机遇到字符串问题

    问题 使用主机迁移服务迁移windows server 2008出现问题 2.按照教程安装Windows Agent(Python2)下载后,在源主机上运行agent-start.exe,输入ak后, ...

  3. 『Plotly实战指南』--架构与设计理念

    在数据科学和数据分析领域,数据可视化是理解数据和传达信息的关键环节. Python 作为最受欢迎的编程语言之一,拥有众多强大的可视化库,而 Plotly 无疑是其中的佼佼者. 本文将深入介绍 Plot ...

  4. 0基础的人关于C++多态产生的一系列疑问

    之前在面试的时候被问过懂不懂C++,懂不懂"多态".我之前搞科研一直在用Python,不会C++.完全没听过"多态"这个词,只听说过"多模态" ...

  5. MongoDB 复制集机制及原理

    复制集的作用 MongoDB 复制集的主要意义在于实现服务高可用. 它的现实依赖于两个方面的功能: 数据写入时将数据迅速复制到另一个独立节点上 在接受写入的节点发生故障时自动选举出一个新的代替节点 在 ...

  6. Go语言中的匿名函数和闭包,简单理解

    匿名函数是一种没有函数名的函数,即定义即使用:闭包作为一种携带状态的函数,我们可以简单地将它理解为"对象",因为它同时具备状态和行为. 匿名函数 匿名函数没有函数名,只有函数体,它 ...

  7. matplotlib -- 绘图操作 -- 数据分析三剑客

    博客地址:https://www.cnblogs.com/zylyehuo/ 开发环境 anaconda 集成环境:集成好了数据分析和机器学习中所需要的全部环境 安装目录不可以有中文和特殊符号 jup ...

  8. 关于IPMP

    国际项目经理资质认证(International Project Manager Professional,简称IPMP)是国际项目管理协会(International Project Managem ...

  9. Oracle11g的SGA和PGA设置为多大最合适?

    Oracle官方文档推荐: MEMORY_TARGET=物理内存 x 80% MEMORY_MAX_SIZE=物理内存 x 80% 对于OLTP系统:  SGA_TARGET=(物理内存 x 80%) ...

  10. 从零开始:基于 PyTorch 的图像分类模型

    摘要:本文详细记录了使用 PyTorch 从零搭建一个图像分类模型的过程,涵盖卷积神经网络(CNN).数据预处理.模型设计.训练调试与优化.通过对 CIFAR-10 数据集的处理实践,结合经典文献和 ...