Java框架学习之Spring—面向切面AOP

5、面向切面:AOP

5.1 场景模拟

模拟+ - * / 运算

5.1.1声明接口

public interface Calculator {

    int add(int i,int j);

    int sub(int i,int j);

    int mul(int i,int j);

    int div(int i,int j);

}

5.1.2加减乘除实现类

public class CalculatorLogImpl implements Calculator{
    @Override
    public int add(int i, int j) {

        System.out.println("[日志] add 方法开始了 参数是 "+i+","+j);
        int result = i+j;
        System.out.println("方法内部result="+result);
        System.out.println("[日志] add 方法结束了 结果是"+result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        System.out.println("[日志] sub 方法开始了 参数是 "+i+","+j);

        int result = i-j;
        System.out.println("方法内部result="+result);
        System.out.println("[日志] sub 方法结束了 结果是"+result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        System.out.println("[日志] mul 方法开始了 参数是 "+i+","+j);
        int result = i*j;
        System.out.println("方法内部result="+result);
        System.out.println("[日志] mul 方法结束了 结果是"+result);

        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("[日志] div 方法开始了 参数是 "+i+","+j);
        int result = i/j;
        System.out.println("方法内部result="+result);
        System.out.println("[日志] div 方法结束了 结果是"+result);

        return result;
    }
}

如上 日志和代码混在一起 从维护等各方面角度都不行 通过引入代理模式来解决该问题

5.2 代理模式

5.2.1 概念

一种设计模式 结构性模式 提供一个代理类 在调用方法时 不是直接对目标方法进行调用 而是通过代理类间接调用 让不属于目标方法的核心逻辑代码从目标方法中剥离出来 称解耦 减少目标方法的调用 让附加功能集中在一起有利于维护

5.2.2 静态代理

public class CalculatorStaticProxy implements Calculator{
    //被代理的目标对象传递过来
    private Calculator calculator;

    public CalculatorStaticProxy(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public int add(int i, int j) {
        //输出日志
        System.out.println("[日志] add 方法开始了 参数是 "+i+","+j);
        //调用目标对象的方法实现核心业务
        int addresult = calculator.add(i,j);
        
        
        System.out.println("[日志] add 方法结束了 结果是"+addresult);
        return addresult;
    }

    @Override
    public int sub(int i, int j) {
        return 0;
    }

    @Override
    public int mul(int i, int j) {
        return 0;
    }

    @Override
    public int div(int i, int j) {
        return 0;
    }
}

静态代理实现了解耦 但是把代码都写死了 不具备任何的灵活性 日志功能 其他地方需要加日志的话 需要声明更多的静态代理类 没有统一管理 过于分散

提出进一步要求 日志功能集中到一个代理类中 将来有任何日志需求 通过这一个代理类实现

5.2.3 动态代理

动态代理 只需要动态创建一个代理类 把日志统一管理

// 生产代理对象的工厂类
public class ProxyFactory {

    //目标对象
    private Object target;

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

    //返回代理对象
    public Object getProxy(){

        /**
         * 有三个参数
         * 1 ClassLoader:加载动态生成代理的类加载器
         * 2 Class[] interfaces:目标对象实现所有接口的class类型的数组
         * 3 invocationHandler: 设置代理对象实现目标对象方法的过程
         */

        //1 ClassLoader:加载动态生成代理的类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        //2 Class[] interfaces:目标对象实现所有接口的class类型的数组
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //3 invocationHandler: 设置代理对象实现目标对象方法的过程
        InvocationHandler invocationHandler =new InvocationHandler(){

            /**
                第一个参数 代理对象
                第二个参数 需要重写目标对象的方法
                第三个参数 method方法里的参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //方法调用之前输出日志
                System.out.println("[动态代理][日志]"+method.getName()+"参数:"+ Arrays.toString(args));
                //调用目标的方法
                Object result = method.invoke(target, args);
                //方法调用之后输出
                System.out.println("[动态代理][日志]"+method.getName()+"结果:"+result);
                return result;
            }
        };

        return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
    }
}
//测试类
public class TestCal {
    public static void main(String[] args) {

        //创建代理对象
        ProxyFactory proxyFactory = new ProxyFactory(new CaculatorImpl());
        Calculator proxy = (Calculator) proxyFactory.getProxy();
        proxy.add(1,2);

    }
}

5.3 AOP概念和相关术语

5.3.1 概述

AOP 面向切面编程 对面向对象的补充和完善 通过预编译和运行期动态代理的方式实现 不修改源码的情况下 给程序动态的添加额外的功能 利用AOP可以对业务的逻辑各部分进行隔离 从而使业·务逻辑之间的耦合读降低 提高程序得到复用性

5.3.2 相关术语

5.3.2.1横切关注点

分散在各个模块中解决同一样的问题 用户验证 日志管理 事务处理 数据缓存 都属于横切关注点

从每个方法中把非核心业务抽取出来 即为横切关注点 可以使用多个横切关注点读相关方法进行多个不同方面增强

可以根据附加功能逻辑上的需要:有10个附加功能 可有10个横切关注点

5.3.2.2 通知(增强)

要增强的功能

每一个横切关注点上要做的事情都需要一个方法来实现 这样的方法就交通知方法

  • 前置通知 被代理的目标方法前执行

  • 返回通知 在被代理的目标方法成功结束后执行**(寿终正寝)**

  • 异常通知 在被代理的目标方法异常结束后执行**(死于非命)**

  • 后置通知 在被代理的目标方法最终结束后执行**(盖棺定论)**

  • 环绕通知 使用try..catch..finally 结构围绕整个被代理的目标方法 包括上面四种通知对应的所有位置

5.3.2.3 切面

封装通知方法的类

5.3.2.4 目标

被代理的目标对象

5.4.2.5 代理

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

5.4.2.6 连接点

一个纯逻辑概念 不是语法定义

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

就是spring运行你使用通知的方法

5.4.2.7 切入点

定位连接点的方式

每个类的方法都包含多个连接点 如果把连接点看作是数据库中的记录 那么切入点就是查询记录的SQL语句

Spring的AOP技术可以通过切入点定位到特点的连接点 通俗说 要去实际增加的方法

切点通过org.springframework.aop.Pointcut 接口进行描述 它使用类和方法作为连接点的查询条件

5.3.2 作用

  • 简化代码 把方法中固定的位置的重复代码抽离出来 让被抽取的方法更加专注自己的核心功能 提高内聚性

  • 代码增强 把特定的功能封装到切面类中 哪里有需要 就往哪里用 被套用了切面逻辑的方法就被切面给增强了

5.4 基于注解的AOP

5.4.1 技术说明

5.4.1.1 JDK动态代理和cglib动态代理

有接口 使用JDK动态代理 生成接口实现类代理对象 代理对象和目标对象实现同样的接口

没有接口 使用cglib动态代理 生成子类代理对象 通过继承被代理的目标类

5.4.1.2 AspectJ

AOP思想的一种的实现 本质上是静态代理 将代理逻辑织入被代理的目标类编译得到的字节码文件 所以最终效果是动态的 weaver就是织入器 Spring只是借用了AspectJ中的注解

5.4.2 准备工作

5.4.2.1添加依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.21</version>
</dependency
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.21</version>
</dependency>
5.4.2.2创建目标资源

①接口

public interface Calculator {

    int add(int i,int j);

    int sub(int i,int j);

    int mul(int i,int j);

    int div(int i,int j);

}

②实现类

@Component
public class CaculatorImpl implements Calculator {

    @Override
    public int add(int i, int j) {

        int result = i+j;
        System.out.println("方法内部result="+result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i-j;
        System.out.println("方法内部result="+result);
        return result;
    }

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

    @Override
    public int div(int i, int j) {
        int result = i/j;
        System.out.println("方法内部result="+result);
        return result;
    }
}

配置文件

<?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
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

<!--开启组件的扫描-->
    <context:component-scan base-package="com.szy.spring.aop.annoaop"></context:component-scan>
<!--开启aspectj自动代理 并为目标对象生成代理-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>


</beans>

5.4.3 创建切面类

切入点与通知类型

//切面类
@Component//交给IOC管理
@Aspect//切面类
public class LogAspect {
    //设置切入点和通知类型
    //通知类型:前置 返回 异常 后置 环绕
    /**
     * @Before
     * @AfterReturnong
     * @After
     * @Around
     */

    //前置 @Before(value = "切入点表达式配置切入点")
    //切入点表达式 execution(访问修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(方法参数))
    @Before(value = "execution(public int com.szy.spring.aop.annoaop.CaculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("Loger--> 前置通知 方法名称"+methodName+" 参数 "+ Arrays.toString(args));
    }


    //后置
    @After(value = "execution(* com.szy.spring.aop.annoaop.CaculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Loger--> 后置通知 方法名称"+methodName);
    }

    //返回 @AfterReturning
    @AfterReturning(value = "execution(* com.szy.spring.aop.annoaop.CaculatorImpl.*(..))",returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Loger--> 返回通知 方法名称"+methodName+"返回结果"+result);
    }

    //异常
    @AfterThrowing(value = "execution(* com.szy.spring.aop.annoaop.CaculatorImpl.*(..))",throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Loger--> 异常通知 方法名称"+methodName+"异常信息"+ex);
    }

    //环绕
    @Around(value = "execution(* com.szy.spring.aop.annoaop.CaculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        Object result=null;
        try {
            System.out.println("环绕通知==目标方法之前");
            //调用目标方法
            result = joinPoint.proceed();

            System.out.println("环绕通知==目标方法返回值之后");

        }catch (Throwable throwable){
            throwable.printStackTrace();
            System.out.println("环绕通知==目标方法出现异常执行");
        }finally {
            System.out.println("环绕通知==目标方法执行完毕执行");
        }
        return result;
    }

}

5.4.4 重用切入点表达式

5.4.4.1 声明
@Pointcut(value = "execution(* com.szy.spring.aop.annoaop.CaculatorImpl.*(..))")
public void pointcut(){
    

}
5.4.4.2 在同一个切面中使用
@Before(value = "pointcut()")
public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    Object[] args = joinPoint.getArgs();
    System.out.println("Loger--> 前置通知 方法名称"+methodName+" 参数 "+ Arrays.toString(args));
}
5.4.4.3 在不同切面使用
@Before(value = "com.szy.spring.aop.annoaop.LogAspect.pointcut()")
public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    Object[] args = joinPoint.getArgs();
    System.out.println("Loger--> 前置通知 方法名称"+methodName+" 参数 "+ Arrays.toString(args));
}

5.5 基于XML的AOP

配置类

<?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
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

<!--开启组件扫描-->

    <context:component-scan base-package="com.szy.spring.aop.xmlaop"></context:component-scan>


<!--配置aop的五中类型-->
    <aop:config>
    <!--配置切面类-->
        <aop:aspect ref="logAspect">
        <!--配置切入点-->
            <aop:pointcut id="pointcut" expression="execution(* com.szy.spring.aop.xmlaop.CaculatorImpl.*(..))"/>
        <!--配置五种通知类型-->
            <!--前置-->
            <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
            <!--后置-->
            <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
            <!--返回通知-->
            <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointcut"></aop:after-returning>

            <!--异常-->
            <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointcut"></aop:after-throwing>

            <!--环绕-->
            <aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>
//切面类
@Component//交给IOC管理
public class LogAspect {
    //设置切入点和通知类型
    //通知类型:前置 返回 异常 后置 环绕
    /**
     * @Before
     * @AfterReturnong
     * @After
     * @Around
     */

    //前置
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("Loger--> 前置通知 方法名称"+methodName+" 参数 "+ Arrays.toString(args));
    }


    //后置
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Loger--> 后置通知 方法名称"+methodName);
    }

    //返回
    public void afterReturningMethod(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Loger--> 返回通知 方法名称"+methodName+"返回结果"+result);
    }

    //异常
    public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Loger--> 异常通知 方法名称"+methodName+"异常信息"+ex);
    }

    //环绕
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        Object result=null;
        try {
            System.out.println("环绕通知==目标方法之前");
            //调用目标方法
            result = joinPoint.proceed();

            System.out.println("环绕通知==目标方法返回值之后");

        }catch (Throwable throwable){
            throwable.printStackTrace();
            System.out.println("环绕通知==目标方法出现异常执行");
        }finally {
            System.out.println("环绕通知==目标方法执行完毕执行");
        }
        return result;
    }
    
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值