Spring AOP及Spring整合Junit

Spring AOP.png

Spring AOP.pdf

1.回顾注解

Spring - IOC基本注解

@Component
  |-作用: 添加到某个类上,创建该类的对象,如果该注解没有指定成员属性名,则使用类名首字母小写,存入spring容器中
 
分层语义注解: @Repository @Service ,@Controller

注入数据注解:
  @Resource  资源 -JavaEE
     
  @Autowired 自动装配 Spring
        |-@Qualifier 指定注入的bean对象id名

   |- 使用这两个注解后,不需要再编写setter
     
@Value 基本数据类型值      
 

Spring新注解:

@Configuration 
  |-作用: 相当于一个XML配置文件,标注的类称为Java配置类

@Bean 注解
  |- 调用方法的返回值对象存入到spring容器中,使用方法名
 

@ComponentScan(basePackage="" 或value="")
  |-指定扫描注解的包名
   com.woniu
 
@PropertySource("classpath:db.properties")
  |- 加载外部properties属性文件


@Import 导入
  |-导入外部的配置信息,优先加载 指定外部配置类的class信息
 

2.Spring整合JUnit

问题1: 为什么要整合JUnit

每个测试方法都要创建ApplicationContext容器对象

问题2: 解决思路:

我们需要的是程序能自动帮我们创建Spring容器,并将程序包中的注解解析,创建Bean对象存入容器中

我们可以使用JUnit提供的一个注解@RunWith,用于替换掉JUnit的执行器Runner

实现步骤:

step1:添加spring整合JUnit依赖

!--spring整合Junit的依赖-->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-test</artifactId>
   <version>5.2.9.RELEASE</version>
</dependency>

step2: 更新JUnit测试类

/**
* 业务测试类,模拟表示层
*/
@RunWith(SpringJUnit4ClassRunner.class)  //使用Spring运行器替换了JUnit的运行器,会自动创建Spring IOC容器
//@ContextConfiguration(locations = {"classpath:applicationContext.xml"}) //根据 xml配置文件创建容器
@ContextConfiguration(classes={SpringConfiguration.class})  //根据注解配置类来创建容器
public class UserServiceTest {
   @Autowired
   private UserService userService;

   @Test
   public void testGetUserList(){
       List<User> userList = userService.getUserList();
       for (User user : userList) {
           System.out.println(user);
       }
       System.out.println("-----------------------------------------------");
       User user = userService.getUserById(1);
       System.out.println(user);
   }
}

3.转帐案例

step1: 添加依赖

  <!--spring-Context依赖-->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>5.2.9.RELEASE</version>
</dependency>

<!--spring整合Junit的依赖-->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-test</artifactId>
   <version>5.2.9.RELEASE</version>
</dependency>

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-jdbc</artifactId>
   <version>5.2.9.RELEASE</version>
</dependency>

<!--Mybatis-->
<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>3.5.6</version>
</dependency>

<!--mysql驱动-->
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>8.0.23</version>
</dependency>

<!--mybatis与spring的整合包-->
<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis-spring</artifactId>
   <version>2.0.6</version>
</dependency>

<!--数据源jar-->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid</artifactId>
   <version>1.2.5</version>
</dependency>

<!--logback-->
<dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-classic</artifactId>
   <version>1.2.3</version>
</dependency>

<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <version>1.18.22</version>
</dependency>

<dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.13</version>
   <scope>test</scope>
</dependency>

step2:spring整合mybatis 相关配置

applicationContext.xml 和  SpringConfiguration配置类

package com.woniu.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;
import java.io.IOException;

//相当于一个spring xml配置文件
@Configuration
@ComponentScan("com.woniu")
@Import(JdbcConfig.class)  //引入外部的配置类文件,可以在MapperScannerConfigurer之前加载
public class SpringConfiguration {

   @Bean("sqlSessionFactoryBean")
   public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource dataSource) throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        //设置dataSource
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage("com.woniu.entity");

        //设置sqlMapper XML映射文件的路径
       ResourcePatternResolver loader=new PathMatchingResourcePatternResolver();
       sqlSessionFactoryBean.setMapperLocations(
               loader.getResources("classpath:com/woniu/dao/*.xml"));

       //指定mybatis的核心配置文件
       // sqlSessionFactoryBean.setConfigLocation(
       //         loader.getResource("classpath:mybatis-config.xml"));

        return sqlSessionFactoryBean;
   }

   //用于生成Mapper接口代理子类对象,并存入spring容器中
   @Bean
   public MapperScannerConfigurer getMapperScannerConfigurer(){
       MapperScannerConfigurer configurer = new MapperScannerConfigurer();
       configurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean");
       configurer.setBasePackage("com.woniu.dao");
       return configurer;
   }
}

@PropertySource("classpath:db.properties") //读取外部的properties文件信息
public class JdbcConfig {
   @Value("${jdbc.url}")
   private String url;

   @Value("${jdbc.driver}")
   private String driverClassName;

   @Value("${jdbc.user}")
   private String username;

   @Value("${jdbc.pass}")
   private String password;


   //@Bean注解 调用此方法,将返回值对象以方法名为id存入spring容器中
   @Bean("dataSource")
   public DataSource createDataSource(){
       DruidDataSource dataSource = new DruidDataSource();
       dataSource.setUrl(url);
       dataSource.setDriverClassName(driverClassName);
       dataSource.setUsername(username);
       dataSource.setPassword(password);
       dataSource.setInitialSize(4);
       dataSource.setMaxActive(100);
       dataSource.setMinIdle(2);
       return dataSource;
   }
}

step3: 创建实体类Account

@Data
public class Account {
   private Integer id;
   private String userName;
   private Integer money;
}

step4: 编写测试(模拟表示层),业务层,以及Dao层代码

public interface AccountDao {
   @Select("select * from account where userName=#{userName}")
   Account getAccountByName(String userName);


   @Update("update account set money=#{money} where id=#{id}")
   void updateAccount(Account account);
}

业务实现类:


@Service("accountService")
public class AccountServiceImpl implements AccountService {
   @Autowired
   private AccountDao accountDao;

   /**
    * 实现转找业务方法
    * @param sourceName  源账户名
    * @param targetName 目标帐户名
    * @param money 转账金额
    * @return
    */
   public boolean transfer(String sourceName, String targetName, Integer money) {
       try {
           //通过帐户名查询 Account对象
           Account sourceAccount = accountDao.getAccountByName(sourceName);
           Account targetAccount = accountDao.getAccountByName(targetName);

           //设置转账金额
           sourceAccount.setMoney(sourceAccount.getMoney()-money);
           targetAccount.setMoney(targetAccount.getMoney()+money);

           //保存到数据库
           accountDao.updateAccount(sourceAccount);
           int i=3/0;
           accountDao.updateAccount(targetAccount);

           return true;
       } catch (Exception e) {
           e.printStackTrace();
           return false;
       }
   }
}

业务测试类 -- 整合JUnit

/**
* 账户业务测试类 -- 模拟表示层
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= SpringConfiguration.class) //指定注解配置类,创建Spring工厂容器
public class AccountServiceTest {

   @Autowired
   private AccountService accountService;

   @Test
   public void testTransfer(){
       boolean flag = accountService.transfer("jack","tom",100);
       System.out.println(flag ? "转账成功" : "转账失败");
   }
}

step5: 运行测试类,测试转帐业务

11:27:26.436 [main] DEBUG c.woniu.dao.AccountDao.updateAccount - ==>  Preparing: update account set money=? where id=?
11:27:26.436 [main] DEBUG c.woniu.dao.AccountDao.updateAccount - ==> Parameters: 800(Integer), 1(Integer)
11:27:26.448 [main] DEBUG c.woniu.dao.AccountDao.updateAccount - <==    Updates: 1
11:27:26.448 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4ba534b0]
java.lang.ArithmeticException: / by zero
at com.woniu.service.impl.AccountServiceImpl.transfer(AccountServiceImpl.java:33)
at com.woniu.test.service.AccountServiceTest.testTransfer(AccountServiceTest.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
转账失败

结论:通过上述案例,我们通过日志发现,Spring整合mybatis后,并没有添加事务管理,此时,执行每个 Dao操作都会自动提交 。

4.Spring AOP

4-1 AOP的概述

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术

AOP 是一种编程思想,使用代理模式技术手段实现AOP

代理模式:
  |-静态代理
      |-代理类与目标类共同实现抽象接口 -代理关系是在编译期确定
 
  |-动态代理
       |-JDK - 代理类与目标类共同实现抽象接口 -- 代理关系在运行时确定
                   |-针对被代理的目标类必须要实现接口情形
                   
       |-CGLIB - 代理类继承被代理的目标类   -- 代理关系在运行时确定
                   Enhancer enhancer = new Enhancer();
                   enhancer.setSuperClass(目标类.class)
                   enhancer.setCallBack()
       
AOP编程的目标:将业务层中系统性相关公共代码,提取出来,单独定义,降低程序的耦合,在需要的时间进行切入

4-2 解决转账案例事务处理问题

  • 使用手动事务处理

step1:注掉SpringConfiguration配置类中的@Bean注解方法,只保留一个DataSource

//相当于一个spring xml配置文件
@Configuration
@ComponentScan("com.woniu")
@Import(JdbcConfig.class)  //引入外部的配置类文件,可以在MapperScannerConfigurer之前加载
public class SpringConfiguration {

  // @Bean("sqlSessionFactoryBean")
   public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource dataSource) throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        //设置dataSource
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage("com.woniu.entity");

        //设置sqlMapper XML映射文件的路径
       ResourcePatternResolver loader=new PathMatchingResourcePatternResolver();
       sqlSessionFactoryBean.setMapperLocations(
               loader.getResources("classpath:com/woniu/dao/*.xml"));

       //指定mybatis的核心配置文件
       // sqlSessionFactoryBean.setConfigLocation(
       //         loader.getResource("classpath:mybatis-config.xml"));

        return sqlSessionFactoryBean;
   }

   //用于生成Mapper接口代理子类对象,并存入spring容器中
  // @Bean
   public MapperScannerConfigurer getMapperScannerConfigurer(){
       MapperScannerConfigurer configurer = new MapperScannerConfigurer();
       configurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean");
       configurer.setBasePackage("com.woniu.dao");
       return configurer;
   }
}

step2: 创建ConnectionUtil连接工具类,依赖注入DataSource属性


@Component
public class ConnectionUtil {

   @Autowired
   private DataSource dataSource;
   private ThreadLocal<Connection> conns = new ThreadLocal<>(); //用于保证一次操作多获获取同一个Connection

   /**
    * 获取连接对象
    *
    * @return
    */
   public Connection getConnection() {
       Connection conn = conns.get();
       try {
           if (conn == null) {
               conn = dataSource.getConnection();
               conns.set(conn);
           }
       } catch (SQLException throwables) {
           throwables.printStackTrace();
       }
       return conn;
   }

   /**
    * 关闭连接
    * @param conn
    */
   public void closeConnection(Connection conn){
       try {
           if(conn!=null){
               conn.close();
               conns.remove();
           }
       } catch (SQLException throwables) {
           throwables.printStackTrace();
       }
   }
}

step3: 手动实现AccountDao接口AccountDaoImpl实现类

@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
   @Autowired
   private ConnectionUtil connectionUtil;


   /**
    * 根据用户名查询Account
    * @param userName
    * @return
    */
   public Account getAccountByName(String userName) {
       Connection conn = connectionUtil.getConnection();
       Account account = null;
       try {
           PreparedStatement pstmt = conn.prepareStatement("select * from account where userName=?");
           pstmt.setString(1,userName);
           ResultSet rs = pstmt.executeQuery();
           if(rs.next()){
               account = new Account();
               account.setId(rs.getInt(1));
               account.setUserName(rs.getString(2));
               account.setMoney(rs.getInt(3));
           }
           return account;
       } catch (SQLException throwables) {
           throwables.printStackTrace();
       }
       return null;
   }

   public int updateAccount(Account account) {
       Connection conn = connectionUtil.getConnection();

       try {
           PreparedStatement pstmt = conn.prepareStatement("update account set money=? where id=?");
           pstmt.setInt(1,account.getMoney());
           pstmt.setInt(2,account.getId());

          return  pstmt.executeUpdate();
       } catch (SQLException throwables) {
           throwables.printStackTrace();
       }
       return 0;
    }
}

step4: 编写TransactionManager事务管理器类

/**
* 自定义事务管理器类
*/
@Component
public class TransactionManager {
   @Autowired
   private ConnectionUtil connectionUtil;

   public void beginTransaction() {
       try {
           System.out.println("开启事务...");
           connectionUtil.getConnection().setAutoCommit(false);
       } catch (SQLException throwables) {
           throwables.printStackTrace();
       }
   }

   public void commitTransaction() {
       try {
           System.out.println("提交事务...");
           connectionUtil.getConnection().commit();
       } catch (SQLException throwables) {
           throwables.printStackTrace();
       }
   }

   public void rollbackTransaction() {
       try {
           System.out.println("回滚事务...");
           connectionUtil.getConnection().rollback();
       } catch (SQLException throwables) {
           throwables.printStackTrace();
       }
   }

   /**
    * 释放资源
    */
   public void release() {
       if (connectionUtil.getConnection() != null) {
           connectionUtil.closeConnection(connectionUtil.getConnection());
       }
   }
}

step5:更新AccountServiceImpl业务方法,添加事务管理代码:

@Service("accountService")
public class AccountServiceImpl implements AccountService {
   @Autowired
   private AccountDao accountDao;

   @Autowired
   private TransactionManager transactionManager;

   /**
    * 实现转找业务方法
    * @param sourceName  源账户名
    * @param targetName 目标帐户名
    * @param money 转账金额
    * @return
    */
   public boolean transfer(String sourceName, String targetName, Integer money) {
       //开启事务
       transactionManager.beginTransaction();
       try {
           //通过帐户名查询 Account对象
           Account sourceAccount = accountDao.getAccountByName(sourceName);
           Account targetAccount = accountDao.getAccountByName(targetName);

           //设置转账金额
           sourceAccount.setMoney(sourceAccount.getMoney()-money);
           targetAccount.setMoney(targetAccount.getMoney()+money);

           //保存到数据库
           accountDao.updateAccount(sourceAccount);
           int i=3/0;
           accountDao.updateAccount(targetAccount);
           transactionManager.commitTransaction(); //提交
           return true;
       } catch (Exception e) {
           e.printStackTrace();
           transactionManager.rollbackTransaction(); //回滚
           return false;
       } finally {
           transactionManager.release(); //释放资源
       }
   }
}

step6:测试运行AccountServiceTest测试转账业务方法,发现如果转账异常,则金额都不会提交。

  • 使用手动处理事务,遇到新的问题 :

业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。

可以使用二阶段讲过的ServiceProxyFactory动态代理来解决.

4-3 AOP的核心概念 - [重点]

  • Spring中的AOP,就是要通过配置的方式,来实现上一节的案例功能

  • AOP相关术语

    所谓连接点是指那些被拦截到的点
    在 Spring应用中连接点都表示被代理的方法
    spring 只支持方法类型的连接点
    所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义

    切入点属于连接点的范围
    所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知  - TransactionManager通知类

    通知的类型:
        前置通知 beforeAdvice -- 开启事务
        后置通知 afterReturnAdvice [执行正常返回]  --提交事务
        异常通知 throwAdvice [执行出现错误]  --回滚
        最终通知 afterAdvice  -- finally{}释放资源release()
       
        (环绕通知)
    代理的目标对象
    指把增强应用到目标对象来创建新的代理对象的过程
    一个类被 AOP 织入增强后,就产生一个结果代理类
    Pointcut切入点 + Advice通知 = Aspect 切面

    image-20220618153519189

    • Aspect(切面)

    • Proxy(代理对象)

    • Weaving(织入)

    • Target(目标对象):

    • Advice(通知/增强):

    • Pointcut切入点

    • Joinpoint(连接点)

  • Spring中AOP要明确的事

a、开发阶段(我们做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。
b、运行阶段(Spring 框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

4-4 Spring AOP-XML配置实现

step1: 将业务层业务方法更新-不做事务处理

@Service("accountService")
public class AccountServiceImpl implements AccountService {
   @Autowired
   private AccountDao accountDao;

   /**
    * 实现转找业务方法
    *
    * @param sourceName 源账户名
    * @param targetName 目标帐户名
    * @param money      转账金额
    * @return
    */
   public boolean transfer(String sourceName, String targetName, Integer money) {
       //通过帐户名查询 Account对象
       Account sourceAccount = accountDao.getAccountByName(sourceName);
       Account targetAccount = accountDao.getAccountByName(targetName);

       //设置转账金额
       sourceAccount.setMoney(sourceAccount.getMoney() - money);
       targetAccount.setMoney(targetAccount.getMoney() + money);

       //保存到数据库
       accountDao.updateAccount(sourceAccount);
       int i = 3 / 0;
       accountDao.updateAccount(targetAccount);
       return true;
   }
}

step2: pom.xml添加Spring切面的依赖包 spring-aspects

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${spring.version}</version>
</dependency>

step3:创建spring的配置文件并导入约束 【此处要导入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:context="http://www.springframework.org/schema/context"
       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/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">


    <!--开启注解的包扫描-->
    <context:component-scan base-package="com.woniu" />

    <!--在当前xml文件中加载外部properties配置文件-->
    <context:property-placeholder location="classpath:db.properties" />

    <!--数据源配置-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.user}" />
        <property name="password" value="${jdbc.pass}" />
        <property name="initialSize" value="${druid.initialSize}" />
        <property name="maxActive" value="${druid.maxActive}" />
        <property name="minIdle" value="${druid.minIdle}" />
    </bean>
 
   ...
 </beans>

step4:抽取公共代码制作成通知advice

package com.woniu.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.SQLException;

/**
 * 自定义事务管理器类 --通知类Advice
 */
@Component
public class TransactionManager {
    @Autowired
    private ConnectionUtil connectionUtil;

    public void beginTransaction() {
        try {
            System.out.println("开启事务...");
            connectionUtil.getConnection().setAutoCommit(false);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    public void commitTransaction() {
        try {
            System.out.println("提交事务...");
            connectionUtil.getConnection().commit();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    public void rollbackTransaction() {
        try {
            System.out.println("回滚事务...");
            connectionUtil.getConnection().rollback();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    /**
     * 释放资源
     */
    public void release() {
        if (connectionUtil.getConnection() != null) {
            connectionUtil.closeConnection(connectionUtil.getConnection());
        }
    }
}

step5: 在applicatonContext.xml中配置spring Aop

a.添加TransactionManager通知类上的@Component注解

b.配置aop

<!--Spring AOP配置-->
<aop:config>
    <!--配置切入点 id为切点名,expression用于指定切点表达式
           全匹配:public boolean com.woniu.service.impl.AccountServiceImpl.trasfer(String,String,Integer)
           情形1: 访问修饰符可以省略
                boolean com.woniu.service.impl.AccountServiceImpl.trasfer(String,String,Integer)
           情形2:  切入点方法返回类型可有可无
              *  com.woniu.service.impl.AccountServiceImpl.trasfer(String,String,Integer)
           情形3:    包名使用* 表示可以是当前项目中的任意包,但是有几级包,需要写几个星
              * *.*.*.*.AccountServiceImpl.transfer(String,String,Integer)
           情形4:   类名使用* 表示指定包中的任意类
             * *.*.*.*.*.transfer(String,String,Integer)
           情形5: 方法名使用* 表示任意类中的任意方法
                * *.*.*.*.*.*(String,String,Integer)
           情形6: 参数可以使用* 表示任意数量的任类型,但是必须有参数
                * *.*.*.*.*.*(*)
            情形7: 参数可以使用.. 表示参数可有可无
                * *.*.*.*.*.*(..)
            情形8:包中中可以使用.. 表示任意级数包
                * *..*.*(..)   全通配

            推荐项目中使用: * com.woniu.service.impl.*.*(..)  作为切点表达式
        -->
    <aop:pointcut id="pt"
                  expression="execution(* com.woniu.service.impl.*.*(..))"/>
    <!--配置切面-->
    <aop:aspect id="txAdvice" ref="transactionManager">
        <!--前置通知-->
        <aop:before method="beginTransaction" pointcut-ref="pt" />
        <!--后置通知-->
        <aop:after-returning method="commitTransaction" pointcut-ref="pt"/>
        <!--异常通知-->
        <aop:after-throwing method="rollbackTransaction" pointcut-ref="pt" />
        <!--最终通知-->
        <aop:after method="release" pointcut-ref="pt" />
    </aop:aspect>
</aop:config>

Spring AOP配置的一点细节:

(1) 通知类中增强方法的参数

 在通知方法中可以声明一个JoinPoint类型的参数,代表当前通知增强代码中所拦截到的点

通过JoinPoint可以访问连接点的细节:

1.java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; 
2.Signature getSignature() :获取连接点的方法签名对象; 
3.java.lang.Object getTarget() :获取连接点所在的目标对象; 
4.java.lang.Object getThis() :获取代理对象本身

通知类示例:

@Component
public class TransactionManager {
    @Autowired
    private ConnectionUtil connectionUtil;

    public void beginTransaction(JoinPoint joinPoint) {
        try {
            Object[] args = joinPoint.getArgs();
            System.out.println(args[0]+","+args[1]+","+args[2]);
            System.out.println("连接点方法的签名: "+joinPoint.getSignature());

            System.out.println("开启事务...");
            connectionUtil.getConnection().setAutoCommit(false);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    public void commitTransaction(JoinPoint joinPoint,Object returnVal) {
        try {
            System.out.println("提交事务...");
            System.out.println("获取业务方法执行完成后的返回值: "+returnVal);
            connectionUtil.getConnection().commit();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    public void rollbackTransaction(JoinPoint joinPoint,Throwable ex) {
        try {
            System.out.println("发生了异常:"+ex.getMessage());
            System.out.println("回滚事务...");
            connectionUtil.getConnection().rollback();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    /**
     * 释放资源
     */
    public void release(JoinPoint joinPoint) {
        if (connectionUtil.getConnection() != null) {
            connectionUtil.closeConnection(connectionUtil.getConnection());
        }
    }

}

添加returning与throwing属性的配置

<aop:config>
    <aop:pointcut id="pt"
                   expression="execution(* com.woniu.service.impl.*.*(..))"/>
    <!--配置切面-->
    <aop:aspect id="txAdvice" ref="transactionManager">
        <!--前置通知-->
        <aop:before method="beginTransaction" pointcut-ref="pt"/>
        <!--后置通知-->
        <aop:after-returning method="commitTransaction" returning="returnVal" pointcut-ref="pt"/>
        <!--异常通知-->
        <aop:after-throwing method="rollbackTransaction" throwing="ex" pointcut-ref="pt" />
        <!--最终通知-->
        <aop:after method="release" pointcut-ref="pt" />
        <!--环绕通知-->
        <aop:around method="round" pointcut-ref="pt" />
    </aop:aspect>
</aop:config>

(2) 环绕通知配置

通知类添加环绕通知方法 :

/**
 * 自定义事务管理器类 --通知类Advice
 */
@Component
public class TransactionManager {
    @Autowired
    private ConnectionUtil connectionUtil;

    public void beginTransaction(JoinPoint joinPoint) {
        try {
            Object[] args = joinPoint.getArgs();
            System.out.println(args[0]+","+args[1]+","+args[2]);
            System.out.println("连接点方法的签名: "+joinPoint.getSignature());

            System.out.println("开启事务...");
            connectionUtil.getConnection().setAutoCommit(false);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    public void commitTransaction(JoinPoint joinPoint,Object returnVal) {
        try {
            System.out.println("提交事务...");
            System.out.println("获取业务方法执行完成后的返回值: "+returnVal);
            connectionUtil.getConnection().commit();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    public void rollbackTransaction(JoinPoint joinPoint,Throwable ex) {
        try {
            System.out.println("发生了异常:"+ex.getMessage());
            System.out.println("回滚事务...");
            connectionUtil.getConnection().rollback();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    /**
     * 释放资源
     */
    public void release(JoinPoint joinPoint) {
        if (connectionUtil.getConnection() != null) {
            connectionUtil.closeConnection(connectionUtil.getConnection());
        }
    }

    /**
     * 环绕通知处理方法
     * @param joinPoint
     */
    public Object  round(ProceedingJoinPoint joinPoint){
         Object returnVal=null;
        try{
            //开启事务
            beginTransaction(joinPoint); 
            //调用被 代理目标对象业务方法  //method.invoke()
             returnVal  = joinPoint.proceed(joinPoint.getArgs()); 
            //提交
            commitTransaction(joinPoint,returnVal);
        }catch(Throwable e){
            e.printStackTrace();
            rollbackTransaction(joinPoint,e);
        }finally {
            release(joinPoint);
        }
        return returnVal;
    }
}

环绕通知配置:

<aop:config>
    <aop:pointcut id="pt"
                  expression="execution(* com.woniu.service.impl.*.*(..))"/>
    <!--配置切面-->
    <aop:aspect id="txAdvice" ref="transactionManager">
        <!--环绕通知-->
        <aop:around method="round" pointcut-ref="pt" />
    </aop:aspect>
</aop:config>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值