springaop的使用_Spring注解

springaop的使用_Spring注解目录SpringAOP简介AOP概念SpringAOP简单流程图SpringAOP之Annotation前置通知(Beforeadvice)返回后通知(Afterreurningadvice)抛出异常后通知(Afterthrowingadvice)后置通知(After(finally)advice)环绕通知(Aroundadvice)引入…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

目录

SpringAOP简介

AOP概念

Spring AOP简单流程图

Spring AOP之Annotation

前置通知(Before advice)

返回后通知(After reurning advice)

抛出异常后通知(After throwing advice)

后置通知(After (finally) advice)

环绕通知(Around advice)

引入(Introduction)

SpringAOP之XML

AOP日志实现

参考文献


SpringAOP简介

    面向切面编程(Aspect Oriented Programming)提供了另一种角度来思考程序的结构,通过这种方式弥补面向对象编程(Object Oriented Programming)的不足。除了类以外,AOP提供了切面,切面对关注点进行模块化,例如横切多个类型和对象的事务管理(这些关注点术语通常称作横切(crosscutting)关注点)。Spring AOP是Spring的一个重要组件,但是Spring IOC并不依赖于Spring AOP,这意味着你可以自由选择是否使用AOP,AOP提供了强大的中间件解决方案,这使得Spring IOC更加完善。我们可以通过AOP来实现日志监听,事务管理,权限控制等等。

AOP概念

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是Java应用程序中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用通过类(基于模式(XML)的风格)或者在普通类中以@Aspect注解(AspectJ风格)来实现。
  • 连接点(Join point):程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中一个连接点总是代表一个方法的执行。个人理解:AOP拦截到的方法就是一个连接点。通过声明一个org.aspectj.lang.JoinPoint类型参数我们可以在通知(Advice)中获得连接点的信息。这个在稍后会给出案例。
  • 通知(Advice):在切面(Aspect)的某个特定连接点上(Join point)执行的动作。通知的类型包括”around”,”before”,”after”等等。通知的类型将在后面进行讨论。许多AOP框架,包括Spring 都是以拦截器作为通知的模型,并维护一个以连接点为中心的拦截器链。总之就是AOP对连接点的处理通过通知来执行。个人理解:Advice指当一个方法被AOP拦截到的时候要执行的代码。
  • 切入点(Pointcut):匹配连接点(Join point)的断言。通知(Advice)跟切入点表达式关联,并在与切入点匹配的任何连接点上面运行。切入点表达式如何跟连接点匹配是AOP的核心,Spring默认使用AspectJ作为切入点语法。个人理解:通过切入点的表达式来确定哪些方法要被AOP拦截,之后这些被拦截的方法会执行相对应的Advice代码。
  • 引入(Introduction):声明额外的方法或字段。Spring AOP允许你向任何被通知(Advice)对象引入一个新的接口(及其实现类)。个人理解:AOP允许在运行时动态的向代理对象实现新的接口来完成一些额外的功能并且不影响现有对象的功能。
  • 目标对象(Target object):被一个或多个切面(Aspect)所通知(Advice)的对象,也称作被通知对象。由于Spring AOP是通过运行时代理实现的,所以这个对象永远是被代理对象。个人理解:所有的对象在AOP中都会生成一个代理类,AOP整个过程都是针对代理类在进行处理。
  • AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能),在Spring中AOP可以是JDK动态代理或者是CGLIB代理。
  • 织入(Weaving):把切面(aspect)连接到其他的应用程序类型或者对象上,并创建一个被通知对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯AOP框架一样,在运行时完成织入。个人理解:把切面跟对象关联并创建该对象的代理对象的过程。

通知(Advice)的类型:

  • 前置通知(Before advice):在某个连接点(Join point)之前执行的通知,但这个通知不能阻止连接点的执行(除非它抛出一个异常)。
  • 返回后通知(After returning advice):在某个连接点(Join point)正常完成后执行的通知。例如,一个方法没有抛出任何异常正常返回。
  • 抛出异常后通知(After throwing advice):在方法抛出异常后执行的通知。
  • 后置通知(After(finally)advice):当某个连接点(Join point)退出的时候执行的通知(不论是正常返回还是发生异常退出)。
  • 环绕通知(Around advice):包围一个连接点(Join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

Spring AOP简单流程图

springaop的使用_Spring注解

    上图为我个人对AOP流程的一个理解。我把面向对象的过程从一个HttpRquest到访问数据库DB的整个流程看做是一条直线。AOP定义了一个切面(Aspect),一个切面包含了切入点,通知,引入,这个切面上定义了许多的切入点(Pointcut),一旦访问过程中有对象的方法跟切入点匹配那么就会被AOP拦截。此时该对象就是目标对象(Target Object)而匹配的方法就是连接点(Join Point)。紧接着AOP会用过JDK动态代理或者CGLIB生成一个目标对象的代理对象(AOP proxy),这个过程就是织入(Weaving)。这个时候我们就可以按照我们的需求对连接点进行一些拦截处理。可以看到,我们可以引入(Introduction)一个新的接口,让代理对象来实现这个接口来,以实现额外的方法和字段。也可以在连接点上进行通知(Advice),通知的类型包括了前置通知,返回后通知,抛出异常后通知,后置通知,环绕通知。最后也是最骚的是整个过程不会改变代码原有的逻辑。下面我将通过简单的案例对Spring AOP进行演示,过程会包括XML以及Annotation。实例结构基本跟基于SpringMVC+Spring+Hibernate+Maven+Bootstrap的简单Demo以及SpringMVC整合Mybatis+Maven+Bootstrap的简单Demo一致不一样的地方我会贴出代码。

Spring AOP之Annotation

    首先在bean.xml文件中添加<aop:aspectj-autoproxy />开启Spring对@Aspect的支持。

       <context:component-scan base-package="com.ctc" />//IOC自动扫包
       <aop:aspectj-autoproxy />//使用AOP注解

    声明一个切面,在类UserAspect上加上@Aspect注解。并定义了两个切入点addLog()以及skipMethod()。Spring主要使用的execetion来匹配连接点。此外还有within,this,target等等,这边不再解释有需要可以参考官方文档。此外Spring文档要求定义切入点(Pointcut)的方法的返回值必须的void类型。但是我自己测试了下其他返回类型,还是可以正常使用。不知道是不是因为测试环境的原因,总之就按照官方的来吧。

@Aspect
public class UserAspect {
	
	//匹配所有ServiceImpl包下面的所有类的所有方法
	@Pointcut("execution(* com.ctc.ServiceImpl.*.*(..))")
	public void addLog(){}
	
	
	//@Pointcut("execution(public * *(..))")
	//public void skipMethod(){}

}

    接着定义一个Advice类在上面加上@Aspect注解,同时必须在类上添加注解@Component否则Spring就扫描不到这个类。下面将对5种通知类型以及一个引入(Introduction)AgeGroup进行演示。

@Aspect
@Component
public class LogAdvice {
	
/*	@DeclareParents(value="com.ctc.ServiceImpl.*+",
			defaultImpl=Adult.class)
	public static AgeGroup ageGroup;*/

    //所有的通知都可以使用这种方式,直接把Pointcut跟Advice连接起来,但是为了更好的理解前文的概念以及图片,这边分开定义。
    //@Before("execution(* com.ctc.ServiceImpl.*.*(..))");
    @Before("com.ctc.AspectJ.UserAspect.addLog()")
    public void before(){
        System.out.println("LogAdvice before advice ");
    }
	
/*	@AfterReturning("com.ctc.AspectJ.UserAspect.addLog()")
	public void AfterReturning(){
		System.out.println("LogAdvice after returning advice ");
	}
	
	@AfterThrowing("com.ctc.AspectJ.UserAspect.addLog()")
	public void AfterThrowing(){
		System.out.println("LogAdvice after throwing advice ");
	}
	
	@After("com.ctc.AspectJ.UserAspect.addLog()")
	public void After(){
		System.out.println("LogAdvice after advice ");
	}
	
        //除了可以通过名字来指向对应的切入点表达式,还可以可以使用'&&', '||' 和 '!'来合并。
        //切入点表达式的 args(user,..) 表示某个与切入表达式匹配的连接点它把User对象作为第一个参数,通过这个语法我们可以在通知中访问到这个User对象。
        @Around("com.ctc.AspectJ.UserAspect.addLog()&&" +"args(user,..)")
	public void around(ProceedingJoinPoint  joinPoint,User user) throws Throwable{
		System.out.println("log begin!");
		System.out.println("log end");
	}*/

}
	@Test
	public void Aspect(){
		User user = new User();
		user.setUserName("test");
		user.setPassword("123");
		UserService userService = (UserService) cx.getBean("userServiceImpl");
		userService.addUser(user);
/*		AgeGroup userAdult = (AgeGroup) cx.getBean("userServiceImpl");
		userAdult.isAdult();
		System.out.println(userAdult instanceof UserService);*/
		}

        //UserServiceImpl中的方法
	@Override
	public void addUser(User user) {
		System.out.println("add into DB");
                } 

前置通知(Before advice)

springaop的使用_Spring注解

返回后通知(After reurning advice)

springaop的使用_Spring注解

    如果方法抛出异常,那么返回后通知就不会执行:

springaop的使用_Spring注解

抛出异常后通知(After throwing advice)

    springaop的使用_Spring注解

后置通知(After (finally) advice)

    springaop的使用_Spring注解

把userDaoImpl = null删掉。

springaop的使用_Spring注解

 

环绕通知(Around advice)

    环绕通知是一种功能比较强大的通知类型,它可以把第一个参数定义为org.aspectj.lang.Joinpoint类型(环绕通知需要定义为ProcessJoinPoint类型,它是JoinPoint的一个子类)。通过ProcessJoinPoint可以调用process()决定是否让连接点(Join point)继续执行,或者是调用getArgs()返回方法参数,getThis()返回代理对象,getTarget()返回目标对象,getSignature()返回正在被通知的方法的相关信息和toString()打印出正在被通知的方法的有用信息。

springaop的使用_Spring注解

下面将从四种情况进行演示:

第一种在方法执行前后添加通知:

springaop的使用_Spring注解

第二种方法不执行:

springaop的使用_Spring注解

第三种抛出异常停止执行:

springaop的使用_Spring注解

springaop的使用_Spring注解

第四种返回方法的返回值:

       //因为原有的方法为void就是一个空值,这边改用String方便测试。
	@Override
	public String addUser(User user) {
		userDaoImpl.addUser(user);
		return "add success!";
	}
 

springaop的使用_Spring注解

引入(Introduction)

    引入在AspectJ中被称为inter-tye声明,它可以使代理对象实现一个给定的接口用来添加额外的方法或字段。在下面案例中,我们首先引入一个新的接口以及接口的实现类,然后再通过@DeclareParents来定义一个引入,其中value表示要引入的目标对象,defaultImpl表示要实现接口的实现类的Class对象。

public interface AgeGroup {
	
	public void isAdult();

}

public class Adult implements AgeGroup {

	@Override
	public void isAdult() {
		System.out.println("Yes,he is an adult.");
	}

}

springaop的使用_Spring注解

springaop的使用_Spring注解

SpringAOP之XML

    XML的实例就是把前面用Annotation注解的方式转到配置文件中,案例代码不变并且只给出一个案例,不全部还原。

 //为了方便,只保留了一个before用来演示。
 public class LogAdvice {
	
	public void before(){
		System.out.println("LogAdvice before advice ");
	}
	
}

springaop的使用_Spring注解

springaop的使用_Spring注解

  在选择XML还是Annotation上面,XML的配置是Spring用户最熟悉的,可以很清楚的从配置文件中了解到AOP的应用。但是它的缺点在于XML能够支持的功能会比Annotation的方式差一点,例如在注解上面,我们支持两个切入点表达式进行组合。而XML的方式无法做到。而且通过Annotation的方式如果有需要的话可以很容易的移植到AspectJ上,所以Spring团队更喜欢用Annotation的方式。总之仁者见仁智者见智,看需要吧。

AOP日志实现

  AOP能够实现的事情比较多,此处给出如何通过aop进行日志处理,包括方法调用时长,日志链添加,前置通知等。(注:项目中实现日志的方式有很多种。)

@Aspect
@Component
public class LogAdvice {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 横切所有controller下的public方法
     */
    @Pointcut("execution(public * com.ctc.controller.*.*(..))")
    public void controllerPointCut(){}

    /**
     * 横切所有service下的public方法
     */
    @Pointcut("execution(public * com.ctc.service.*.*(..))")
    public void servicePointCut(){}

    /**
     * 环绕通知
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("controllerPointCut()")
    public BaseResponse aroundController(ProceedingJoinPoint  joinPoint) throws Throwable{
        String uuid = UUID.randomUUID().toString().replace("-", "");
        // 添加日志链
        MDC.put("mdcId", uuid);
        // 获取Controller入参
        Object[] objects = joinPoint.getArgs();
        String request = "";
        for (Object o : objects) {
            if (o instanceof BaseRequest) {
                request = o.toString();
                break;
            }
        }
        String methodName = joinPoint.getSignature().getName();
        logger.info("请求开始: methodName = {}, request = {}", methodName, request);
        long startTime = System.currentTimeMillis();
        BaseResponse response = (BaseResponse) joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        long executeTime = endTime - startTime;
        logger.info("请求结束: methodName = {}, result = {}, 执行时间: time = {}ms", methodName, response, executeTime);
        if (MDC.get("reqId") != null) {
            // 请求结束后移除日志链
            MDC.remove("reqId");
        }
        return response;
    }

    /**
     * 前置通知业务层
     */
    @Before("servicePointCut()" )
    public void aroundService(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] names = methodSignature.getParameterNames();
        // 获取Service入参
        Object[] objects = joinPoint.getArgs();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < objects.length; i++) {
            sb.append(names[i] + " = " + objects[i]);
            if (i != objects.length) {
                sb.append(", ");
            }
        }
        String methodName = methodSignature.getName();
        logger.info("执行方法:{}; {}", methodName, sb.toString());
    }
}
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private TestService testService;

    @GetMapping("/hello")
    public BaseResponse getInfo() {
        return ResponseUtil.getSuccessResponse();
    }

    @PostMapping("/post")
    public BaseResponse getInfo2(@RequestBody ProductInfoRequest request) {
        testService.test1();
        testService.test2(1, "adff", request);
        return ResponseUtil.getSuccessResponse(request);
    }
}

@Service
public class TestServiceImpl implements TestService {

    @Override
    public void test1() {
        test3();
    }

    @Override
    public void test2(int i, String s, BaseRequest request) {
    }
    
    // 该示例中test3不会被横切到,因为其方法修饰符为private
    private void test3() {
    }
}

执行结果:

--2019-08-03 14:01:59.631 - INFO 7624 --- [6d4848cd552c46c5bcbd8d083f30c2d4] com.ctc.aspect.LogAdvice    : 请求开始: methodName = getInfo, request = 
--2019-08-03 14:01:59.631 - INFO 7624 --- [6d4848cd552c46c5bcbd8d083f30c2d4] com.ctc.aspect.LogAdvice    : 请求结束: methodName = getInfo, result = BaseResponse(code=0, data=null, msg=success), 执行时间: time = 0ms
--2019-08-03 14:03:00.670 - INFO 7624 --- [799d9b6a1c27498b8ab765f5b409c146] com.ctc.aspect.LogAdvice    : 请求开始: methodName = getInfo2, request = ProductInfoRequest(id=1, name=haha)
--2019-08-03 14:03:00.671 - INFO 7624 --- [799d9b6a1c27498b8ab765f5b409c146] com.ctc.aspect.LogAdvice    : 执行方法:test1; 
--2019-08-03 14:03:00.675 - INFO 7624 --- [799d9b6a1c27498b8ab765f5b409c146] com.ctc.aspect.LogAdvice    : 执行方法:test2; i = 1, s = adff, request = ProductInfoRequest(id=1, name=haha), 
--2019-08-03 14:03:00.675 - INFO 7624 --- [799d9b6a1c27498b8ab765f5b409c146] com.ctc.aspect.LogAdvice    : 请求结束: methodName = getInfo2, result = BaseResponse(code=0, data=ProductInfoRequest(id=1, name=haha), msg=success), 执行时间: time = 4ms

参考文献

https://docs.spring.io/spring/docs/4.3.25.RELEASE/spring-framework-reference/htmlsingle/

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/169595.html原文链接:https://javaforall.net

(0)
上一篇 2022年8月11日 下午2:46
下一篇 2022年8月11日 下午2:46


相关推荐

  • google gis_系统数据交互图

    google gis_系统数据交互图转载自http://www.cnblogs.com/yinxiangpei/articles/2574502.html,仅用作资料保存 在地理行业中,项目前期规划都会涉及遥感图像,更高级一点的是建立研究区的三维模型。在实践中,笔者对这一些常见的疑问进行了总结,包括如下几个方面。   1、 如何把ArcGIS的数据导入到GoogleEarth中;   2、 如何把Googl

    2025年11月23日
    7
  • Ubuntu 20.04搭建FTP服务器

    Ubuntu 20.04搭建FTP服务器Ubuntu20 04 下搭建 FTP 服务器这是一个新手小白的搭建流程 使用的是 Ubuntu20 04 桌面版 小伙伴们可以参考一下哦 nbsp nbsp 下面就是作者小白经过一番折腾后总结出来的搭建流程啦 一 安装 1 下载 VSFTPD 终端 ps 如何你在这里没有使用 root 用户下面的 sudo 不能少哦 否则会提示权限不够哒 sudoapt getinstallvs 二 配置 VSFTPD 服务器 1 修改配置文件可以用你自己喜欢的编辑器进行编辑修改 我用的是 gedit 编辑器

    2026年3月18日
    2
  • 数字电路实验(一)——译码器

    数字电路实验(一)——译码器1、实验步骤:异或门过程1、 新建,编写源代码。(1).选择保存项和芯片类型:【File】-【newprojectwizard】-【next】(设置文件路径+设置projectname为【C:\Users\lenovo\Desktop\笔记\大二上\数字电路\实验课\实验一\异或门】)-【next】(设置文件名【gg】)-【next】(设置芯片类型为【cyclone-EP1CT144C…

    2022年7月12日
    20
  • WinSCP连接VMware虚拟机被拒绝「建议收藏」

    WinSCP连接VMware虚拟机被拒绝「建议收藏」最近在做一个电商项目练手,使用了dubbo,并安装了虚拟机准备模拟熟悉一下,但是使用WinSCP一直提示拒绝连接,下面分享下我的解决办法期望对你们有帮助1.提示信息2.一开始的分析,以为是IP地址错误导致,所有ping了IP显示如下:3.分析IP地址,eth0上面显示的并不是我们常看到的4位的IP127.0.0.1这一类,继续分析可能是没有连网络,意思是:虚拟机也需要单独连接网

    2025年12月14日
    3
  • Pascal 语言中字符与字符串

    Pascal 语言中字符与字符串

    2021年9月4日
    49
  • Redis 入门指南

    Redis 入门指南一 简介 Redis 是一个开源的 高性能的 基于键值对的缓存和存储系统 通过多种键值数据类型来适应不同场景下的缓存和存储需求 Redis 是一个高性能的键值对数据库 相比于其他键值对缓存产品 有以下特点 支持数据的持久化 可以将内存中的数据保存在磁盘中 重启的时候可以再次加载进行使用 不仅仅支持简单的键值对类型数据 同时还提供 list set zset hash 等数据结构的存储

    2026年3月19日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号