SpringAOP切面编程小理解

本文深入介绍了AOP(面向切面编程)的概念、目标及底层原理,强调它在软件开发中的组件化思想。通过动态代理,AOP实现了共性功能的抽取和动态织入,从而实现代码的解耦。文章列举了多种切点表达式示例,并详细解释了通知的五种类型及其应用场景。此外,提供了XML和注解两种方式的AOP配置示例,帮助读者理解AOP的实践操作。
摘要由CSDN通过智能技术生成

概念:

AOP(Aspect Oriented Programing)面向切面编程,一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构,其实就是一种思想!

解析:

就是将整个项目组件化,将各个组件组合到一起完成整个项目,例如。手机的各个部件,又众多组件组合而成,切面编程就是将整个项目功能分解,然后做成小片段,组合后完成整个功能模块!更重要的是思想!编程范式,关注开发的共性功能,抽取共性功能独立出来,在原始程序运行时再动态织入进去

底层原理

动态代理(JDK动态代理,CGLib动态代理),这块就不做详细描述了,在之前总结过!

AOP编程的目标

将软件开发由手工制作走向半自动化/全自动化阶段,实现“插拔式组件体系结构”搭建

AOP相关的概念:

连接点:所有运行的方法
切入点:具有共性功能(已经被挖掉)的运行的方法
通知:共性功能抽取后制作的类中的方法/功能
通知类别:共性功能从原始切入点中挖取的位置(前还是后?)
切面:描述共性功能与切入点之间的关系
目标对象:原始功能运行后的对象
代理:对目标对象进行代理,最终运行完整功能的是代理丢向(代理对象)
织入:共性功能在运行期通过代理的形式进入到目标对象对应的代理对象中的动态过程
引入:....

AOP运行过程:

当监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置将通知对应的功能织入,完成完整的代码逻辑并运行!

AOP的开发方式

准备maven坐标

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

切点表达式:

*:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
如:execution(public * com.test.*.UserService.find*(*))
匹配com.test包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

.. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
如:execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

+:专用于匹配子类类型
execution(* *..*Service+.*(..))


范例:作为参考
execution(* *(..))
execution(* *..*(..))
execution(* *..*.*(..))
execution(public * *..*.*(..))
execution(public int *..*.*(..))
execution(public void *..*.*(..))
execution(public void com..*.*(..))
execution(public void com..service.*.*(..))
execution(public void com.test.service.*.*(..))
execution(public void com.test.service.User*.*(..))
execution(public void com.test.service.*Service.*(..))
execution(public void com.test.service.UserService.*(..))
execution(public User com.test.service.UserService.find*(..))
execution(public User com.test.service.UserService.*Id(..))
execution(public User com.test.service.UserService.findById(..))
execution(public User com.test.service.UserService.findById(int))
execution(public User com.test.service.UserService.findById(int,int))
execution(public User com.test.service.UserService.findById(int,*))
execution(public User com.test.service.UserService.findById(*,int))
execution(public User com.test.service.UserService.findById())
execution(List com.test.service.*Service+.findAll(..))

通知类型:

前置通知:原始方法执行前执行,如果通知中抛出异常,阻止原始方法运行

应用:数据校验

后置通知:原始方法执行后执行,无论原始方法中是否出现异常,都将执行通知

应用:现场清理

返回后通知:原始方法正常执行完毕并返回结果后执行,如果原始方法中抛出异常,无法执行

应用:返回值相关数据处理

抛出异常后通知:原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行

应用:对原始方法中出现的异常信息进行处理

环绕通知:在原始方法执行前后均有对应执行执行,还可以阻止原始方法的执行

应用:十分强大,可以做任何事情

 

1 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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--最后两行导入AOP通知-->

    <!--3.开启AOP命名空间-->
    <bean id="userService" class="com.service.impl.UserServiceImpl"/>
    <!--2.配置共性功能成功spring控制的资源-->
    <bean id="myAdvice" class="com.aop.AOPAdvice"/>
    
    <!--可以配置多个-->
    <!--4.配置AOP-->
    <aop:config>
        <!--可以配置多个-->
        <!--5.配置切入点(需要加强的方法)-->
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <!--可以配置多个-->
        <!--6.配置切面(切入点与通知的关系)-->
        <aop:aspect ref="myAdvice">
            <!--可以配置多个-->
            <!--7.配置具体的切入点对应通知中那个操作方法-->
            <!--根据五种通知需求进行选择-->
            <aop:before method="function" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>
</beans>

两个范例,必要时参考修改

<?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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--目标对象类-->
    <bean id="userService" class="com.itheima.service.impl.Ts"/>
    <!--切面类-->
    <bean id="myAdvice" class="com.itheima.aop.AOPTest"/>

    <!--配置AOP-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="p" expression="execution(public void com.itheima.service.impl.Ts.save(..))"/>
        <!--配置切面,切入点(具体的加强方法对象)和具体的通知(方法),注意:切面可以配置多个-->
        <aop:aspect ref="myAdvice">
            <!--具体的需要加强的方法(pointcut切入点)和需要加强的方法(通知)-->
            <!--前置通知:在被增强的方法(切入点)前执行,获取到具体的参数列表,主要用于校验数据-->
            <aop:before method="beforeTest1" pointcut-ref="p"/>
            <!--后置通知:在被加强的方法(切入点)后执行,无论切入点有没有异常都会执行,主要用于后续清理,
            注意 :可以获取到被加强方法执行的结果做返回值相关数据处理-->
            <aop:after method="after" pointcut-ref="p"/>
            <!--返回后通知:原始方法正常执行完毕并返回结果后执行,如果原始方法中抛出异常,无法执行,
            注意 :可以获取到被加强方法执行的结果做返回值相关数据处理-->
            <aop:after-returning method="beforeTest1" pointcut-ref="p"/>
            <!--抛出异常后通知:原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行,应用:对原始方法中出现的异常信息进行处理-->
            <aop:after-throwing method="beforeTest1" pointcut-ref="p"/>
            <!--环绕通知:在原始方法执行前后均有对应执行执行,还可以阻止原始方法的执行:应用场景包含以上所有的应用场景,
            注意 :可以获取到被加强方法执行的结果做返回值相关数据处理-->
            <aop:around method="beforeTest1" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>

</beans>
<?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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
    <bean id="myAdvice" class="com.itheima.aop.AOPAdvice"/>


    <aop:config>
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
            <aop:before method="before" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
            <aop:before method="before" pointcut-ref="pt"/>
        </aop:aspect>
        <aop:aspect ref="myAdvice">
            <aop:before method="before" pointcut-ref="pt"/>
        </aop:aspect>
        <aop:aspect ref="myAdvice">
            <aop:before method="before" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>

    <aop:config>
        <!--公共切入点-->
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
            <!--局部切入点-->
            <aop:pointcut id="pt2" expression="execution(* *..*(..))"/>
            <!--<aop:before method="before" pointcut-ref="pt2"/>-->
            <aop:before method="before" pointcut="execution(* *..*(..))"/>
        </aop:aspect>
    </aop:config>

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
            <aop:before method="before" pointcut-ref="pt"/>
            <aop:after method="after" pointcut-ref="pt"/>
            <aop:after-returning method="afterReturing" pointcut-ref="pt"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
            <aop:around method="around" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>


    <aop:config>
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
            <!--<aop:around method="around" pointcut-ref="pt"/>
            <aop:before method="before" pointcut-ref="pt"/>-->

            <!--<aop:around method="around" pointcut-ref="pt"/>
            <aop:after-returning method="afterReturing" pointcut-ref="pt"/>
            <aop:after method="after" pointcut-ref="pt"/>-->

            <aop:before method="before3" pointcut-ref="pt"/>
            <aop:before method="before" pointcut-ref="pt"/>
            <aop:before method="before2" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
            <aop:before method="before" pointcut-ref="pt"/>
            <aop:around method="around" pointcut-ref="pt"/>
            <aop:after-returning method="afterReturing" pointcut-ref="pt"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
            <aop:after method="after" pointcut-ref="pt"/>

            <!--使用names会交换参数位置-->
            <aop:before
                    method="before1"
                    arg-names="y,x"
                    pointcut="execution(* *..*(int,int)) &amp;&amp; args(x,y)"/>

        </aop:aspect>
    </aop:config>

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
            <aop:after-returning method="afterReturing" pointcut-ref="pt" returning="ret"/>
            <aop:around method="around" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
            <aop:around method="around" pointcut-ref="pt"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pt" throwing="t"/>
        </aop:aspect>
    </aop:config>
</beans>

 通知类获取切点信息的写法:

package com.itheima.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class AOPAdvice {
    /**
     * 前置通知:原始方法执行前执行,如果通知中抛出异常,阻止原始方法运行
     * 应用:数据校验
     * @param jp
     */

    public void before(JoinPoint jp){
        Object[] args = jp.getArgs();
        //获取参数,做提前的参数校验
    }

    /***
     * 参数为x和y,但是前提是自己知道这个参数类型,这种方法是朕的不好用!
     * @param x
     * @param y
     */
    public void before1(int x,int y){
        System.out.println("before(int)..."+x+","+y);
    }

    /**
     * 后置通知:原始方法执行后执行,无论原始方法中是否出现异常,都将执行通知
     * 应用:现场清理
     * @param jp
     */
    public void after(JoinPoint jp){
        Object[] args = jp.getArgs();
        //不确定是否拥有返回值和参数等,所以不做相关处理
    }

    /**
     * 返回后通知:原始方法正常执行完毕并返回结果后执行,如果原始方法中抛出异常,无法执行
     * 应用:返回值相关数据处理
     * @param ret ret作为返回后的结果
     */
    public void afterReturing(Object ret){
        //返回后的结果
    }


    /**
     * 抛出异常后通知:原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行
     * 应用:对原始方法中出现的异常信息进行处理
     * t执行后抛出的异常
     * @param t
     */
    public void afterThrowing(Throwable t){
     //处理抛出的异常
    }

    /**
     * 环绕通知:在原始方法执行前后均有对应执行执行,还可以阻止原始方法的执行
     * 应用:十分强大,可以做任何事情
     * @param pjp
     * @return
     */
    public Object around(ProceedingJoinPoint pjp) {
        //获取参数数组
        Object[] args = pjp.getArgs();
        System.out.println("around before...");
        Object ret = null;
        try {
            //对原始方法的调用
            ret = pjp.proceed();
        } catch (Throwable throwable) {
            System.out.println("around...exception...."+throwable.getMessage());
        }
        System.out.println("around after..."+ret);
        return ret;
    }
}

2 注解加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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.itheima"/>
    <aop:aspectj-autoproxy/>
</beans>

切点承载类:


/**
 * 本类只作为配置切点承载类
 */
public class AOPPointcut {
    //切点可以配置多个。根据表达式而定
    @Pointcut("execution(* *..*(..))")
    public void pt1(){}
    //......可以有多个
}

通知类:

package com.itheima.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
//@order可以影响配置的执行顺序,本注解可有可无,看情况,但是配置通知的执行顺序根据实际的调试结果做调整
//大多时候的这个顺序收到字典顺序的影响
@Order(2)
public class AOPAdvice {
//   一般都是另起一个类作为切点的承载类
//    @Pointcut("execution(* *..*(..))")
//    public void pt(){}

//    @Before("AOPPointcut.pt1()")
//    public void before(){
//        System.out.println("前置before...");
//    }
//
//    @After("AOPPointcut.pt1()")
//    public void after(){
//        System.out.println("后置after...");
//    }
//
//    @AfterReturning("AOPPointcut.pt1()")
//    public void afterReturing(){
//        System.out.println("返回后afterReturing...");
//    }
//
//    @AfterThrowing("AOPPointcut.pt1()")
//    public void afterThrowing(){
//        System.out.println("抛出异常后afterThrowing...");
//    }
//
//    @Around("AOPPointcut.pt1()")
//    public Object around(ProceedingJoinPoint pjp) throws Throwable {
//        System.out.println("环绕前around before...");
//        Object ret = pjp.proceed();
//        System.out.println("环绕后around after...");
//        return ret;
//    }

    @Before("AOPPointcut.pt1()")
    public void aop002log() {
        System.out.println("前置before...1");
    }

//    @Before("AOPPointcut.pt1()")
//    public void aop002exception(){
//        System.out.println("前置before...2");
//    }

}

纯注解开发直接加载 Spring核心配置类,不需要xml文件,在通知类和切点承载类中已使用注解标识了:

@Configuration
@ComponentScan("com")
@EnableAspectJAutoProxy
public class SpringConfig {
}

加载Spring的核心配置文件即可扫描到核心配置文件,关键还是使用了注解标识:

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
}

AOP相关注解:

 @Aspect 通知类注解 类注解

 AOP五种通知类型对应的注解:

  @Before 前置

  @After  后置

  @AfterReturning  返回后

  @AfterThrowring  返回异常后

  @Around 环绕        

@Pointcut 切点注解方法注解

 @EnableAspectJAutoProxy 开启AOP注解,切面编程注解,类注解,使用在配置文件上即可

应用示例:环绕通知:

@Component
@Aspect
public class RunTimeMonitorAdvice {

    //切入点,监控业务层接口
    @Pointcut("execution(* com..service.*Service.find*(..))")
    public void pt(){}

    @Around("pt()")
    public Object runtimeAround(ProceedingJoinPoint pjp) throws Throwable {
        //获取执行签名信息
        Signature signature = pjp.getSignature();
        //通过签名获取执行类型(接口名)
        String className = signature.getDeclaringTypeName();
        //通过签名获取执行操作名称(方法名)
        String methodName = signature.getName();

        //执行时长累计值
        long sum = 0L;

        for (int i = 0; i < 10000; i++) {
            //获取操作前系统时间beginTime
            long startTime = System.currentTimeMillis();
            //原始操作调用
            pjp.proceed(pjp.getArgs());
            //获取操作后系统时间endTime
            long endTime = System.currentTimeMillis();
            sum += endTime-startTime;
        }
        //打印信息
        System.out.println(className+":"+methodName+"   (万次)run:"+sum+"ms");
        return null;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值