Spring—AOP

3.AOP

1>场景模拟:

在这里插入图片描述

接口Calculator:

public interface Calculator {


    int add(int a,int b);

    int sud(int a,int b);

    int mul(int a,int b);

    int div(int a,int b);

}

实现类CalculatorImpl:

(日志功能简化如实现类中的add方法)

public class CalculatorImpl implements Calculator{

    @Override
    public int add(int a, int b) {
        System.out.println("日志:加法运算:"+a+"+"+b);
        int result = a + b;
        System.out.println("内部方法,result:"+result);
        System.out.println("日志:结果 = "+result);
        return result;
    }

    @Override
    public int sud(int a, int b) {
        int result = a - b;
        System.out.println("内部方法,result:"+result);
        return result;
    }

    @Override
    public int mul(int a, int b) {
        int result = a * b;
        System.out.println("内部方法,result:"+result);
        return result;
    }

    @Override
    public int div(int a, int b) {
        int result = a / b;
        System.out.println("内部方法,result:"+result);
        return result;
    }
}
2>现代码存在问题

针对带日志功能的实现类,我们发现如下缺陷:

  • 针对业务核心功能有干扰,导致程序员在开发核心业务功能时分散精力
  • 附加功能分散在各业务功能方法中,不利于统一维护

解决思路:

核心:解耦。需要把附加功能从业务功能代码中抽取出来

3>代理模式
(1)介绍:

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来――解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

(2)相关术语:

代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类,对象,方法。

目标:被代理“套用”的非核心逻辑代码的类,对象,方法。

(3)静态代理

CalculatorStaticProxy类:

(只展示add方法)

public class CalculatorStaticProxy implements Calculator{

    public CalculatorStaticProxy(CalculatorImpl target) {
        this.target = target;
    }

    private CalculatorImpl target;

    @Override
    public int add(int a, int b) {
        System.out.println("日志,方法,add,参数"+a+","+b);
        int result = target.add(a,b);
        System.out.println("日志,方法,add,结果"+result);
        return result;
    }

    @Override
    public int sud(int a, int b) {
        return 0;
    }

    @Override
    public int mul(int a, int b) {
        return 0;
    }

    @Override
    public int div(int a, int b) {
        return 0;
    }
}

测试:

@Test
public void test(){
    CalculatorStaticProxy calculatorStaticProxy = new CalculatorStaticProxy(new CalculatorImpl());
    calculatorStaticProxy.add(1,2);
}

在这里插入图片描述

静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。

提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。

(4)动态代理

动态代理分为两种:

  • jdk动态代理:要求必须有接口最终生成的代理类和目标类实现相同的接口,在cmo.sun.proxy包下,类名为$proxy加数字
  • cglib动态代理:最终生成的动态代理类会继承目标类,并且和目标类在相同的包下

在此只介绍jdk动态代理

场景3.1>相同

生成代理对象的工厂类:

public class ProxyFactory {

    //traget是目标对象,即被代理对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }





    public Object getProxy(){
//     Proxy.newProxyInstance()的三个参数:
//       ClassLoader loader:指定加载动态生成的代理类的加载器
//       Class[] interfaces:获取目标对象实现的所有接口的class对象的数组
//       InvocationHandler h: 设置代理类中的抽象方法如何重写

        ClassLoader classLoader = this.getClass().getClassLoader();

        Class<?>[] interfaces = target.getClass().getInterfaces();

//        InvocationHandler是一个接口,因此在此使用匿名内部类实现此接口
        InvocationHandler handler = new InvocationHandler() {
            @Override
//        此invoke()方法内部的代码实现的功能就是生成的动态代理对象重写的方法实现的功能   
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("动态代理日志:"+method.getName()+",参数"+ Arrays.toString(args));
//                proxy表示代理对象,method表示要执行的方法,args表示要执行的方法的参数列表
                Object result = method.invoke(target,args);
                System.out.println("动态代理日志结果:"+result);
                return result;
            }
        };

        return Proxy.newProxyInstance(classLoader,interfaces,handler);
    }
}

测试类:

public class Proxy {
    @Test
    public void test(){
        ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
//        proxyFactory.getProxy()方法返回的是CalculatorImpl的动态代理对象(Object类型)
//        因此,向上转型为他所实现的接口Calculator类型
        Calculator proxy = (Calculator) proxyFactory.getProxy();
        proxy.add(1,2);
    }
}

在这里插入图片描述

4>AOP概念及相关术语

(1)概述

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术

(2)相关术语
1,横切关注点

从每个方法中抽取出来同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。

这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有多少个附加功能,就有多少个横切关注点

2,通知

每一个切面关注点上要做的事情都需要写一个方法来实现,这样的1方法就叫通知

  • 前置通知:在被代理的目标方法前执行
  • 返回通知:在被代理的目标方法成功结束后执行
  • 异常通知:在被代理的目标方法异常结束后执行
  • 后置通知:在被代理的目标方法最终结束后执行
  • 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种哦通知对应的所有位置
3,切面

封装通知方法的类。

4,目标

被代理的目标对象

5,代理

向目标对象应用通知后创建的代理对象

6,连接点

这也是一个纯逻辑概念,不是语法定义的。

把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看出y轴方向,x轴和y轴的交出点就是连接点

7,切入点

定位连接点的方式

(3)基于注解的AOP
1,在IOC的基础上加导入依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.1</version>
</dependency>
2,创建接口和其实现类

创建Calcultator接口:

public interface Calculator {


    int add(int a,int b);

    int sud(int a,int b);

    int mul(int a,int b);

    int div(int a,int b);

}

其实现类CalcultatorImpl:

@Component
public class CalculatorImpl implements Calculator{

    @Override
    public int add(int a, int b) {
        int result = a + b;
        return result;
    }

    @Override
    public int sud(int a, int b) {
        int result = a - b;
        System.out.println("内部方法,result:"+result);
        return result;
    }

    @Override
    public int mul(int a, int b) {
        int result = a * b;
        System.out.println("内部方法,result:"+result);
        return result;
    }

    @Override
    public int div(int a, int b) {
        int result = a / b;
        System.out.println("内部方法,result:"+result);
        return result;
    }
}
3,创建切面类:
//在切面中,需要通过指定的注解将方法标识为通知方法


@Component

// 将当前组件标识为切面
@Aspect
public class LoggerAspect {




//    切入点表达式:设置在标识通知的注解的value属性中
//    execution(* org.example.aop.annotation.CalculatorImpl.*(..))
//    第一个*表示任意的访问修饰符和返回值类型
//    第二个*表示类中任意的方法
//    ..表示任意的参数列表
//    在类的地方也可以写*,表示包下所有的类
//    @Before("execution(public int org.example.aop.annotation.CalculatorImpl.add(int,int))")





    //@Before:前置通知,在目标对象方法执行之前执行
    @Before("execution(* org.example.aop.annotation.CalculatorImpl.*(..))")
//    获取连接点
    public void beforeAdviceMethod(JoinPoint joinPoint){
//      获取连接点所对应的方法的签名信息
        Signature signature = joinPoint.getSignature();
//      获取连接点对应方法的参数
        Object[] args = joinPoint.getArgs();

        System.out.println("LoggerAspect,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
        System.out.println("LoggerAspect,前置通知");
    }




    //    重用切入点表达式
//    @Pointcut ("execution(* org.example.aop.annotation.CalculatorImpl.*(..))")
//    使用方式: @After("pointcut()")
    @Pointcut ("execution(* org.example.aop.annotation.CalculatorImpl.*(..))")
    public void pointcut(){};



    //    @After:后置通知,在目标对象方法的finally子句中执行的
    @After("pointcut()")
    public void afterAdviceMethod(){
        System.out.println("LoggerAspect,后置通知");
    }


    //    @AfterReturning:返回通知:在目标对象方法返回值之后执行
//    在返回通知中要获取目标对象方法的返回值,只需要在 @AfterReturning注解的returning属性就可以将通知方法发某个参数指定为接收目标对象方法的返回值
    @AfterReturning(value = "pointcut()",returning = "result")
    public void afterReturningAdviceMethod(JoinPoint joinPoint,Object result){


        System.out.println("LoggerAspect,返回通知,结果:"+result);

    }



//    @AfterThrowing:异常通知
//    @AfterThrowing注解的throwing属性就可以将通知方法发某个参数指定为接收目标对象方法的异常参数

    @AfterThrowing(value = "pointcut()", throwing = "ex")
    public void afterThrowingAdviceMethod(JoinPoint joinPoint,Exception ex){

        System.out.println("LoggerAspect,异常通知:"+ex);
    }
}
4,Spring配置文件:
<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://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注意事项:
        切面类和目标类都要交给IOC容器管理
        切面类必须通过@Asoect注解标识为一个切面
        在Spring配置文件中设置<aop:aspect-autoproxy/>开启基于注解的AOP
-->

    <context:component-scan base-package="org.example.aop.annotation"></context:component-scan>

<!--开启基于注解的AOP-->
    <aop:aspectj-autoproxy/>

</beans>

在这里插入图片描述

5,环绕通知
@Around("pointcut()")
//    环绕通知方法的返回值一定要和目标对象方法返回值一致
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){

    Object result;
    try {
        System.out.println("环绕通知---》前置通知");
        //        表示目标对象方法的执行
         result = joinPoint.proceed();
        System.out.println("环绕通知---》返回通知");
    } catch (Throwable e) {
        System.out.println("环绕通知---》异常通知");
        throw new RuntimeException(e);
    }finally {
        System.out.println("环绕通知---》后置通知");
    }
    return result;
}
6,测试:
@Test
public void aopTest(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop-annotation.xml");
    Calculator calculator = applicationContext.getBean(Calculator.class);
    int result = calculator.add(1,1);
    System.out.println(result);
}

在这里插入图片描述

7,切面的优先级

新创建一个切面类:

@Component
@Aspect

public class ValidateAspect {


    @Pointcut("execution(* org.example.aop.annotation.CalculatorImpl.*(..))")
    public void pointcut(){};


    @Before("pointcut()")
    public void beforeMethod(){
        System.out.println("V2!!!!!!!!!");
    }

}

在这里插入图片描述

此切面类的通知方法优先级小于LoggerAspect类

要想改变其优先级,只需在此切面类上加如@orde()标签,其标签内value(默认Integer的最大值)的int的值越小,优先级越高。

@Component
@Aspect
@Order(1)
public class ValidateAspect {


    @Pointcut("execution(* org.example.aop.annotation.CalculatorImpl.*(..))")
    public void pointcut(){};


    @Before("pointcut()")
    public void beforeMethod(){
        System.out.println("V2!!!!!!!!!");
    }

}

在这里插入图片描述

(4)基于xml的AOP

复制(3)中的类,并删除除@Component了之外标签

xml实现AOP如下(其中标签功能就相当于(3)中的注解功能):

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://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="org.example.aop.xml"></context:component-scan>


    <aop:config>
<!--        设置一个公共切入点-->
        <aop:pointcut id="pointcut" expression="(* org.example.aop.xml.CalculatorImpl.*(..))"/>
<!--IOC容器中的bean设置为切面-->
        <aop:aspect ref="loggerAspect order="1"">
            <aop:before method="beforeAdviceMethod" pointcut-ref="pointcut"></aop:before>
            <aop:after method="afterAdviceMethod" pointcut-ref="pointcut"></aop:after>
            <aop:after-returning method="afterReturningAdviceMethod" returning="result" pointcut-ref="pointcut"></aop:after-returning>
            <aop:after-throwing method="afterThrowingAdviceMethod" throwing="ex" pointcut-ref="pointcut"></aop:after-throwing>
            <aop:around method="aroundAdviceMethod" pointcut-ref="pointcut"></aop:around>
        </aop:aspect>
    </aop:config>


</beans>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值