spring之AOP介绍

AOP 面向切面编程

一、动态代理

​ 动态代理是指,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理对象只是由代理生成工具(不是真实定义的类)在程序运行时由 JVM根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。

1、JDK动态代理

​ 动态代理的实现方式常用的有两种:使用 JDK的Proxy,与通过CGLIB生成代理

Jdk的动态要求目标对象必须实现接口,这是java设计上的要求

​ 从jdk1.3以来,java 语言通过 java.lang.reflect包提供三个类支持代理模式Proy、Method 和 InovcationHandler

2、CGLIB动态代理(了解)

​ CGLIB(Code Generation Library)是一个开源项目。是一个强大的、高性能、高质量的Code生成类库,它可以在运行期扩展Java 类与实现Java 接口。它广泛的被许多AOP的框架使用,例如Spring AOP。

​ 使用 JDK 的Proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在接口,则无法使用该方式实现。但对于无接口的类,要为其创建动态代理,就要使用CGLIB来实现。

CGLIB代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。所以,使用CGLIB生成动态代理,要求目标类必须能够被继承,即不能是 final 的类。

​ CGLIB经常被应用在框架中,例如Spring ,Hibernate等。cglib 的代理效率高于Jdk。项目中直接使用动态代理的地方不多。一般都使用框架提供的功能。

3、动态代理的作用
  • 在目标类源代码不改变的情况下,增加功能
  • 减少代码的重复
  • 专注业务逻辑代码
  • 解耦合,让业务功能和日志,事务非业务功分离

二、不使用AOP的开发方式(理解)

1、创建目标类

SomeServiceImpl(目标类)

package cn.edu.huat.service.impl;

import cn.edu.huat.service.SomeService;

public class SomeServiceImpl implements SomeService {
    public void doSome() {
        System.out.println("执行业务方法doSome");
    }

    public void doOther() {
        System.out.println("执行业务方法doOther");
    }
}
2、创建InvocationHandler接口的实现类
(1)创建工具类util,增加功能
package cn.edu.huat.util;

import java.util.Date;

public class ServiceTools {
    public static void doLog(){
        System.out.println("方法的执行时间" + new Date());
    }

    public static void doTrans(){
        System.out.println("方法执行完毕后,提交事务");
    }
}
(2)实现目标方法的增加功能
package cn.edu.huat.handle;

import cn.edu.huat.util.ServiceTools;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {
    private Object target; //目标对象

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //通过代理对象执行方法时,会调用这个invoke()
        System.out.println("执行MyInvocationHandler中的Invoke()");
        System.out.println("method名称:" + method.getName());
        String methodName = method.getName();
        Object result = null;
        if ("doSome".equals(methodName)){
            //在目标方法执行之前,输出时间
            ServiceTools.doLog();
            //执行目标类的方法,通过Method类实现
            result = method.invoke(target,args);
            //在目标方法执行之后,提交事务
            ServiceTools.doTrans();
        } else {
            result = method.invoke(target,args);
        }
        //目标方法的执行结果
        return result;
    }
}
3、使用jdk中类Proxy,创建代理对象

实现创建对象的能力

package cn.edu.huat;

import cn.edu.huat.handle.MyInvocationHandler;
import cn.edu.huat.service.SomeService;
import cn.edu.huat.service.impl.SomeServiceImpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class MyTest {
    public static void main(String[] args) {
        //使用jdk的Proxy创建代理对象
        //创建目标对象
        SomeService target = new SomeServiceImpl();
        //创建InvocationHandler对象
        InvocationHandler handler = new MyInvocationHandler(target);
        //使用Proxy创建代理
        SomeService proxy = (SomeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler);
        //通过代理执行方法,会调用Handler中的invoke()
        proxy.doSome();
        System.out.println("=====================");
        proxy.doOther();
    }
}

在这里插入图片描述

三、AOP简介

AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。

​ AOP底层,就是采用动态代理模式实现的。采用了两种代理:

  • JDK的动态代理
  • CGLIB的动态代理

​ AOP为 Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技 术。AOP是Spring 框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

​ 面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等

​ 若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。

​ 例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的 “冗余” 代码,还大大干扰了主业务逻辑 ------- 如转账。

如何理解面向切面编程?

  • 需要在分析项目功能时,找出切面

  • 合理的安排切面的执行时间(在目标方法前,还是目标方法后)

  • 合理的安全切面执行的位置,在哪个类,哪个方法增加增强功能

    何时考虑使用aop技术?

  • 当要给一个系统中存在的类修改功能 。但是原有类的功能不完善,并且还有源代码时,使用aop增加功能

  • 要给项目中的多个类,增加一个相同的功能时,使用aop

  • 给业务方法增加事务、日志输出等功能

四、AOP编程术语(掌握)

1、切面(Aspect)

​ 切面,表示增强的功能。就是一堆代码,完成某一个功能。非业务功能。常见的切面功能有日志、事务、统计信息、参数检查、权限验证。

​ 常用的切面是通知(Advice)。 实际就是对主业务逻辑的一种增强。

2、连接点(JoinPoint)

​ 连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

3、切入点(Pointcut)

​ 切入点,指多个连接点方法的集合。多个方法

​ 切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。

​ 被标记为final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的

4、目标对象(Target)

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

5、通知(Advice)

​ 表示切面功能执行的时间

6、切面三要素
(1)切面的功能代码

​ 使用Aspect表示切面干什么
在这里插入图片描述

(2)切面的执行位置

​ 使用Pointcut表示切面执行的位置
在这里插入图片描述

(3)切面的执行时间

​ 使用Advice表示时间,在目标方法之前,还是目标方法之后。
在这里插入图片描述

五、AspectJ对AOP的实现(掌握)

​ 对于AOP这种编程思想,很多框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了AOP的功能,且其实现方式更为简捷,使用更为方便。而且还支持注解式开发。所以,Spring 又将AspectJ 对于AOP的实现也引入到了自己的框
架中。

​ 在Spring中使用AOP开发时,一般使用AspectJ 的实现方式。

AspectJ 是一个优秀面向切面的框架,它扩展了Java 语言,提供了强大的切面实现。

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

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

  • 使用xml 的配置文件:配置全局事务
  • 使用注解,在项目中要做aop功能,一般都使用注解
1、AspectJ的通知类型
(1)@Aspect

aspectJ 框架中的注解

  • 作用:表示当前类是切面类。
  • 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
  • 位置:在类定义的上面
(2)@Before

前置通知注解

  • 属性:value,是切入点表达式,表示切面的功能执行的位置
  • 位置:在方法的上面
  • 特点
    • 在目标方法之前先执行的
    • 不会改变目标方法的执行结果
    • 不会影响目标方法的执行
(3)@AfterReturning

后置通知

  • 属性
    • value,切入点表达式
    • returning自定义的变量,表示目标方法的返回值
    • 自定义变量名必须和通知方法的形参名一样
  • 位置:在方法定义的上面
  • 特点
    • 在目标方法之后执行的
    • 能够获取到目标方法的返回值, 可以根据这个返回值做不同的处理功能
    • 可以修改这个返回值
(4)@Around

环绕通知

  • 属性:value切入点表达式

  • 位置:在方法的定义上面

  • 特点

    • 功能最强的通知
    • 在目标方法的前和后都能增強功能
    • 控制目标方法是否被调用执行
    • 修改原来的目标方法的执行结果,影响最后的调用结果
  • 环绕通知,等同于jdk 动态代理的 InvocationHandler 接口

  • 参数:ProceedingJoinPoint 等同于 Method

  • 作用:执行目标方法

  • 返回值:目标方法的执行结果 ,可以被修改

(5)@AfterThrowing

异常通知

  • 属性
    • value,切入点表达式
    • throwinng 自定义的变量,表示目标方法抛出的异常对象
    • 变量名必须和方法的参数名一样
  • 特点
    • 在目标方法抛出异常时执行的
    • 可以做异常的监控程序,监控目标方法执行时是不是有异常
    • 如果有异常,可以发送邮件,短信等进行通知
(6)@After

最终通知

  • 属性:value,切入点表达式
  • 位置:在方法的上面
  • 特点
    • 总是会执行
    • 在目标方法之后执行的
(7)@Pointcut

定义和管理切入点,如果你的项目中有多个切入点表达式是重复的,可以复用的。可以使用@Pointcut

  • 属性:value,切入点表达式
  • 位置:在自定义的方法上面
  • 特点
    • 当使用@Pointcut 定义在一个方法的上面。此时这个方法的名称就是切入点表达式的别名
    • 其它的通知中,value属性可以使用这个方法名称,代替切入点表达式
2、AspectJ的切入点表达式

切入点表达式:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws pattern?)
//execution(访问权限 方法返回值 方法声明(参数) 异常类型)

解释:

  • modifiers-pattern:访问权限类型
  • ret-type-pattern:返回值类型
  • declaring-type-pattern:包名类名
  • name- pattern(param-pattern):方法名(参数类型和参数个数)
  • throws-pattern:抛出异常类型
  • ?:表示可选的部分

​ 切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

  • *:0至多个任意字符
  • :用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包路径
  • +:用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类

常用举例:

execution(public * *(..))		//指定切入点为:任意公共方法
execution(* set*(..))		//任何一个以“set"开始的方法
execution(* com.xyz.service.*.*(..)			//定义在service包里的任意类的任意方法
execution(* com.xyz.service..*.*(..))		//定义在service包或者子包里的任意类的任意方法。“."出现在类名中时,后面必须跟“*”,表示包、子包下的所有类
execution(* *..service.*.*(..))		//指定所有包下的serivce子包下所有类(接口)中所有方法为切入点
3、AspectJ的开发环境
(1)maven依赖
<!--spring依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
<!--aspectj依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
(2)引入AOP约束

​ 在AspectJ 实现AOP时,要引入AOP的约束。配置文件中使用的AOP约束中的标签,均是AspectJ 框架使用的,而非Spring框架本身在实现AOP时使用的。

​ AspectJ 对于AOP的实现有注解和配置文件两种方式,常用是注解方式。

4、Aspect基于注解的AOP实现
(1)实现步骤
  • 创建目标类:接口和他的实现类。要做的是给类中的方法增加功能

  • 创建切面类:普通类

    • 在类的上面加入@Aspect
    • 在类中定义方法,方法就是切面要执行的功能代码。在方法的上面加入aspectj中的通知注解, 例如@Before有需要指定切入点表达式execution()
  • 创建spring的配置文件:声明对象,把对象交给容器统一管理。声明对象可以使用注解或者xml 配置文件<bean>

    • 声明目标对象
    • 声明切面类对象
    • 声明aspectJ 框架中的自动代理生成器标签。自动代理生成器:用来完成代理对象的自动创建功能的
  • 创建测试类,从spring容器中获取目标对象(实际就是代理对象)。通过代理执行方法,实现aop的功能增强。

(2)@Before前置通知-方法有JoinPoint参数

​ 在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。

​ 不光前置通知的方法,可以包含一个JoinPoint类型参数,所有的通知方法均可包含该参数。

举例:

MyAspect(切面类)

前置通知定义方法,方法是实现切面功能的。

方法的定义要求:

  • 公共方法public
  • 方法没有返回值
  • 方法名称自定义
  • 方法可以有参数,也可以没有参数
  • 如果有参数,参数不是自定义的,有几个参数类型可以使用
package cn.edu.huat.sp01;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;

@Aspect
public class MyAspect {
    @Before(value = "execution(public void cn.edu.huat.sp01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(){
        //切面要执行的功能代码
        System.out.println("前置通知,切面功能:在目标方法之前输出时间:" + new Date());
    }
}

applicationContext.xml(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容器统一创建,管理对象-->
	<!--声明目标对象--> 
    <bean id="SomeService" class="cn.edu.huat.sp01.SomeServiceImpl" />
    <!--声明切面类对象-->
    <bean id="MyAspect" class="cn.edu.huat.sp01.MyAspect" />
    <!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。创建代理对象是在内存中实现的,修改目标对象的		内存中的结构。创建为代理对象所以目标对象就是被修改后的代理对象。
	   aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象-->
    <aop:aspectj-autoproxy />
</beans>

MyTest01(测试类)

package cn.edu.huat;

import cn.edu.huat.sp01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest01 {
    @Test
    public void test01(){
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标对象
        SomeService proxy = (SomeService) ctx.getBean("SomeService");
        //通过代理的对象执行方法,实现目标方法执行时,增强了功能
        proxy.doSome("李四",22);
    }
}

在这里插入图片描述
指定通知方法中的参数:JoinPoint

  • JoinPoint:业务方法,要加入切面功能的业务方法
  • 作用:可以在通知方法中获取方法执行时的信息,例如方法名称, 方法的实参
  • 如果切面功能中需要用到方法的信息,就加入JoinPoint。这个JoinPoint参数的值是由框架赋予,必须是第一个位置的参数
package cn.edu.huat.sp01;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;

@Aspect
public class MyAspect {
    @Before(value = "execution(* *..SomeServiceImpl.do*(..))")
    public void myBefore(JoinPoint joinPoint){
        //获取方法的完整定义
        System.out.println("方法的签名(定义) = " + joinPoint.getSignature());
        System.out.println("方法的名称 = " + joinPoint.getSignature().getName());
        //获取方法的实参
        Object args[] = joinPoint.getArgs();
        for (Object arg:args){
            System.out.println("参数 = " + arg);
        }
        //切面要执行的功能代码
        System.out.println("前置通知,切面功能:在目标方法之前输出时间:" + new Date());
    }
}

在这里插入图片描述

(3)@AfterReturning后置通知-注解有returning属性

​ 在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含JoinPoint参数外,还可以包含用于接收返回值的变量。该变量最好为Object 类型,因为目标方法的返回值可能是任何类型。

后置通知定义方法,方法是实现切面功能的。

方法的定义要求:

  • 公共方法public
  • 方法没有返回值
  • 方法名称自定义
  • 方法有参数的,推荐是object,参数名自定义

SomeService(接口)

package cn.edu.huat.sp02;

public interface SomeService {
    String doOther(String name,Integer age);
}

SomeServiceImpl(实现方法)

package cn.edu.huat.sp02;

public class SomeServiceImpl implements SomeService {
    @Override
    public String doOther(String name, Integer age) {
        System.out.println("===目标方法doOther()===");
        return "abc";
    }
}

MyAspect(切面类)

package cn.edu.huat.sp02;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

@Aspect
public class MyAspect {
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "result")
    public void myAfterReturning(Object result){
        //Object result:是目标方法执行后的返回值,根据返回值做你的切面的处理功能
        System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:" + result);
    }
}

在这里插入图片描述
后置通知的执行:

object result = doother(); 
//参数传递:传值,传引用
myAfterReturing(result);
System.out.println("result = " + result);
(4)@Around环绕通知-增强方法有ProceedingJoinPoint参数

​ 在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个ProceedingJoinPoint 类型的参数。接口ProceedingJoinPoint 其有一个proceed() 方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

环绕通知方法的定义格式

  • 公共方法public
  • 必须有一个返回值,推荐使用Object
  • 方法名称自定义
  • 方法有参数,固定的参数 ProceedingJoinPoint

SomeService(接口)

package cn.edu.huat.sp03;

public interface SomeService {
    String doFirst(String name,Integer age);
}

SomeServiceImpl(实现方法)

package cn.edu.huat.sp03;

public class SomeServiceImpl implements SomeService {
    @Override
    public String doFirst(String name, Integer age) {
        System.out.println("===业务方法doFirst()===");
        return "doFirst";
    }
}

MyAspect(切面类)

package cn.edu.huat.sp03;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

@Aspect
public class MyAspect {
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        //实现环绕通知
        Object result = null;
        System.out.println("环绕感知:在目标方法之前,输出时间:" + new Date());
        //1、目标方法调用
        result = pjp.proceed(); //等同于method.invoke();或者Object result = doFirst();
        //2、在目标方法的前或后增加功能
        System.out.println("环绕通知:在目标方法之后,提交事务");
        //返回目标方法的执行结果
        return result;
    }
}

在这里插入图片描述
控制目标方法是否被调用执行演示:

package cn.edu.huat.sp03;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

@Aspect
public class MyAspect {
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        String name = "";
        //获取第一个参数值
        Object args[] = pjp.getArgs();
        if (args != null && args.length > 1){
            Object arg = args[0];
            name = (String)arg;
        }
        //实现环绕通知
        Object result = null;
        System.out.println("环绕感知:在目标方法之前,输出时间:" + new Date());
        //1、目标方法调用
        if ("王五".equals(name)){
            //符合条件,调用目标方法
            result = pjp.proceed(); //等同于method.invoke();或者Object result = doFirst();
        }
        //2、在目标方法的前或后增加功能
        System.out.println("环绕通知:在目标方法之后,提交事务");
        //返回目标方法的执行结果
        return result;
    }
}

输出不符合条件的参数输出结果如下:
在这里插入图片描述
修改原来的目标方法的执行结果演示:

package cn.edu.huat.sp03;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

@Aspect
public class MyAspect {
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        String name = "";
        //获取第一个参数值
        Object args[] = pjp.getArgs();
        if (args != null && args.length > 1){
            Object arg = args[0];
            name = (String)arg;
        }
        //实现环绕通知
        Object result = null;
        System.out.println("环绕感知:在目标方法之前,输出时间:" + new Date());
        //1、目标方法调用
        if ("王五".equals(name)){
            //符合条件,调用目标方法
            result = pjp.proceed(); //等同于method.invoke();或者Object result = doFirst();
        }
        //2、在目标方法的前或后增加功能
        System.out.println("环绕通知:在目标方法之后,提交事务");
        //修改目标方法的执行结果,影响最后的调用结果
        if (result != null){
            result = "Hello AspectJ AOP";
        }
        //返回目标方法的执行结果
        return result;
    }
}

MyTest03(测试类)

package cn.edu.huat;

import cn.edu.huat.sp03.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest03 {
    @Test
    public void test03() {
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        SomeService proxy = (SomeService) ctx.getBean("SomeService");
        String str = proxy.doFirst("王五", 33);
        System.out.println("str = " + str);
    }
}

在这里插入图片描述
注意:环绕通知(Around)经常做事务, 在目标方法之前开启事务,执行目标方法在目标方法之后提交事务

(5)@AfterThrowing异常通知-注解中有throwing属性(了解)

​ 在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。

异常通知方法的定义格式

  • 公共方法public
  • 没有返回值
  • 方法名称自定义
  • 方法有一个参数Exception, 如果还有则是 JoinPoint

SomeService(接口)

package cn.edu.huat.sp04;

public interface SomeService {
    void doSecond();
}

SomeServiceImpl(实现方法)

package cn.edu.huat.sp04;

public class SomeServiceImpl implements SomeService {
    @Override
    public void doSecond() {
        System.out.println("执行业务方法doSecond()" + (10/0));
    }
}

MyAspect(切面类)

package cn.edu.huat.sp04;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

@Aspect
public class MyAspect {
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "e")
    public void myAfterThrowing(Exception e){
        System.out.println("异常通知:方法发生异常时,执行:" + e.getMessage());
        //发送邮件,短信,通知开发人员
    }
}

在这里插入图片描述
执行时相当于就是以下程序:

try{
	SomeServiceImpl.doSecond(..);
}catch(Exception e){
	myAfterThrowing(e); 
}
(6)@After最终通知(了解)

​ 无论目标方法是否抛出异常,该增强均会被执行。

最终通知方法的定义格式

  • 公共方法public
  • 没有返回值
  • 方法名称自定义
  • 方法没有参数,如果有则是 JoinPoint

SomeService(接口)

package cn.edu.huat.sp05;

public interface SomeService {
    void doThird();
}

SomeServiceImpl(实现方法)

package cn.edu.huat.sp05;

public class SomeServiceImpl implements SomeService {
    @Override
    public void doThird() {
        System.out.println("执行业务方法doThird()" + (10/0));
    }
}

MyAspect(切面类)

package cn.edu.huat.sp05;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MyAspect {
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
        System.out.println("执行最终通知,总是会被执行的代码");
        //一般做清除资源的工作
    }
}

在这里插入图片描述
执行时相当于就是以下程序:

try{
	SomeServiceImpl.doThird(..);
} catch(Exception e) {
    
} finally {
	myAfter();
}
(7)@Pointcut定义切入点

​ 当较多的通知增强方法使用相同的execution切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut注解,用于定义execution切入点表达式。

​ 其用法是,将@Pointcut注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut定义的切入点。这个使用@Pointcut注解的方法一般使用 private 的标识方法,即没有实际作用的方法。

package cn.edu.huat.sp06;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MyAspect {
    @After(value = "myPc()")
    public void myAfter(){
        System.out.println("执行最终通知,总是会被执行的代码");
    }

    @Before(value = "myPc()")
    public void myBefore(){
        System.out.println("前置通知,在目标方法执行之前执行");
    }

    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
    private void myPc(){
        //无需代码
    }
}

在这里插入图片描述

六、cglib代理

1、没有接口是cglib代理

MyTest07(测试类)

package cn.edu.huat;

import cn.edu.huat.sp07.SomeServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest07 {
    @Test
    public void test03() {
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        SomeServiceImpl proxy = (SomeServiceImpl) ctx.getBean("SomeService");
        System.out.println("proxy = " + proxy.getClass().getName());
        proxy.doThird();
    }
}

目标类没有接口,使用cglib动态代理,spring 框架会自动应用cglib
在这里插入图片描述

2、有接口也可使用cglib代理
<?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">

    <bean id="SomeService" class="cn.edu.huat.sp08.SomeServiceImpl" />
    <bean id="MyAspect" class="cn.edu.huat.sp08.MyAspect" />
    <!--告诉框架,要使用cglib动态代理-->
    <aop:aspectj-autoproxy proxy-target-class="true" />
</beans>

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值