Spring(三)spring核心技术——aop

2022-08-02,,

一、AOP简介

  AOP是Aspect Orient Programming的缩写,即面向切面编程。基于动态代理的,可以使用jdk和cglib两种代理方式。

  • Aspect: 切面,给你的目标类增加的功能,就是切面。像上面用的日志,事务都是切面。切面的特点:一般都是非业务方法,独立使用的。

  • Orient:面向,对着。

  • Programming:编程

  AOP就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理。

作用:

  • 在目标类不修改的情况下增加功能

  • 减少代码的重复

  • 使开发人员专注业务功能的实现

  • 解耦合:业务功能和日志、事务等非业务功能的耦合

二、动态代理的实现方式

  • jdk动态代理

  使用jdk中的Proxy,Method,InvocaitonHanderl创建代理对象。jdk动态代理要求目标类必须实现接口

  • cglib动态代理

  第三方的工具库,创建代理对象,原理是继承。 通过继承目标类,创建子类。子类就是代理对象。要求目标类不能是final的,方法也不能是final的。

三、AOP中名词概念

  • aspect(切面):表示给业务方法增加的功能,一般为体制输出、事务、权限检查等

  • JoinPoint:连接点,连接业务方法和切面的位置,就某类中的业务方法

  • pointcut(切入点):是一个或多个JoinPoint的集合,表示切面功能执行的位置

  • 目标对象:给哪个类的方法增加功能,这个类就是目标对象

  • advice(通知):也叫增强。表示切面执行的时间,在方法前或后

四、何时使用AOP

  • 某项目功能类不完善,需要增加功能,但是没有源代码

  • 给项目的多个类需要增加相同功能

  • 为业务功能增加事务、日志输出

五、AOP的实现

1、Spring

  Spring在内部实现了aop规范,能做aop的工作。spring主要在事务处理时使用aop,在项目开发中很少使用spring的aop实现,因为spring的aop比较笨重。

2、使用Aspectj框架实现AOP

  一个开源的专门做aop的框架。spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能。

aspectJ框架实现aop有两种方式:

1)使用xml的配置文件 : 配置全局事务

2)使用注解,项目中要做aop功能,一般都使用注解

  • 使用相应注解确定切面执行的时间

  • 使用切面表达式确定切面执行的位置

  切面位置的切入点表达式:execution(修饰符 返回值 包名.类名.方法名(方法参数) 异常)。用来指定切面执行的位置。

aspectj中常用注解如下:

  1. @Aspect:声明该类是切面类
/*
*   @Aspect:是Aspectj框架中的注解
*       作用:声明该类是切面类
*       切面类:为目标类增加功能的类,包含切面的功能代码
*       位置:在类的上面
* */
@Aspect
public class MyAspect {}
  1. @Before:前置通知,在目标方法之前执行切面的功能
@Aspect
public class MyAspect {
    /*
    *   定义方法,是实现切面功能的方法
    *     方法定义要求:
    *       1、方法时公共的
    *       2、方法名自定义
    *       3、方法没有返回值
    *       4、可以有参数,也可以无参数
    *           如果有参数,参数不是自定义的
    *   @Before:前置通知注解
    *       属性:value,是切入点表达式,表明切面的功能执行的位置
    *       位置:在方法的上面
    *   特点:
    *       1、在目标方法前执行
    *       2、不会改变和影响目标方法的执行
    * */
    @Before(value = "execution(public void cn.krain.ba01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(){
        System.out.println("前置通知,切面功能,在目标方法执行前输出执行的时间:"+new Date());
    }
  1. @AfterReturning:后置通知,在目标方法之后执行切面的功能,能获取返回值
@Aspect
public class MyAspect {
    /*
    *   @AfterReturning定义方法,是实现切面功能的方法
    *     方法定义要求:
    *       1、方法时公共的
    *       2、方法名自定义
    *       3、方法没有返回值
    *       4、方法有参数
    *   @AfterReturning:后置通知
    *       属性:1、value:切入点表达式
    *             2、returning:自定义变量,目标方法的返回值
    *                           自定义变量名称和通知方法的形参名一致
    *       位置:在方法定义的上面
    *   特点:
    *   1、在目标方法之后执行
    *   2、能获取目标方法的返回值
    *   3、可修改这个返回值
    * */
    @AfterReturning(value = "execution(* cn.krain.ba02.SomeServiceImpl.doOther(..))",
                    returning="res")
    public void myAfterReturning(Object res){
        System.out.println("后置通知,切面功能,在目标方法执行前输出执行的时间:"+res);
        if (res!=null){
            res = "Hello";
        }
    }
}
  1. @Around:环绕通知,能在目标方法前后增强功能,能够控制目标方法的执行,修改返回值
@Aspect
public class MyAspect {
    /*
    *   环绕通知的定义格式
    *     方法定义要求:
    *       1、方法时公共的
    *       2、方法名自定义
    *       3、必须有返回值
    *       4、方法有固定的参数:proceedingJoinPoint
    *   @Around:环绕通知
    *       属性:value 切入点表达式
    *       位置:在方法的定义上面
    *   特点:
    *       1、是功能最强的通知
    *       2、能够在目标方法前后增强功能
    *       3、控制目标方法是否被执行
    *       4、修改原来目标方法的执行结果,影响最后的调用结果
    * */
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))" )
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {

        //获取第一个参数的值
        String name = null;
        Object args [] = joinPoint.getArgs();
        if (args!=null && args.length>=1){
            Object arg = args[0];
            name = (String) arg;
        }

        Object result = null;

        //实现环绕通知
        System.out.println("环绕通知,在目标方法之前,输出时间:"+new Date());

        //1、目标方法调用
        //控制目标方法的执行
        if (name.equals("张三")){
            result = joinPoint.proceed();       //等同于执行目标方法
        }

        System.out.println("环绕通知,在目标方法之后,提交事务");

        //修改目标函数返回结果
        if (result!=null){
            result = "Hello Aop";
        }
        return result;
    }
}
  1. @AfterThrowing:异常通知,在目标方法抛出异常后执行
@Aspect
public class MyAspect {
    /*  异常通知的定义格式
    *     方法定义要求:
    *       1、方法时公共的
    *       2、方法名自定义
    *       3、方法没有返回值
    *       4、固定参数Exception,如果还有参数则是:JoinPoint
    *       5、throwing的值要与Exception的参数名相同
    *   @AfterThrowing:异常通知
    *       属性:1、value 切入点表达式
    *            2、throwing 自定义变量,表示目标方法抛出的异常对象
    *                       如果有异常,通过邮件、短信通知
    *   特点:
    *       1、在目标方法出现异常时执行
    *       2、监控目标方法是否存在异常
    *
    *   执行过程:
    *       try{
    *           SomeServiceImpl.doSecond();
    *       }catch(Exception e){
    *           myAfterThrowing(e);
    *       }
    * */
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond())",
                    throwing = "ex")
    public void myAfterThrowing(Exception ex){
        System.out.println("异常通知,方法执行异常,执行:"+ex.getMessage());
        //发送短信
    }
}
  1. @After:最终通知,总是会执行
@Aspect
public class MyAspect {
    /*  最终通知的定义格式
    *   方法定义要求:
    *       1、方法时公共的
    *       2、方法名自定义
    *       3、方法没有返回值
    *       4、没有参数,如果有参数则是:JoinPoint
    * */

    /*
    *   @After  最终通知
    *       属性:value  切入表达式
    *       位置:在方法的上面
    *
    *   特点:
    *       1、总是会执行
    *       2、在目标方法之后执行
    *
    *   执行过程:
    *       try{
    *           //目标方法
    *       }catch(){
    *
    *       }finally{
    *           myAfter();
    *       }
    * */
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
        System.out.println("执行最终通知,总是会被执行的代码");
    }
}
  1. @Pointcut:定义和管理切入点的辅助注解
@Aspect
public class MyAspect {
    @After(value = "myPT()")
    public void myAfter(){
        System.out.println("执行最终通知,总是会被执行的代码");
    }

    @Before(value = "myPT()")
    public void myBefore(){
        System.out.println("执行前置通知");
    }
    /*
    *   @Pointcut:定义和管理切入点表达式,如果项目中的切入点表达式是重复的,可使用Pointcut
    *       属性:value  切入点表达式
    *       位置:在自定义方法上面
    *   为切入点表达式定义别名,使用"方法名称()"代替切入点表达式
    * */
    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
    private void myPT(){
        //无功能代码
    }
}
  • 编写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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--把对象交给spring,有spring统一创建和管理-->
    <!--创建SomeService目标对象-->
    <bean name="someService" class="cn.krain.ba.SomeServiceImpl" />

    <!--创建aspect切面对象-->
    <bean name="myAspect" class="cn.krain.ba.MyAspect" />

    <!--
        声明自动代理生成器:使用aspectj的内部功能,创建目标对象的代理对象。
        创建代理对象是在内存中实现的,修改目标对象在内存中的结构,创建成代理对象
        因此,目标对象就是被修改后的代理对象
    -->
    <aop:aspectj-autoproxy />

    <!--
        如果项目中有接口时,仍希望使用CGHLIB动态代理
        proxy-target-class="true":告诉框架使用cglib动态代理
    -->
    <aop:aspectj-autoproxy proxy-target-class="true" />
</beans>
  • 测试类MyTest.java
public class MyTest {
    public void Test(){
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //根据对象名获取代理对象
        SomeService proxy = (SomeService) ac.getBean("someService");
        //proxy."目标方法名"();
    }
}

本文地址:https://blog.csdn.net/ccccc_chuang/article/details/107389467

《Spring(三)spring核心技术——aop.doc》

下载本文的Word格式文档,以方便收藏与打印。