Spring学习笔记(二)——AOP及Spring事务学习

这周末来学习一下Spring的另一个重点,AOP及Spring中的事务学习,我之前写过关于事务的文章,都是说在工作中应该如何运用,今天补充一下基础内容,尽量用一些直白的话来说明作用,然后整理一下前段时间偶然听到的面试问题,好了,我们现在开始。

AOP

AOP(Aspect Oriented Programming)面向切面编程,学习Java的小伙伴肯定都知道Java是OOP(Object Oriented Programming)面向对象的编程语言。

我简单的说一下:我认为这个面向XX指的就是解决问题的方式,Java的面向对象就是通过对象来调用方法,相对于面向过程的C等编程语言来说提高了代码的复用性降低了代码的耦合性。

那这个面向切面我们应该如何理解呢?他是不是利用切面来解决了一些问题呢?

面向切面编程实际就是利用动态代理模式,在方法执行前,执行后,抛出异常等指定情况下执行指定方法。

下面这张图希望可以帮助您了解什么是面向切面编程:

那么AOP是如何实现面向切面编程的呢?

AOP使用了动态代理模式来进行横向方法的添加,如果有小伙伴不是很了解代理模式,可以看一下我的这篇文章,应该会有所收获。

设计模式——代理模式(Proxy Pattern)

如果有了解代理模式及JDK动态代理与CGlib动态代理的小伙伴,我们就继续啦。

根据上面的流程图不难发现,AOP让我们在不需要改动原代码的基础上增加新的逻辑和功能,降低了代码的耦合性,对原代码进行增强

使用AOP

方式一:

1.我们需要导入AOP织入包的依赖(不导入无法使用)

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>

2.我们需要创建测试用的业务类

2-1.展示一下我测试用的项目目录

 2-2.创建User接口

package aopTest;

/**
 * @author Me
 * @date 2022/2/26 12:30
 */
public interface User {
    /**
     * 插入
     */
    void insert();
    /**
     * 更新
     */
    void update();
    /**
     * 删除
     */
    void delete();
    /**
     * 查询
     */
    void select();
}

2-3.创建UserImpl实现类

package aopTest;

/**
 * @author ME
 * @date 2022/2/26 12:30
 */
public class UserImpl implements User{
    @Override
    public void insert() {
        System.out.println("插入一个对象");
    }

    @Override
    public void update() {
        System.out.println("更新一个对象");
    }

    @Override
    public void delete() {
        System.out.println("删除一个对象");
    }

    @Override
    public void select() {
        System.out.println("查询一个对象");
    }
}

3.创建切面方法

package aopTest;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

/**
 * @author ME
 * @date 2022/2/26 12:33
 */
// 前置切面需要实现MethodBeforeAdvice接口,重写before方法
public class BeforeLog implements MethodBeforeAdvice {
    /**
     * @param method 要执行的目标对象的方法
     * @param objects 目标对象方法的参数
     * @param o 目标对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("前置切面执行了" + o.getClass().getName() + "类中的" + method.getName() + "方法");
    }
}
package aopTest;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

/**
 * @author ME
 * @date 2022/2/26 12:40

 * AfterAdvice 为方法结束之后,返回之前执行,拿不到返回值
 * AfterReturningAdvice 为方法返回之后执行,可以拿到返回值
 */
public class AfterLog implements AfterReturningAdvice {
    /**
     * @param returnValue 返回值内容
     * @param method 要执行的目标对象的方法
     * @param args 目标对象方法的参数
     * @param target 目标对象
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("后置切面执行了" + target.getClass().getName() + "类中的" + method.getName() + "方法");
    }
}

4.创建applicationContext.xml配置文件

4-1.展示一下配置文件所在的目录

4-2.创建配置文件(使用aop的情况下要添加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: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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

<!--    注册bean,必须注册Class,不可注册接口-->
    <bean id="user" class="aopTest.UserImpl"/>
    <bean id="beforeLog" class="aopTest.BeforeLog"/>
    <bean id="afterLog" class="aopTest.AfterLog"/>

<!--    配置AOP,需要导入aop的约束才可配置-->
<!--    Spring原生API方式创建-->
    <aop:config>
<!--        配置切入点,id为切点标识,expression为生效的范围,区分大小写-->
<!--        表达式可以上网搜,我用的这个表达式为src.main.java路径下所有带i开头的方法(区分大小写,I开头的不生效)-->
<!--        <aop:pointcut id="pointcut" expression="execution(* i*(..))"/>-->
<!--        这个表达式为src.main.java.aopTest路径下的UserImpl类中的所有方法-->
        <aop:pointcut id="pointcut" expression="execution(* aopTest.UserImpl.*(..))"/>

<!--        标识在expression配置的类模式下,我们要配置几个切面,切面执行的逻辑依据的beanId与切点的配置id-->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>

</beans>

5.创建启动类文件,进行方法调用

import aopTest.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author ME
 * @date 2022/2/19 22:39
 */
public class StartController {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 由于Spring原生AOP是基于JDK动态代理的,所以需要类型转换为接口类型
        // 我们获取了子类对象的话使用AOP会报错,不使用则不会报错
        User user = (User) application.getBean("user");
        user.insert();
    }
}

查看执行结果,我们发现不是单纯的只执行了一句话,而是如下所示:

我们发现切面编程已经生效。

方式二:

除了上面这种方式外,我们还可以自定义指定类,接下来介绍一下这种AOP的实现方式,我们还是用User类作为测试对象。

1.创建切面方法

package aopTest;

/**
 * @author ME
 * @date 2022/2/26 14:24
 */
public class AopPoint {
    /**
     * 前置方法的名字自定义
     */
    public void myBefore() {
        System.out.println("方法执行前的前置通知");
    }

    /**
     * 后置方法的名字自定义
     */
    public void myAfter() {
        System.out.println("方法执行后的后置通知");
    }
}

2.修改配置文件

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

<!--    注册bean,必须注册Class,不可注册接口-->
    <bean id="user" class="aopTest.UserImpl"/>
    <bean id="aopPoint" class="aopTest.AopPoint"/>

<!--    配置AOP,需要导入aop的约束才可配置-->
<!--    自定义切面类创建-->
    <aop:config>
<!--        引入配置的切面类的Bean对象-->
        <aop:aspect ref="aopPoint">
<!--            配置切点,execution表达式表示src.java.aopTest.UserImpl类的所有方法都会添加切面-->
            <aop:pointcut id="point" expression="execution(* aopTest.UserImpl.*(..))"/>
<!--            表示在配置的切点上,执行切点内的方法时前置执行Bean容器内ID为aopPoint类中的myBefore方法-->
            <aop:before method="myBefore" pointcut-ref="point"/>
<!--            表示在配置的切点上,执行切点内的方法时后置执行Bean容器内ID为aopPoint类中的myAfter方法-->
            <aop:after method="myAfter" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>

</beans>

3.执行启动类

import aopTest.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author ME
 * @date 2022/2/19 22:39
 */
public class StartController {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 由于Spring原生AOP是基于JDK动态代理的,所以需要类型转换为接口类型
        // 我们获取了子类对象的话使用AOP会报错,不使用则不会报错
        User user = (User) application.getBean("user");
        user.insert();
    }
}

执行后控制台输出如下:

方式三:

第三种方式是使用注解完成AOP,还是使用同一个例子

1.创建切面类

package aopTest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * @author ME
 * @date 2022/2/26 20:31
 */
// Aspect注解标注此类为AOP切面类
@Aspect
public class AnnoPoint {

    // 注解表示为方法前置通知,参数为execution表达式
    // 我用的表达式为src.main.java.aopTest包下的UserImpl类中的所有方法
    @Before("execution(* aopTest.UserImpl.*(..))")
    public void before() {
        System.out.println("方法执行前的前置通知");
    }

    // 注解表示为方法后置通知
    @After("execution(* aopTest.UserImpl.*(..))")
    public void after() {
        System.out.println("方法执行后的后置通知");
    }

    // 注解表示为方法环绕通知,也就是可以在此方法执行前置和后置通知
    // 在环绕增强中我们可以传入一个参数,代表我们要切入的方法对象
    @Around("execution(* aopTest.UserImpl.*(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知之前置通知");
        // 获取方法的签名
        System.out.println("切入方法的方法签名为:" + joinPoint.getSignature());
        // 调用此方法表示执行切入的方法,返回值为切入方法的返回值,void关键字会返回null
        Object proceed = joinPoint.proceed();
        System.out.println("环绕通知之后置通知");
    }
}

2.创建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: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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

<!--    注册bean,必须注册Class,不可注册接口-->
    <bean id="user" class="aopTest.UserImpl"/>
    <bean id="annoPoint" class="aopTest.AnnoPoint"/>

<!--    开启AOP注解支持(同样需要导入上方beans标签内的aop约束)-->
<!--    proxy-target-class可以指定使用(false)JDK动态代理或(true)CGlib动态代理,默认为(false)JDK动态代理-->
<!--    两者的区别在于JDK动态代理的AOP只能作用于接口对象,CGlib动态代理的AOP可用于接口对象与类对象-->
<!--    两种类型的动态代理具体区别请看文章上面的代理模式的链接-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

</beans>

3.执行启动类

import aopTest.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author ME
 * @date 2022/2/19 22:39
 */
public class StartController {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 由于Spring原生AOP是基于JDK动态代理的,所以需要类型转换为接口类型
        // 通过设置XML文件中的aop属性开启CGlib动态代理后,可以使用类对象作为切点进行通知
        // 在JDK动态代理情况下我们获取了子类对象的话使用AOP会报错
        User user = (User) application.getBean("user");
        user.insert();
    }
}

执行结果如下:

我们可以发现三种方式的区别:

第一种方式可以获取到执行的切点方法的内容,例如参数,返回值,类信息等内容

第二种方式获取不到切点方法的信息,只能通过其他方式获取数据再执行自身的逻辑

第三种方式配置最为简易,缺点在于每个通知方法都需要写表达式,容易出现低级失误

我之前写过一篇使用自定义注解实现AOP的文章,链接拿过来啦,如果有小伙伴在工作中需要添加日志功能,可以使用这种方式,配置后需要进行AOP增强的方法只需要在方法上添加一个注解即可,十分好用!

SpringBoot自定义AOP注解

Spring事务

事务这部分内容我以前也有整理过一篇文章,由于Spring现在用的比较少,测试Spring事务又比较麻烦,还要先继承Mybatis,连接数据库等等。。而且事务这部分到了SpringBoot中就是一个注解的事情,所以多的就不再重复说了,有需要的可以看一下我的这篇文章进行了解。

Spring事务及工作中的使用

如果小伙伴们很想了解Spring中的事务可以去看看视频,比较推荐狂神,因为我就是看他的视频学习的。接下来我下面再补充介绍一些概念性内容。

Spring中的事务分为声明式事务与编程式事务。

声明式事务:利用AOP,在不影响原逻辑的情况下进行增强

编程式事务:需要在代码中进行事务的管理

1.在声明式事务中,我们需要了解事务的一个参数-propagation,这个参数用来表示如何对方法使用事务,使用还是不使用。

名称含义
REQUIRED(默认)支持当前事务,如果当前没有事务,就新建一个事务
SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行
MANDATORY支持当前事务,如果当前没有事务,就抛出异常
REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起
NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,则把当前事务挂起
NEVER以非事务方式执行,如果当前存在事务,则抛出异常
NESTED支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务

2.read-only属性,此事务中只支持select操作。

到这里Spring基本上也肝完了,现在的工作要求使用Spring的公司越来越少了,但是Spring还是面试问题中高频的存在,IOC与AOP还是要重点理解的内容,所以希望小伙伴们还是好好学习一下基础吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值