Spring详解之AOP

Spring整合单元测试

在前面的案例中我么需要自己创建ApplicationContext对象,然后在调用getBean来获取需要测试的Bean

Spring提供了一种更加方便的方式来创建测试所需的ApplicationContext,并且可以帮助我们把需要测试的Bean直接注入到测试类中

添加依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

我们讲解了spring注解注入,于是我们想用注解来进行测试,就可以像下面这样:

@RunWith(SpringJUnit4ClassRunner.class)//整合测试类
@ContextConfiguration("classpath:applicationContext.xml")//加载配置文件
public class MyTest {

    @Autowired//注入要测试的类
    private UserService userService;

    @Test
    public void test1(){
        userService.sayHi();//执行要测试的方法
    }
}

AOP概念

AOP为Aspect Oriented Programming的缩写,翻译为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象)的延续,是软件开发中的一个热点,也 是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分 进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效 率。

是不是有点懵,还是带着问题继续看看吧!!!

为什么需要AOP

案例分析

在项目开发中我们经常遇到一系列通用需求比如:权限控制,日志输出,事务管理,数据统计等,这些看似简单的需求,在实际开发中却会带来麻烦,举个例子:

在某个的Dao层如UserDao,存在以下几个方法:

public class UserDao{
  public void save(){
    System.out.println("save sql");
  }
  public void delete(){
    System.out.println("delete sql");
  }
  public void update(){
    System.out.println("update sql");
  }
}

已经实现了程序的实际功能,但是后来发现数据库操作出现瓶颈,这时领导说要对这些方法进行执行时间统计,并输出日志分析问题;

解决方案
你一顿操作猛如虎,写出了下面代码:

public void save(){
    //初始时间
    long time = new Date().getTime();
    System.out.println("save sql");
    //时间差
    long l = new Date().getTime() - time;
    System.out.println(l);
  }

问题
需求实现了,但是作为优秀的开发工程师你当然不会就这么完事了,因为上述代码存在以下问题:

1.修改了源代码(违反了OCP,违反了对修改封闭,但满足调用方式不变)

2.如果大量方法需要执行,每个都这样操作,存在大量重复代码

AOP登场

AOP之所以出现就是因为,我们需要对一些已经存在的方法进行功能扩展,但是又不能通过修改源代码或改变调用方式的手段来解决

反过来说就是要在保证不修改源代码以及调用方式不变的情况下为原本的方法增加功能

而由于需要扩展的方法有很多,于是把这些方法称作一个切面,即切面就是一系列需要扩展功能的方法的集合

AOP的目的
将日志记录,性能统计,安全控制,事务处理,异常处理等重复代码从业务逻辑代码中划分 出来,通过对这些行为的分离,我们希望可以将它们独立到非业务逻辑的方法中,进而改变这些行为的时候不会影响业务逻辑的代码。

AOP相关术语

连接点(joinpoint)

是扩展内容与原有内容的交互的点,可以理解为可以被扩展的地方,通常是一个方法,而AspectJ中也支持属性作为连接点

示例:案例中的三个方法

切点(pointcut)

切点指的是要被扩展(增加了功能)的内容,包括方法或属性(joinpoint)

示例:案例中的两个增加了功能的方法

通知(adivce)

通知指的是要在切点上增加的功能

示例:上述案例中的输出执行时间功能

按照执行时机不同分为:

前置,后置,异常,最终,环绕,引介

引介通知指的是在不修改类代码的前提下,为类增加方法或属性(了解即可非重点)

目标(target)

目标就是要应用通知的对象,即要被增强的对象

示例:上述案例中的userDao

织入(weaving)

织入是一个动词,描述的是将扩展功能应用到target的这个过程

示例:案例中修改源代码的过程

代理(proxy)

Spring是使用代理来完成AOP,对某个对象增强后就得到一个代理对象;

Spring AOP的整个过程就是对target应用advice最后产生proxy,我们最后使用的都是proxy对象;

切面(aspect)

是切入点和通知的结合切面,是一个抽象概念; 一个切面指的是所有应用了同一个通知的切入点的集合

示例:案例中的切入点(save 和 delete方法)和通知(输出执行时间功能)共同组成一个切面

AOP的传统实现

原理图:
image-20200425222100912
图片写的很清楚,就不多说了,直接代码实现;

按上图进行实现:

public interface UserDao {

    void save();

    void delete();

    void update();
}

代理类:

在代理类中添加要增强的代码,再代码的适当位置调用实现类的方法。

public class MyProxy1 implements UserDao {
    private UserDao target;

    public MyProxy1() {
    }

    public MyProxy1(UserDao target) {
        this.target = target;
    }

    @Override
    public void save() {
        long time = new Date().getTime();
        target.save();
        long l = new Date().getTime() - time;
        System.out.println(l);
    }

    @Override
    public void delete() {
        long time = new Date().getTime();
        target.delete();
        long l = new Date().getTime() - time;
        System.out.println(l);
    }

    @Override
    public void update() {
        long time = new Date().getTime();
        target.update();
        long l = new Date().getTime() - time;
        System.out.println(l);
    }
}

实现类:

public class UserDaoImpl implements UserDao{
  @Test
  public void save(){
    System.out.println("save sql");
  }
  public void delete(){
    System.out.println("delete sql");
  }
  public void update(){
    System.out.println("update sql");
  }
}

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MyTest {
    private UserDao userDao = new MyProxy1(new UserDaoImpl());

    @Test
    public void test1(){
        userDao.update();
    }
}

问题:

上面代理虽然可以实现我们需要的功能,并且遵循OCP原则,但是每一个方法都要填写一次通知,造成代码大量冗余。

对于上述问题官方动态代理给了我们解决办法。

动态代理(官方)

JDK1.4出现的

代理结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QJd61Hr7-1588343366413)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1588230873304.png)]

上面代理类可以按下面这样写:

public class MyProxy implements InvocationHandler {
    //代理类型
    private UserDao userDao;
    //无参构造方法
    public MyProxy() {
    }
    //有参构造
    public MyProxy(UserDao userDao) {
        this.userDao = userDao;
    }
    //获取代理对象
    public Object getProxy(){
        //reflect包下的
        //getClassLoader类加载器,动态帮我们生成类
        //getInterfaces 实现的接口有哪些
        //this 是代理方法有谁来做,也就是实现InvocationHandler接口的类,因为本类实现了,所以直接传入this
       return Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),this);
    }
    // InvocationHandler接口里的invoke方法就是要增强的功能
    /**
     *
     * @param proxy 代理对象
     * @param method 要执行的方法
     * @param args 参数列表
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long time = new Date().getTime();
        Object invoke = method.invoke(userDao, args);
        long l = new Date().getTime() - time;
        System.out.println(l);
        return invoke;
    }
}

测试:`

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MyTest {

    private UserDao userDao = (UserDao) new MyProxy(new UserDaoImpl()).getProxy();

    @Test
    public void test1(){
        userDao.update();
    }
}

当我们要对某些方法进行权限控制时也非常简单,只需要判断方法名称,然后增加权限控制逻辑即可;

注意:

1.动态代理,要求被代理的target对象必须实现了某个接口,且仅能代理接口中声明的方法,这给开发带来了一些限制,当target不是某接口实现类时,则无法使用动态代理,CGLib则可以解决这个问题

2.被拦截的方法包括接口中声明的方法以及代理对象和目标对象都有的方法如:toString

3.对代理对象执行这些方法将造成死循环
CGLib

CGLib(字节码生成库)是第三方库,需要添加依赖:

CGLib代理结构:

在这里插入图片描述

接口和实现类不变,下面是CGLib代理类:

public class CGLibProxy implements MethodInterceptor {
    private Object target;

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

    public Object getProxy() {
        //CGlib核心类
        //增强类
        Enhancer enhancer = new Enhancer();
        //设置父类,实现与目标相同的接口
        enhancer.setSuperclass(target.getClass());
        //回调,当有人调用增强类,就会调用这个类
        enhancer.setCallback(this);
        //产生代理对象
        return enhancer.create();
    }

    /**
     *
     * @param proxy 代理对象
     * @param method 外界要执行的方法
     * @param objects 传递的参数
     * @param methodProxy 方法代理对象 用于执行方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        //添加额外的逻辑
        System.out.println("方法:"+method.getName());

        //调用原始方法
        Object o = methodProxy.invokeSuper(proxy, objects);

        return o;
    }
}

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MyTest {
	//调用动态代理
    private UserDao userDao = (UserDao) new CGLibProxy(new UserDaoImpl()).getProxy();

    @Test
    public void test1(){
        userDao.update();
    }
}

注意:

1.CGLib可以拦截代理目标对象的所有方法

2.CGLib采用的是产生一个继承目标类的代理类方式产生代理对象,所以如果类被final修饰将无法使用CGLib

Spring就是利用上述就可以实现AOP

Spring中的AOP

Spring在运行期,可以自动生成动态代理对象,不需要特殊的编译器,Spring AOP的底层就是通过JDK动态代理和CGLib动态代理技术 为目标Bean执行横向织入。

Spring会自动选择代理方式

1.若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。

2.若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。

Spring通知类型:
前置 org.springframework.aop.MethodBeforeAdvice 用于在原始方法执行前的预处理

后置 org.springframework.aop.AfterReturningAdvice 用于在原始方法执行后的后处理

环绕 org.aopalliance.intercept.MethodInterceptor 这个名字不知道谁给起的,其实不算是通知,而是叫拦截器,在这里我们可以阻止原始方法的执行,而其他通知做不到

异常 org.springframework.aop.ThrowsAdvice 用于在原始方法抛出异常时处理

最终:org.springframework.aop.AfterAdvice 是在目标方法执行之后执行的通知。无论如何都会在目标方法调用过后都会执行。

引介org.springframework.aop.IntroductionInterceptor在目标类中添加一些新的方法和属

性(非重点)

注意:

最终通知是在目标方法执行之后执行的通知,和后置通知不同之处在于,后置通知是在方法正常返回后执行的通知,如果方法没有正常返-例如抛出异常,则后置通知不会执行。而最终通知无论如何都会在目标方法调用过后执行,即使目标方法没有正常的执行完成。另外,后置通知可以通过配置得到返回值,而最终通知无法得到。

spring切面类型

普通切面指的是未指定具体切入点的切面,那么将把目标对象中所有方法作为切入点(全部增强)

接口与实现类看上面,通知类

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class MyAdvice implements MethodInterceptor, MethodBeforeAdvice, AfterReturningAdvice{


    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("输出后置通知");
    }

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("输出前置通知");
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("环绕前");
        Object o = methodInvocation.proceed();
        System.out.println("环绕后");
        return o;
    }
}

applicationContext.xml

<!-- 目标:实现类 -->
        <bean id="userDaoImpl" class="com.lbb.dao.UserDaoImpl"/>
        <!--通知:通知类-->
        <bean id="advice" class="com.lbb.dao.MyAdvice"/>
        <!--创建代理对象-->
        <bean id="factoryUserBean" class="org.springframework.aop.framework.ProxyFactoryBean">
                <!--指定通知-->
                <property name="interceptorNames" value="advice"/>
                <!--指定目标类-->
                <property name="target" ref="userDaoImpl"/>
        </bean>

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MyTest {
    @Resource(name = "factoryUserBean")
    private UserDao userDao;

    @Test
    public void test1(){
        userDao.update();
    }
}

上面方式对目标类的方法全部增强了,但是我们有时候只想增强其中的一个或几个方法又该怎么办呢?

切入点切入面使用( PointcutAdvisor )

指定为目标对象中仅某进行增强

使用正则匹配方法的切面:

<!-- 目标:实现类 -->
        <bean id="userDaoImpl" class="com.lbb.dao.UserDaoImpl"/>
        <!--通知:通知类-->
        <bean id="advice" class="com.lbb.dao.MyAdvice"/>
        <!--组织切面信息-->
        <bean id="pointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
                <!--匹配-->
                <property name="pattern" value=".*update"/>
                <!--通知-->
                <property name="advice" ref="advice"/>
        </bean>
        <bean id="userDao" class="org.springframework.aop.framework.ProxyFactoryBean">
                <!--目标类-->
                <property name="target" ref="userDaoImpl"/>
                <!--切面-->
                <property name="interceptorNames" value="pointcutAdvisor"/>
        </bean>

使用默认的切点切面:

整体的配置与正则相同 同样是在代理Bean中指定目标,通知切点之间的关系;

只是需要增加一个表示切点的Bean

<!-- 目标:实现类 -->
    <bean id="userDaoImpl" class="com.lbb.dao.UserDaoImpl"/>
    <!--通知:通知类-->
    <bean id="advice" class="com.lbb.dao.MyAdvice"/>
    <!--切点-->
    <bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
        <!--指定要增强的方法名称 -->
        <property name="mappedNames">
            <list>
                <!--*是通配符 标识任意长度的任意内容 与RE无关 如.*表示方法名称带有点的-->
                <value>*update</value>
                <value>*save</value>
            </list>
        </property>
    </bean>
    <!--组织切面信息-->
    <bean id="pointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <!--匹配-->
        <property name="pointcut" ref="pointcutBean"/>
        <!--通知-->
        <property name="advice" ref="advice"/>
    </bean>
    <bean id="userDao" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--目标类-->
        <property name="target" ref="userDaoImpl"/>
        <!--切面-->
        <property name="interceptorNames" value="pointcutAdvisor"/>
    </bean>

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MyTest {
    @Resource(name = "userDao")
    private UserDao userDao;

    @Test
    public void test1(){
        userDao.update();
    }
}
自动生成代理

如果每个Bean都需要配置代理Bean的话,开发维护的工作量将是巨大的;

自动生成代理有三种方式

​ 1、根据BeanName来查找目标对象并且其生成代理

​ 2、根据切面信息来查找目标对象并且其生成代理

​ 3、通过AspectJ注解来指定目标对象

根据BeanName来生成代理

<!-- 目标:实现类 -->
    <bean id="userDao" class="com.lbb.dao.UserDaoImpl"/>
    <bean id="userDaoImpl2" class="com.lbb.dao.UserDaoImpl"/>
    <!--通知:通知类-->
    <bean id="advice" class="com.lbb.dao.MyAdvice"/>
    <bean id="advice2" class="com.lbb.dao.MyAdvice"/>
    <!--beanName-->
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <!-- 通知-->
        <property name="interceptorNames" value="advice,advice2"/>
        <!--切入点-->
        <property name="beanNames" value="userDao,userDaoImpl"/>
    </bean>

我们会发现上面的配置中没有与切点相关的信息,的确这种方式定义的切面是普通切面,即所有目标的所有方法都会被增强

调用时,根据BeanNames进行调用:

	@Resource(name = "userDao")
    private UserDao userDao;

根据切点信息生成代理

<!-- 目标:实现类 -->
    <bean id="userDao" class="com.lbb.dao.UserDaoImpl"/>
    <bean id="userDaoImpl2" class="com.lbb.dao.UserDaoImpl"/>
    <!--通知:通知类-->
    <bean id="advice" class="com.lbb.dao.MyAdvice"/>
    <bean id="advice2" class="com.lbb.dao.MyAdvice"/>
    <!--  切点信息  -->
    <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice" ref="advice"/>
        <property name="pattern" value=".*save"/>
    </bean>
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

DefaultAdvisorAutoProxyCreator将会在容器中查找所有Advisor,然后按照re表达式来查找目标对象和切点,最后为目标对象生成代理对象;

两种方式存在一个共同点:都会将容器中的目标对象直接替换为代理对象,这样一来,我们在使用Bean时就不用在考虑获取的是原始对象还是代理对象了,直接使用即可

关键点及其关系

  • 目标

    要被增强的Bean 没有什么特殊之处

  • 通知

    要增强的具体代码

  • 切点

    需要明确目标对象中要增强的方法是哪些,pointcut要做的事情

  • 切面

    需要明确在某个切点上应用某些通知,即advisor要做的事情

  • 代理

    需要明确目标

若是普通切面则 只需要明确,目标,和通知即可;

Spring AOP 基于AspectJ(重点)

简介

AspectJ是一个基于Java语言的AOP框架,Spring2.0以后新增了对AspectJ切点表达式支持。因为Spring1.0的时候Aspectj还未出现;

AspectJ1.5中新增了对注解的支持,允许直接在Bean类中定义切面。新版本的Spring框架建
议我们都使用AspectJ方式来开发AOP,并提供了非常灵活且强大的切点表达式 ;

当然无论使用Spring自己的AOP还是AspectJ相关的概念都是相同的;

注解配置
依赖导入
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>
通知类型

首先需要创建一个普通类作为通知类,@AspectJ用于标注其属于通知类,见下面案例:

1. @Before 前置通知 在原始方法执行前执行
2. @AfterReturning 后置通知 在原始方法执行后执行
3. @Around 环绕通知 彻底拦截原始方法的执行,执行前后都可以增加逻辑,也可以不执行原始方法
4. @AfterThrowing抛出通知,执行原始方法出现异常时执行
5. @After 最终final通知,不管是否异常,原始方法调用后都会执行
6. @DeclareParents 引介通知,相当于IntroductionInterceptor (了解即可)
定义切点

通过execution函数来定义切点

语法:execution(权限修饰符 返回类型 方法名(参数列表) 异常)

注:其中某一项不论是什么都匹配的话,可以用 * 表示 ,如果 权限修饰符 和 返回类型 都是 * 只写一个。参数列表全匹配用 … 代替。execution(* *(..)) 代表全匹配。

切点表达式示例

匹配所有类public方法:execution(public * *(..))第一个*表示返回值 …表示任意个任意类型的参数

匹配指定包下所有类所有方法: execution(* cn.xxx.dao.*.*(..)) 第一个想*表示忽略权限和返回值类型

匹配指定包下所有类所有方法:execution(* cn.xxx.dao..*(..))包含子包

匹配指定类所有方法: execution(* cn.xxx.service.UserService.*(..))

匹配实现特定接口所有类方法 : execution(* cn.xxx.dao.GenericDAO+.*(..))

匹配所有save开头的方法: execution(* save*(..))

匹配某个指定的方法: execution(* com.yh.dao.StudentService.save(..))

具体使用
准备工作

pom依赖:

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <!-- Maven会自动下载所有Spring核心容器和aop的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
    </dependencies>

接口

public interface UserDao {

    void save();

    void delete();

    void update();
}

实现类

public class UserDaoImpl implements UserDao {
  public void save(){
    System.out.println("save sql");
  }
  public void delete(){
    System.out.println("delete sql");
  }
  public void update(){
    System.out.println("update sql");
  }
}

配置applicationContext.xml

<?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
        https://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">
    <!--   要操作的类-->
    <bean class="com.lbb.dao.UserDaoImpl"/>
    <!--开启注解-->
    <aop:aspectj-autoproxy/>

</beans>

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test1 {
    @Autowired
    PersonDao personDao;

    @Test
    public void test(){
        personDao.delete();
        personDao.update();
    }
}
前置通知
@Aspect
public class MyAspect {
    //所有的通知注解都必须指定切点表达式 用于匹配方法 
    //下面是不论何种权限和返回值,以save结束的方法 任意参数列表
    @Before(value = "execution(* *save(..))")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }
}
后置通知
@Aspect
public class MyAspect {
    //所有的通知注解都必须指定切点表达式 用于匹配方法
    //下面是不论何种权限和返回值,以delete结束的方法 任意参数列表
    @AfterReturning("execution(* *delete(..))")
    public void afterRetern(){
        System.out.println("后置通知");
    }
}
环绕通知
@Aspect
public class MyAspect {
    @Around("execution(* *update(..))")
    public Object roundadvice(ProceedingJoinPoint point) {
        System.out.println("环绕前");
        Object o = null;
        try {
            o = point.proceed();
            System.out.println("环绕后");
            return o;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return o;
        }
    }
}

环绕通知与其他通知最大的区别在于环绕通知可以控制是否调用原始方法

注意:参数类型必须为ProceedingJoinPoint,否则 无法执行原始方法,

异常通知
@Aspect
public class MyAspect {
    @AfterThrowing(value = "execution(* *save(..))", throwing = "e")
    public void exceptionHandler(Exception e) {
        System.out.println("出现异常了" + e);
    }
}

把实现类的save改成下面形式,来模拟异常:

 public void save(){
    System.out.println("save sql");
    int i = 1/0;
  }

当方法中出现时才会执行该通知,若需要获取异常信息,可在注解中添加throwing指定参数名称

我们可以使用环绕+异常通知来处理数据库事务,在环绕中开启事务以及提交事务,异常通知中回滚事务,当然Spring已经对事务进行了封装不需要自己写

上面出现异常你会发现后置执行没有执行,因为后置通知是再正确返回时才会执行。

最终执行
@Aspect
public class MyAspect {
    @After("execution(* *save(..))")
    private void After(){
        System.out.println("最终通知");
    }
}

上面代码可以知道,虽然出现异常停止执行,但是最终通知依然执行了。

逻辑操运算符的表达式

在表达式中可以使用户逻辑操运算符,与&&||!

示例:

//cn.xxx.service.UserDao.insert或cn.xxx.service.UserDao.delete方法
execution(* cn.xxx.service.UserDao.insert或(..))||execution(* cn.xxx.service.UserDao.delete(..))
//cn.xxx.service.UserDao包下 以inser开头 并 以nsert结束的方法
execution(* cn.xxx.service.UserDao.*nsert(..))&&execution(* cn.xxx.service.UserDao.inser*(..))
//除了cn.xxx.service.UserDao.insert方法
!execution(* cn.xxx.service.UserDao.insert(..))
切点命名

定义命名切点

	//定义命名切点  方法名称即切点名称
    @Pointcut(value = "execution(* *save(..))")
    private void savePointcut(){}
    
    @Pointcut(value = "execution(* *delete(..))")
    private void deletePointcut(){}定义命名切点

多个通知应用到同一个切点:

@Aspect
public class MyAspect {
    //所有的通知注解都必须指定切点表达式 用于匹配方法
    @Before(value = "savePointcut()")
    public void beforeAdvice() {
        System.out.println("前置通知");
    }
    @After("savePointcut()")
    private void After(){
        System.out.println("最终通知");
    }
}

一个通知应用多个切点:

@Aspect
public class MyAspect {
    @After("savePointcut()||deletePointcut()")
    private void After() {
        System.out.println("最终通知");
    }
}

xml配置

XML配置所需的jar 以及各个对象之间的依赖关以及表达式的写法都是一样的,仅仅是换种方式来写而已;

通知类:

public class MyAspect2 {
    public void beforeAdvice() {
        System.out.println("前置通知");
    }

    public void afterRetern() {
        System.out.println("后置通知");
    }

    public Object roundadvice(ProceedingJoinPoint point) {
        System.out.println("环绕前");
        Object o = null;
        try {
            o = point.proceed();
            System.out.println("环绕后");
            return o;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return o;
        }
    }

    public void exceptionHandler(Exception e) {
        System.out.println("出现异常了" + e);
    }

    private void After() {
        System.out.println("最终通知");
    }
}

配置文件:applicationContext.xml

<?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
        https://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">
    <!--   要操作的类-->
    <bean class="com.lbb.dao.UserDaoImpl"/>
    <!--  通知类  -->
    <bean id="myAspect2" class="com.lbb.dao.MyAspect2"/>
    <!--配置AOP-->
    <aop:config>
        <!-- 切入点-->
        <aop:pointcut id="point" expression="execution(* *(..))"/>
        <!--切面定义-->
        <aop:aspect ref="myAspect2">
            <aop:before method="beforeAdvice" pointcut-ref="point"/>
            <aop:after-returning method="afterRetern" pointcut-ref="point"/>
            <aop:after method="After" pointcut-ref="point" />
            <aop:after-throwing method="exceptionHandler" pointcut-ref="point" throwing="e"/>
            <aop:around method="roundadvice" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
</beans>

无论是XML还是注解都不需要手动指定代理,以及目标对象,Aspectj会从切点中获取目标对象信息并自动创建代理。

链接Spring 事务处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值