Spring框架——AOP入门笔记以及个人总结

注:作者本人也是初学者,所以本文有些总结性见解可能存在问题,但是多数问题都是在上网查询过资料后总结的,如果有逻辑或者原理上的错误,或者见解不同,欢迎在评论区讨论!!!

目录

Spring的AOP

1.什么是AOP

2.AOP的作用及优势

3.AOP的底层实现

第一种情况:需要增强的类实现了某个接口(JDK动态代理)

第二种情况:需要增强的类并没有实现某个接口(也适用于有接口)(cblib代理)

4.AOP相关术语

这里我任然拿开篇说的例子来说明

基于xml的aop开发

1.基本步骤

2.配置文件书写步骤

3.通知配置语法

4.AOP五大通知类型

6.切入点表达式

基于注解的aop开发

1.配置文件

2.注解


Spring的AOP

1.什么是AOP

        面向切面编程,通过预编译方式和运行期动态带来实现程序的统一维护的一种技术,利用AOP可以对业务逻辑的各个部分进行隔离,降低各部分的耦合度, 提高程序的可重用性,提高开发效率,aop是oop的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程

        末日丧尸类电影应该都看过吧,末日神器之一就是绑在扫帚棍子上的水果刀。水果刀本身的形状功能没有变,但是我们通过绑上一根棍子加大了他的攻击范围,这就是AOP。

2.AOP的作用及优势

        作用:在程序运行期,在不改变源码的情况下,可以对方法进行增强

        优势:减少重复性代码,提高开发效率,并且便于维护

3.AOP的底层实现

第一种情况:需要增强的类实现了某个接口(JDK动态代理)

        AOP底层是通过Spring提供的动态代理技术实现的,在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时,进行增强功能的介入,在调用目标对象的方法,从而完成功能增强。

public interface TargetInterface {
    void show();
}
public class Target implements TargetInterface{
    @Override
    public void show() {
        System.out.println("使用了普通show方法");
    }
}
public class ProxyDemo {
    public static void main(String[] args) {
        final Target tg = new Target();
​
        final TargetInterface target = (TargetInterface) Proxy.newProxyInstance(Target.class.getClassLoader(), Target.class.getInterfaces(), new InvocationHandler() {
            @Override
            //参数分别为proxy对象,method对象(反射机制Class类中的三大对象之一),方法的属性
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("使用增强方法去增强show方法");
                //通过原对象和方法对象,以及方法参数,利用反射机制启动方法
                Object invoke = method.invoke(tg,args);
                //return原方法的返回值
                return invoke;
            }
        });
​
        target.show();
    }
}

        这里我们不难看出,通过这样的方法我们在使用反射机制使用原方法前,可以进行一些列调用前的操作。在原封不动获取原方法返回值后,同样可以对原方法的返回值进行加工。

第二种情况:需要增强的类并没有实现某个接口(也适用于有接口)(CGLIB代理)

        这里就是基于CGLIB进行动态代理增强。

        科普CGLIB:CGLIB是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。

public class Demo1 {
    public static void main(final String[] args) {
        //目标对象
        final Target target = new Target();
        //使用动态生成给代理对象 基于cglib
        //1.创建增强器对象
        Enhancer enhancer = new Enhancer();
        //2.设置父类(目标类)(设置目标类为代理对象的父类)
        enhancer.setSuperclass(Target.class);
        //3.设置回调
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("记录日志...");
                //执行方法
                Object invoke = method.invoke(target, args);
                System.out.println("使用aop增强了show方法");
                return invoke;
            }
        });
        //由设置的父类创建对象
        Target proxy = (Target) enhancer.create();
        proxy.show();
    }
}

        注意:增强器的create方法,此方法会依据以上第2步设置父类的操作,将代理对象的父类设置成为目标类,并且使用多态的方式返回一个Object类型对象,实际上就是:Object o = new 继承目标类的代理类()。所以最后使用Target强转,强转后的对象任然是利用了多态的方式的实现。

        使用CGLIB方式结合多态,同样可以完成第一种情况的增强操作。

public class Demo1 {
    public static void main(final String[] args) {
        //目标对象
        final TargetInterface target = new Target();
        //使用动态生成给代理对象 基于cglib
        //1.创建增强器对象
        Enhancer enhancer = new Enhancer();
        //2.设置父类(目标类)(设置的是代理对象的父类为目标类的父类,此时代理对象类和目标类是兄弟类关系)
        enhancer.setSuperclass(TargetInterface.class);
        //3.设置回调
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("记录日志...");
                //执行方法
                Object invoke = method.invoke(target, args);
                System.out.println("使用aop增强了show方法");
                return invoke;
            }
        });
        //由设置的目标类的父类创建对象
        TargetInterface proxy = (TargetInterface) enhancer.create();
        proxy.show();
    }
}
  • 如果代理对象父类设置的是Target,也就是目标类,那么代理对象就是Target的子类,增强后后创建对象就相当于把Target的子类强转成TargetInterface,当然,强转成Target也没有问题

  • 如果代理对象父类设置的是TargetInterface,那么代理对象就是TargetInterface的实现类,此时Target和代理类是实现了同一个接口的兄弟类,不能够强转为Target。因此强转成TargetInterface可以,但是,此时代理对象与Target类是属性兄弟关系,无法强转。

4.AOP相关术语

  • 目标对象:代理的目标对象

  • 代理:一个类被AOP植入增强,产生的结果代理类

  • 连接点:那些被拦截到的点(一般指被拦截到的方法)

  • 切入点:针对于哪些连接点进行拦截定义

  • 增强/通知:指定连接点之后的事情就是通知

  • 切面:切入点和通知的结合

这里我任然拿开篇说的例子来说明

        假设现在世界末日,到处都是被病毒感染的丧尸,你需要升级手头能用的日常工具,在末日中生存下来:

  • 首先我们需要一把能够杀死丧尸的待增强武器——水果刀(目标对象)

  • 其次我们需要把这个水果刀增强为长柄水果刀(产生的代理对象)

  • 我们开始思考这把水果刀可以从刀把,是否淬毒等方面进行增强(连接点,即业务层所有方法)

  • 我们发现淬毒杀不了丧尸,所有我们可以在刀把处增强(切入点,也就是被检测到需要增强的方法

  • 我们把水果刀加装了长木棍(织入)

  • 你完成了攻击距离的增强(通知,具体增强的内容,分为前置通知、后置通知、异常通知、最终通知、环绕通知)

  • 水果刀的刀把处(切入点)完成了攻击距离的增强(通知)(切面=切入点+通知)

基于xml的aop开发

1.基本步骤

  1. 导包

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.9.RELEASE</version>
    </dependency>
    ​
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.13</version>
    </dependency>
  2. 创建目标接口和目标类

    //目标类
    public class UserServiceImpl {
        public void method(){
            System.out.println("UserMethod...");
        }
    }
  3. 创建切面类(也就是为目标类增强方法的类,类中的方法从目标类非方法的不同运行阶段执行,对方法进行增强)

    public class MyAspect {
        public void before(){
            System.out.println("前置方法增强...");
        }
    }
  4. 配置

    <?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: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/aop
            https://www.springframework.org/schema/aop/spring-aop.xsd">
    ​
        <!--配置目标类-->
        <bean id="userService" class="com.tencent.service.UserServiceImpl"></bean>
    ​
        <!--配置切面类-->
        <bean id="myAspect" class="com.tencent.service.MyAspect"></bean>
    ​
        <!--配置切点表达式和增强的植入关系-->
        <aop:config>
            <!--引用myAspect的Bean为切面类-->
            <aop:aspect ref="myAspect">
                <!--配置目标类的切入点-->
                <aop:before method="before" pointcut="execution(public void com.tencent.service.UserServiceImpl.method())"></aop:before>
            </aop:aspect>
        </aop:config>
    </beans>
    

2.配置文件书写步骤

        1.使用<bean>标签配置目标类(设置id,class)

        2.使用<bean>标签配置切面类(设置id,class)

        3.使用<aop:config>标签开始配置增强

        4.在<aop:config>内部使用<aop:aspect>标签引入切面类,来增强目标类(使用ref引入)

        5.使用<aop:通知类型>标签设置用于增强的方法(切面类中)和需要增强的方法(目标类中),其中method属性用来引入用于增强的方法,pointcut属性使用通知配置语法来引入需要增强的方法

3.通知配置语法

<aop:before method="before" pointcut="execution(* com..*.*(..))"></aop:before> 
​
<aop:通知类型 method="通知方法名称" pointcut="切入点表达式"></aop:通知类型>

4.AOP五大通知类型

        通知类型有5个:前置通知(before),后置通知(after-returning),异常通知(after-throwing),最终通知(after),环绕通知。这五个通知一一对应切面类的五个方法(自己手写),在其对应的方法对目标方法增强后进行通知。

        这五个通知的中文名不同的文章有不同的翻译,可能有的把after翻译为后置通知,而我把after-returning翻译为后置通知。当然这都没错,我这么翻译是基于自己对这五个通知的理解,但是所有文章的英文名一定对应其后面的功能。

        前置通知before通知的方法在目标方法运行前执行

        后置通知after-returning通知的方法在方法调用后正常返回的时候通知,可以获取返回值,发生异常的时候不会执行

        异常通知after-throwing通知的方法在目标方法出现异常后执行

        最终通知after通知的方法在方法执行完后,无论程序是否发生异常都会执行的

        而最特殊的环绕通知around通知的方法是前四个通知的结合体

以下为切面类代码

public class Logger {
    //前置通知
    public void beforePrintLog(){
        System.out.println("在调用方法前记录操作日志");
    }

    //后置通知
    public void afterPrintLog(){
        System.out.println("在调用方法后记录操作日志");
    }

    //异常通知
    public void exceptionPrintLog(){
        System.out.println("出现异常后记录操作日志");
    }

    //最终通知
    public void finalPrintLog(){
        System.out.println("最终记录操作日志");
    }

    //环绕通知
    public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint){
        //设置空对象,用于记录目标方法被增强后的返回值
        Object returnValue = null;
        try {
            //此时相当于执行前置方法
            System.out.println("前置方法");
            //通过ProceedingJoinPoint对象获取原本方法的参数
            Object[] args = proceedingJoinPoint.getArgs();
            System.out.println("参数列表" + Arrays.toString(args));
            //通过ProceedingJoinPoint对象执行目标方法
            System.out.println("执行目标方法");
            returnValue = proceedingJoinPoint.proceed(args);
            //此处执行后置方法
            System.out.println("后置方法");
            return returnValue;
        } catch (Throwable throwable) {
            //此处相当于执行异常方法
            System.out.println("异常方法");
            throwable.printStackTrace();
        }finally {
            //此处相当于执行最终方法
            System.out.println("最终方法");
            return returnValue;
        }
    }
}

6.切入点表达式

        execution(public void com.tencent.service.UserServiceImpl.method())

        1).语法:execution([访问权限修饰符] 返回值 包名.类名.方法名(参数))

        2).修饰符:修饰符可以省略 ——> execution(返回值 包名.类名.方法名(参数))

        3).返回值:可以是*表示,代替任意返回值

        execution(* 包名.类名.方法名(参数))

        4).包名,类名,方法名都可以使用*代替

        execution(* *.*.*(参数))

        5).参数列表可以使用..两个点,表示任意个数、任意类型参数

        execution(* *.*.*(..))

基于注解的aop开发

1.配置文件

<!-- 配置注解作用域 -->
<context:component-scan base-package="com.tencent"></context:component-scan>
<!--aop的自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

2.注解

        1.@Aspect:在类上进行注解,表示该类是一个切面bean,相当于原本用来导入切面bean的<aop:aspect>标签

        2.@Before @AfterReturning @AfterThrowing @After @Around这五个注解都作用于方法上,分别对应前置通知,后置通知,异常通知,最终通知和环绕通知,每个注解用于表示该方法是对应通知。通知中使用切入点表达式,以此表示此方法用于增强的目标方法。使用这些注解,通知了何时(五个时间)目标方法(execution中配置的方法)执行哪个方法(注解下方的方法)

        3.@Pointcut:作用在方法上,与作用的方法内容无关,作用的方法仅仅就是一个标志,注解中可以配置execution切入点表达式,而作用的方法就相当于一个id,结合2中的五个注解,就可以达到减少代码冗余的操作。

以下为切面类代码

@Component("logger")
@Aspect
public class Logger {

    @Pointcut("execution(* com..AccountServiceImpl.*(..))")
    public void kong(){};

    @Before("kong()")
    public void beforeOp(){
        System.out.println("before...");
    }

    @AfterReturning("kong()")
    public void afterOp(){
        System.out.println("after...");
    }

    @AfterThrowing("kong()")
    public void exceptionOp(){
        System.out.println("exception...");
    }

    @After("kong()")
    public void finalOp(){
        System.out.println("final...");
    }

    @Around("kong()")
    public Object middle(ProceedingJoinPoint pjp){
        //定义返回值
        Object returnValue = null;
        try {
            //启动前置方法
            System.out.println("before...");
            //执行当前方法
            Object[] args = pjp.getArgs();
            System.out.println("当前方法参数为" + Arrays.toString(args));
            returnValue = pjp.proceed();
            //启动后置方法
            System.out.println("after...");
            return returnValue;
        } catch (Throwable throwable) {
            System.out.println("exception...");
            throwable.printStackTrace();
        }finally {
            System.out.println("finally...");
            return returnValue;
        }
    }
}

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aristocrat l

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值