Spring框架学习笔记 --- [在spring中初步上手实现AOP,以及对事务的初步配置使用]

Posted 小智RE0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring框架学习笔记 --- [在spring中初步上手实现AOP,以及对事务的初步配置使用]相关的知识,希望对你有一定的参考价值。


在初步了解事务之前,初步学习一下aop实现和使用

1. AOP概述(Aspect Oriented Programming)

AOP:面向切面编程
作为spring的核心思想之一;由预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术;

OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,是一种整体的设计思想;
而 AOP 则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。它是在OOP上进行延续,增加了新的功能方法;将业务代码和非业务代码进行分离.

AOP的核心原理就是使用动态代理的方式在执行方法前后或者出现异常的时候做加入相关的逻辑;可以达到不修改代码的情况下;为源代码的业务方法添加新的功能;

抽取出的公共代码的条件需要符合条件:功能与业务的逻辑没有直接的关系;

面向切面编程的好处就是: 减少重复,专注业务

这里理解切面方法的话; 可以理解为业务中的方法可以使用的公共方法;把这样的公共方法提取出来;进行使用;且提取的方法是非业务代码,


2.从案例引入;实现AOP

2.1 案例引入

还是从案例开始吧

比如我这个数据访问层UserDao里面早已实现了两个增加方法;
但是后来,考虑到业务的需要,我又想加两个日志方法(一个模拟的前置方法,一个模拟的后置方法);当然这两个方法和主要的业务关系不大,

所以,我在原有的代码中,增加方法内实现时调用了这两个新添加的日志方法;这样的话,虽然说也能实现添加的日志功能;

//标注数据访问层
@Repository
public class UserDao 
    //模拟增加方法;
    public void saveUser(User user)
        prefixLog();
        System.out.println("模拟添加方法-->有参数的");
        suffixLog();
    

    //模拟增加方法2:无参数;
    public void saveUser()
        System.out.println("模拟添加方法2-->无参数的");
    

    //前置日志功能方法;
    public void  prefixLog()
        System.out.println("前置日志方法-->");
    
    //后置日志功能方法;
    public void suffixLog()
        System.out.println("后置日志方法-->");
    

在服务层UserService这边调用数据访问层的方法;

//标记服务层
@Service
public class UserService 
    //自动装配数据服务层UserDao类;
    @Autowired
    private UserDao userDao;

    //模拟调用保存用户信息的方法;
    public void saveUser(User user)
        userDao.saveUser(user);
    

spring-useannotations.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--标记开启注解扫描-->
    <context:component-scan base-package="com.xiaozhi.spring">
    </context:component-scan>
</beans>

spring.xml中整合引入spring-useannotations.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" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--引入读取注解配置的xml-->
    <import resource="spring-useannotations.xml"/>
</beans>

测试使用

@Test
public void test01()
    //读取配置文件;
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    //创建用户服务层的对象;
    UserService userService = ac.getBean("userService",UserService.class);
    //测试调用方法;
    userService.saveUser(new User());

这样也能达到添加日志功能的效果

前置日志方法-->
模拟添加方法-->有参数的
后置日志方法-->

但是,这可是改动了业务的源代码;而且若是多个方法都需要呢;这样是不是有点代码冗余了

可能刚才那样不够直观;这样吧,看这样的一个简易设计图;后面还有很多业务方法;若每个方法中都这样去添加的话;代码的冗余可想而知了;也不够美观

按照AOP思想的话;那么先把这些方法内调用的日志方法全部都去掉;我直接让日志方法切入到业务方法的标记点; 这样的话,倒是有点像之前学过的过滤器/拦截器那个感觉了;


学习AOP之前,先看这几个专业术语

AOP 的基本概念
连接点(Joinpoint):类中可以被增强的方法,(就是需要添加功能的业务方法);
切入点(pointcut):类中有很多方法可以被增强,但是只有实际添加了额外功能的方法才能被称为切入点
通知(Advice): 通知是指一个切面在特定的连接点要做的事情(增强的功能);即需要在连接点上增强的功能; 通知分为方法执行前通知,方法执行后通知,异常通知;环绕通知(包含前置,后置,异常通知)等.
切面(Aspect) 把通知添加到切入点的过程叫切面;就是把新功能添加到切入点方法的过程;
目标(Target): 代理的目标对象(要增强的类)
代理(Proxy): 向目标对象应用通知之后创建的代理对象

2.2 Spring Aop 的实现

aop是一种思想,,很多框架都进行了实现;
在spring中,目前使用AspectJ框架进行实现;AspectJ 是一个基于 Java 语言的 AOP框架(面向切面框架),提供AOP 功能 , 且实现方式简捷,使用方便,支持注解式开发;

常用的五种通知类型是
前置通知,后置通知,环绕通知,异常通知,最终通知.

需要使用到的工具包

<!--aspectj-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

(1)用xml配置的方式实现

前置通知

这次的话,我先试试给saveUser(User user)方法和添加前置日志功能;
现在的话,把UserDao类改变一下;就留两个增加方法;一个有参方法,一个无参数方法

@Repository
public class UserDao 
    //模拟增加方法;
    public void saveUser(User user)
        System.out.println("模拟添加方法-->有参数的");
    

    //模拟增加方法2:无参数;
    public void saveUser()
        System.out.println("模拟添加方法2-->无参数的");
    

那么原先的两个模拟日志方法去哪儿呢?
创建一个名为aop的包,在其下创建AopStudy类;
就把模拟日志方法放入;

//放置新功能方法的类;
public class AopStudy 
    //前置日志功能方法;
    public void  prefixLog()
        System.out.println("前置日志方法-->");
    
    //后置日志功能方法;
    public void suffixLog()
        System.out.println("后置日志方法-->");
    

resources目录下创建spring-aop.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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://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
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--先把放置增强功能方法的类注入到spring-->
    <bean id="aopStudy" class="com.xiaozhi.spring.aop.AopStudy"/>

    <!--进行织入-->

    <aop:config >
        <!--配置切入点-->
        <!--expression表达式中,第一个 *表示返回的类型为所有类型  saveUser内的(..)表示可变长度参数的增加方法 -->
        <aop:pointcut id="saveUser" expression="execution(* com.xiaozhi.spring.dao.UserDao.saveUser(..))"/>
        
        <!--配置切面,这边ref引用到上面注入的增强功能类 ;-->
        <!--before 配置前置通知-->
        <aop:aspect ref="aopStudy">
            <aop:before method="prefixLog" pointcut-ref="saveUser"/>
        </aop:aspect>
    </aop:config>

</beans>

spring.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" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--引入读取注解配置的xml-->
    <import resource="spring-useannotations.xml"/>

    <!--引入读取aop配置的xml-->
    <import resource="spring-aop.xml"/>
</beans>

执行测试

@Test
    public void test01()
        //读取配置文件;
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
        //创建用户服务层的对象;
        UserService userService = ac.getBean("userService",UserService.class);
        //测试调用方法;
        userService.saveUser(new User());
    

执行成功

前置日志方法-->
模拟添加方法-->有参数的

看一下这个切入点的表达式

把这个配置切入点的表达式拿出来看看

<aop:pointcut id="saveUser" expression="execution(* com.xiaozhi.spring.dao.UserDao.saveUser(..))"/>

首先感受一下这个表达式中saveUser(…)的.. 的效果;

UserService下添加方法

public void saveAndSave(User user)
        //调用两个添加方法
        userDao.saveUser(user);
        userDao.saveUser();

执行测试

@Test
public void test01()
    //读取配置文件;
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    //创建用户服务层的对象;
    UserService userService = ac.getBean("userService",UserService.class);
    //测试调用方法;
    userService.saveAndSave(new User());

注意到;两个方法都被作为切入点;添加上了前置日志方法

前置日志方法-->
模拟添加方法-->有参数的
前置日志方法-->
模拟添加方法2-->无参数的

那么这次我试试不给这个表达式的saveUser(…)中加..;

执行一下;诶,好像不对,切入点不是无参数的那个吗;


Ok,在UserService之前没有调用无参数的那个添加方法;并且测试时也没调用到;
那就在UserService中添加一下;

public void saveUser()
    //调用无参数添加方法
    userDao.saveUser();

测试方法这边,我重新写一下;

@Test
 public void test()
     //读取配置文件;
     ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
     //创建用户服务层的对象;
     UserService userService = ac.getBean("userService",UserService.class);
     //测试调用方法;
     userService.saveUser();
 

OK,执行一下;这里的前置日志方法也是添加上去了;
切入点是无参数的那个添加方法

前置日志方法-->
模拟添加方法2-->无参数的

再试试吧那个切入点表达式该成类下的所有方法,(即使用*(..)通配符匹配),这里实际匹配了所有方法

UserService类中添加方法

//这里调用两个添加方法
 public void allMethods(User user)
     userDao.saveUser(user);
     userDao.saveUser();
 

重新写个执行方法

@Test
public void test03()
    //读取配置文件;
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    //创建用户服务层的对象;
    UserService userService = ac.getBean("userService",UserService.class);
    //测试调用方法;
    userService.allMethods(new User());

这里也能达到给两个添加方法同时添加前置日志方法的效果

前置日志方法-->
模拟添加方法-->有参数的
前置日志方法-->
模拟添加方法2-->无参数的

后置通知

就是改成after

我这里调用执行一下

@Test
public void test04()
    //读取配置文件;
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    //创建用户服务层的对象;
    UserService userService = ac.getBean("userService",UserService.class);
    //测试调用方法;
    userService.saveUser(new User());

它就在添加方法的后面调用执行了

模拟添加方法-->有参数的
后置日志方法-->

异常通知

异常通知的话,这里需要注意,你写增强的异常方法时,需要注意,方法参数要写成Throwable类型的;

我先在UserDao中写个有算术异常的方法

//模拟一个异常方法;
public void method1()
    System.out.println("这个方法里面有异常");
    int a = 10/0;

在写增强方法的类AopStudy中写个方法

//模拟切入异常方法;
public void exceptionDemo(Throwable throwable)
    System.out.println("这里输出异常信息->");
    throwable.getMessage();

spring-aop.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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://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
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--先把放置增强功能方法的类注入到spring-->
    <bean id="aopStudy" class="com.xiaozhi.spring.aop.AopStudy"/>

    <!--进行织入-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="method1" expression="execution(* com.xiaozhi.spring.dao.UserDao.method1())"/>
        <!--配置切面 ,注意这里要写入参数-->
        <aop:aspect ref="aopStudy">
            <aop:after-throwing method="exceptionDemo" throwing="throwable" pointcut-ref="method1"/>
        </aop:aspect>
    </aop:config>
</beans>

UserService中调用有异常的那个方法

//调用异常方法;
public void exceptionMethod()
    userDao.method1();

测试执行

@Test
public void test05()
    //读取配置文件;
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    //创建用户服务层的对象;
    UserService userService = ac.getBean("userService",UserService.class);
    //测试调用方法;
    userService.exceptionMethod();

执行,且有异常信息提示

若是在异常出现的地方直接捕获了,那么就不会被切入异常的处理方法了

环绕通知

这次还是为UserDao下的这个方法进行切入,其中有异常,这里不在方法内处理

//模拟一个异常方法;
    public void method1()
        System.out.println("这个方法里面有异常");
        int a = 10/0;
    

这里再写增强方法的时候,要为这个方法使用参数类型ProceedingJoinPoint

以上是关于Spring框架学习笔记 --- [在spring中初步上手实现AOP,以及对事务的初步配置使用]的主要内容,如果未能解决你的问题,请参考以下文章

动力节点Spring框架学习笔记-王鹤spring整合MyBatis

Spring学习笔记--Spring简介

Java学习笔记:Spring框架

动力节点Spring框架学习笔记-王鹤Spring 事务

Spring框架学习笔记 --- [Spring框架整合Mybatis框架]

Spring框架学习笔记 ---[spring框架概念 , 初步上手使用Spring , 控制反转 & 依赖注入初步理解 ]