Spring AOP面向切面编程(二)

简介: Spring AOP面向切面编程

二.AOP相关概念


1.AOP关键概念


Spring AOP与AspectJ的关系


Eclipse AspectJ 是一种基于Java平台的面向切面编程的语言。AspectJ 有一套完整的体系,可以在运行时实现AOP面向切面编程。但是作为Spring AOP来说,它并不是完全使用AspectJ来做的。作为Spring AOP使用AspectWeaver实现类与方法匹配。 Spring AOP利用代理模式实现对象运行时的功能拓展。


几个关键的概念:


image.png


2.JoinPiont核心方法


image.png


前面2个方法前面已经演示过我,下面就来演示一下getArgs()这个方法的使用。

在切面类的方法内添加如下的代码


        Object[] args=joinPoint.getArgs();
        System.out.println("---->参数个数:"+args.length);
        for (Object arg:args){
            System.out.println("---->参数:"+arg);
        }


这段代码的含义是:获取到调用的方法传入的参数

我们直接运行入口程序,因为userService.createUser();并不用传入参数,所以args的个数位0


1e4fe97d2e9144578448b47616aaef43.png


我们更改入口的方法调用代码为


        userService.generateRandomPassword("MD5",10);


然后运行程序:


7ba98a87e9f54c3caaad3d00d4314214.png


3.PointCut切点表达式


dcd02dcf47ae4d21b4158e8fbacc1c79.png


public可以不写,因为默认就是public。


如果要让程序匹配Service结尾的类名:


<aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*Service.*(..))


public是可以去掉的


<aop:pointcut id="pointcut" expression="execution( * com.haiexijun..*Service.*(..))"/>


如果只想捕获不传入参数的方法,直接把()内的2个点去掉:


<aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*.*())"/>


如果只想捕获只有2个参数的方法,用*代表参数:


<aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*.*(*,*))"/>


还可以指定方法的参数的类型:


<aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*.*(String,*



三.AOP通知


1.五种通知类型


image.png


还有一个引进的特殊的”通知“—引介增强 这不是Spring官方提供的通知。引介增强(IntroductionInterceptor)是对类的增强,而非方法。他跟通知实际上是没有关系的,它本质是一个拦截器。其他的通知是坐拥在方法上,而引介增强是作用在类上面的。引介增强允许在运行时为目标类增加新属性或方法。 引介增强允许在运行时改变类的行为,让类随运行环境动态变更。引介增强我们了解一下就可以了,日常开发用得比较少。


下面还是把关注点放在这5种通知类型上。


我们在之前编写的切面类中新增加一个方法doAfter() ,看名字就知道了,这是用于后置通知的处理方法。作为后置通知同样要加入JoinPiont连接点参数。作为方法内容我们随便打印一下,了解一下它的执行时机就可以了。


    /**
     * 后置通知
     * @param joinPoint
     */
    public void doAfter(JoinPoint joinPoint){
        System.out.println("<----触发后置通知");
    }


然后要在xml中配置一下:


    <aop:config>
        <!--pointcut是切点的意思,使用execution表达式描述切面的作用范围-->
        <!--下面的execution表达式说明切面作用于com.haiexijun包下的所有类的所有方法上-->
        <aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*Service.*(..))"/>
        <!--定义切面类-->
        <aop:aspect ref="methodAspect">
            <!--before通知,代表在目标方法运行前先执行切面类里面的方法-->
            <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
            <!--after通过-->
            <aop:after method="doAfter" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>


运行后:


166d476d25644977be8e04d1cffd4e0d.png


我们来试试返回后通知,目标方法返回数据后执行,返回后通知要传入2个参数第一个是JoinPoint , 第2个是Object 表示目标方法的返回值。


    /**
     * 返回后通知
     * @param joinPoint 连接点
     * @param ret 目标方法的返回值
     */
    public void doAfterReturning(JoinPoint joinPoint,Object ret){
        System.out.println("<----返回后通知:"+ret);
    }


对于返回后通知,我们的xml的配置也有些不一样。


    <aop:config>
        <!--pointcut是切点的意思,使用execution表达式描述切面的作用范围-->
        <!--下面的execution表达式说明切面作用于com.haiexijun包下的所有类的所有方法上-->
        <aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*Service.*(..))"/>
        <!--定义切面类-->
        <aop:aspect ref="methodAspect">
            <!--before通知,代表在目标方法运行前先执行切面类里面的方法-->
            <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
            <!--after通过-->
            <aop:after method="doAfter" pointcut-ref="pointcut"/>
            <!--返回后通知-->
            <aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>


运行,因为我们编写的createUser方法没有返回值,所以返回后通知的ret为null


e7c0383738c2446bb259232b0bff6ed7.png


最后就是异常通知了,这个通知的也要传入两个参数,第一个一样,第二个传入Throwable,为方法抛出的异常。


    /**
     * 异常通知
     * @param joinPoint 连接点
     * @param th 方法抛出的异常
     */
    public void doAfterThrowing(JoinPoint joinPoint,Throwable th){
        System.out.println("<----异常后通知:"+th.getMessage());
    }


配置xml文件


    <!--AOP配置-->
    <bean id="methodAspect" class="com.haiexijun.aspect.MethodAspect"/>
    <aop:config>
        <!--pointcut是切点的意思,使用execution表达式描述切面的作用范围-->
        <!--下面的execution表达式说明切面作用于com.haiexijun包下的所有类的所有方法上-->
        <aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*Service.*(..))"/>
        <!--定义切面类-->
        <aop:aspect ref="methodAspect">
            <!--before通知,代表在目标方法运行前先执行切面类里面的方法-->
            <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
            <!--after通过-->
            <aop:after method="doAfter" pointcut-ref="pointcut"/>
            <!--返回后通知-->
            <aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
            <!--异常通知-->
            <aop:after-throwing method="doAfterThrowing" pointcut-ref="pointcut" throwing="th"/>
        </aop:aspect>
    </aop:config>


我们模拟一个异常


    public void createUser(){
        if (1==1){
            throw new RuntimeException("用户已存在");
        }
        System.out.println("执行创建用户的业务逻辑");
        userDao.insert();
    }


下面运行一下代码:


11b73672ab1148df8c7ec304362c22e6.png


2.环绕通知


本节用一个案例来讲解环绕通知的使用方法。

在我们实际工作中,你可能会遇到这种情况,由于数据的不断累积,用户量的不断增大。可能会导致我们生产环境中系统越来越慢,那我们如何定位到,到是哪个方法执行慢呢?实际开发起来其实不那么轻松,因为一个大型系统类和方法可能有成千上万。难道我们要为每一个方法都去增加相应的代码,捕捉他们的执行时间吗?这样做效率实在是太差了。那说到这里想必你马上就能反映过来,AOP可以非常好的解决这个问题。我们只需要在方法执行前捕捉方法的开始时间,在方法执行后,捕捉方法的结束时间。这两者一相减,不就知道方法执行了多长时间吗?如果这个时间超过了规定范围的话,我们就将其输出保存在日志里面。那这里又延伸出一个新问题。作为AOP中的五种通知类型,到底用前置通知还是后置通知呢?其实都不行,因为我既要在运行前保存一个时间,又要在运行后保存一个时间,这肯定是单个通知无法做到的。不过好在spring为我们提供了一个功能最强大的选择—环绕通知。利用环绕通知,我们可以控制目标方法完整的运行周期,下面我么们通过实例来进行讲解。


首先,我们回到之前的项目中:

里面的两个dao和两个service,我们要在它们每一个方法上进行时间的检查,如果单个方法时间超过一秒中,我们就认为这个方法执行太慢,需要优化。下面我们基于spring AOP来完成。


下面来编写切面类的切面方法:

环绕通知里面用的就不是JoinPoint了,而是用ProceedingJoinPoint,ProceedingJoinPoint是一个特殊的连接点。是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行。ProceedingJoinPoint有一个proceed()方法,用于执行目标方法,该proceed()方法会返回一个Object对象。这个Object对象就是目标方法执行后的返回值。


package com.haiexijun.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * 切面类
 */
public class MethodAspect {
    /**
     * 环绕通知
     * @param pjp ProceedingJoinPoint是一个特殊的连接点。是JoinPoint的升级版,在原有
     *            功能外,还可以控制目标方法是否执行。
     */
    public Object printExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
        try {
            //得到起始时间
            Long startTime=new Date().getTime();
            Object obj= pjp.proceed();//执行目标方法
            //得到结束时间
            Long endTime=new Date().getTime();
            //执行时间
            long runTime=endTime-startTime;
            //如果目标方法的运行时间超过了1秒,就输出日志
            if (runTime>1000){
                String className=pjp.getTarget().getClass().getName();
                String methodName=pjp.getSignature().getName();
                SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                String now =sdf.format(new Date());
                System.out.println("======"+now+":"+className+"."+methodName+" ( "+runTime+" ms) ====== ");
            }
            return obj;
        } catch (Throwable e) {
            //环绕通知,它通常只会去捕获对应异常的产生
            //但如果目标方法产生了异常,作为产生的异常
            //大多数情况下,它会向外抛出去。
            //把异常抛出去是因为在我们当前系统中,未来运行时可能
            //并不只有一个通知,那如果在当前的环绕通知中,对这个异常进行了消化
            //那就意味着其他后序的处理都不会捕捉到这个异常,就可能会产生一些意料之外的问题
            System.out.println("Exception message:"+e.getMessage());
            throw e;
        }
    }
}


之后在xml里面配置环绕通知:


    <!--AOP配置-->
    <bean id="methodAspect" class="com.haiexijun.aspect.MethodAspect"/>
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(public * com.haiexijun..*.*(..))"/>
        <aop:aspect ref="methodAspect">
            <!--环绕通知-->
            <aop:around method="printExecutionTime" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>


然后为了测试,让createUser睡眠几秒钟:


    public void createUser(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行创建用户的业务逻辑");
        userDao.insert();
    }


然后,编写程序执行的入口:


package com.haiexijun;
import com.haiexijun.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService= context.getBean("userService", UserService.class);
        userService.createUser();
    }
}


运行,会输出超时的方法的日志:


5be0139e1c65480eb05ff0de44ac77bd.png


相关文章
|
28天前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
1月前
|
安全 Java 编译器
什么是AOP面向切面编程?怎么简单理解?
本文介绍了面向切面编程(AOP)的基本概念和原理,解释了如何通过分离横切关注点(如日志、事务管理等)来增强代码的模块化和可维护性。AOP的核心概念包括切面、连接点、切入点、通知和织入。文章还提供了一个使用Spring AOP的简单示例,展示了如何定义和应用切面。
90 1
什么是AOP面向切面编程?怎么简单理解?
|
2月前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
43 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
1月前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
30 1
|
1月前
|
XML Java 开发者
论面向方面的编程技术及其应用(AOP)
【11月更文挑战第2天】随着软件系统的规模和复杂度不断增加,传统的面向过程编程和面向对象编程(OOP)在应对横切关注点(如日志记录、事务管理、安全性检查等)时显得力不从心。面向方面的编程(Aspect-Oriented Programming,简称AOP)作为一种新的编程范式,通过将横切关注点与业务逻辑分离,提高了代码的可维护性、可重用性和可读性。本文首先概述了AOP的基本概念和技术原理,然后结合一个实际项目,详细阐述了在项目实践中使用AOP技术开发的具体步骤,最后分析了使用AOP的原因、开发过程中存在的问题及所使用的技术带来的实际应用效果。
61 5
|
1月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
35 0
|
2月前
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
94 2
|
2月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
137 9
|
2月前
|
Java 数据库连接 Spring
【2021Spring编程实战笔记】Spring开发分享~(下)
【2021Spring编程实战笔记】Spring开发分享~(下)
31 1
|
2月前
|
Java 容器
AOP面向切面编程
AOP面向切面编程
43 0