Spring aop

4 篇文章 0 订阅

手写转账代理

Service类

public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }
    
    @Override
    public void transfer(String sourceName, String targetName, float money) {
        //1.根据名称查询转出行户
        Account sourceAccount = accountDao.findAccountByName(sourceName);

        //2.根据名称查询转入账户
        Account targetAccount = accountDao.findAccountByName(targetName);

        //3.转出账户减钱
        sourceAccount.setMoney(sourceAccount.getMoney() - money);

        //4.转入账户加钱
        targetAccount.setMoney(targetAccount.getMoney() + money);

        //5.更新转出账户
        accountDao.updateAccount(sourceAccount);

        int i = 1 / 0;

        //6.更新转入账户
        accountDao.updateAccount(targetAccount);

    }
}

以上为需要实现事务控制的事务层,再附上事务控制的代码

获取连接的工具类

/**
 * 连接的工具类,用于从数据源获取一个连接,并且实现和线程的绑定
 */
public class ConnectionUtils {

    private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Autowired
    private DataSource dataSource;

    public Connection getThreadConnection(){
        //1.先从ThreadLocal上获取
        Connection connection = threadLocal.get();

        //2.判断当前线程上是否有连接
        if(connection == null){
            //3.从数据源中获取一个连接并绑定线程
            try {
                connection = dataSource.getConnection();
                threadLocal.set(connection);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

        return connection;
    }

    /**
     * 把连接和线程解绑
     */
    public void removeConnection(){
        threadLocal.remove();
    }
}

事务管理类

/**
 * 和事务管理相关的工具类
 *  开启事务
 *  提交事务
 *  回滚事务
 *  释放连接
 */
public class TransactionManager {

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    private ConnectionUtils connectionUtils;

    /**
     * 提交事务
     */
    public void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放连接
     */
    public void release(){
        try {
            connectionUtils.getThreadConnection().close();
            connectionUtils.removeConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 开启事务
     */
    public void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


}

说明:为什么要使用连接工具类把连接与线程绑定?

因为QueryRunner配置的是多例的,每次使用时都会创建一个新的连接,一个连接每次操作都有自己的事务,我们要控制事务就需要把连接固定,并手动控制事务。因为用了连接池,所以当需要就算QueryRunner是单例,每次获取的连接也不一定是同一个连接,将线程与连接绑定后,则可以保证事务前后一致性。

  1. 创建一个工厂类,用于获取经过代理的service,代理转账方法
/**
 * 用于创建Service代理对象的工厂
 */
public class BeanFactory {
    public void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    private IAccountService accountService;

    private TransactionManager transactionManager;

    public final void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    /**
     * 获取Service的代理对象
     * @return
     */
    public IAccountService getAccountService(){

        IAccountService proxyAccountService = (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
            /**
             * 添加事务的支持
             * @param proxy
             * @param method
             * @param args
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object res = null;

                if ("transfer".equals(method.getName())) {
                    String sourceName = (String) args[0];
                    String targetName = (String) args[1];
                    Float money = (Float) args[2];
                    try {
                        transactionManager.beginTransaction();
                        res = method.invoke(accountService,sourceName,targetName,money);
                        transactionManager.commit();
                    } catch (Exception e) {
                        System.out.println("事务回滚");
                        transactionManager.rollback();
                        e.printStackTrace();
                    } finally {
                        transactionManager.release();
                    }

                }

                return res;
            }
        });

        return proxyAccountService;
    }

}

  1. 配置代理工厂和代理的service
<bean id="accountService" class="cn.doubly.service.impl.AccountServiceImpl">
    <property name="accountDao" ref="accountDao"></property>
    <!--<property name="transactionManager" ref="transactionManager"/>-->
</bean>
<!--配置BeanFacotry-->
<bean id="beanFactory" class="cn.doubly.factory.BeanFactory">
    <property name="accountService" ref="accountService"/>
    <property name="transactionManager" ref="transactionManager"/>
</bean>
<!--配置代理的Service-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
  1. 通过代理的service
    @Resource(name =  "proxyAccountService")
    private IAccountService proxyAccountService ;

    @Test
    public void testTransfer(){
        proxyAccountService.transfer("aaa","bbb",100);
    }

至此,代理成功,可以成功控制事务!

AOP

AOP是面向切面编程,使用动态代理的技术。

导包

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.7</version>
</dependency>

完成Service,以及utils方法

配置bean.xml

<!--    配置IOC,把Service注入-->
    <bean id="accountService" class="cn.doubly.service.impl.AccountService"></bean>

<!--    spring中基于xml的AOP配置
    1.把通知Bean交给Spring来管理
    2.使用aop:config标签开始aop的配置
    3.使用aop:aspect标签表明配置切面
        id属性:是给切面提供一个唯一标识
        ref属性:是指定通知类bean的id
    4.在aop:aspect标签内部使用对应的标签来配置通知的类型
        aop:before前置通知
            method属性:用于指定Logger类中那个方法是前置通知
            pointcut属性:用于指定切入点表达式,改表达式的含义只的是对业务层中那些方法增强

        切入点表达式:
            execution(表达式)
            表达式: 访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
-->
<!--    配置Logger-->
    <bean id="logger" class="cn.doubly.utils.Logger"></bean>

<!--    配置AOP-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的类型,且建立通知方法和切入点方法关联-->
            <aop:before method="log" pointcut="execution(public void cn.doubly.service.impl.AccountService.addAccount())" ></aop:before>
        </aop:aspect>
    </aop:config>

切入点表达式:

execution(表达式)

表达式: 访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)

  • 访问修饰符可以省略
  • 返回值可以使用通配符,表示返回任意值
  • 包名可以使用通配符表示任意包。但是有几级包就需要写多少个*.
  • 可以使用…表示当前包及其子包,* *.AccountServiceImpl.addAccount()
  • 类名和方法名都可以使用*实现通配,* *..*.*()
  • 参数列表可以直接写数据类型,也可以使用…表示有无参数均可,* *..*.*(..)
  • 全通配:* *..*.*(..)

实际开发中切入点表达式的通用写法,切到业务层实现类下的所有方法:* cn.doubly.service.impl.*.*(..)

通知分类

  • 前置通知
  • 后置通知
  • 异常通知
  • 最终通知
<!--配置AOP-->
<aop:config>
    <!--配置PointCut,切入点-->
    <aop:pointcut id="pointCut" expression="execution(* cn.doubly.service.impl.*.*(..))"/>
    <!--配置切面-->
    <aop:aspect id="logAdvice" ref="logger">
        <!--前置通知-->
        <aop:before method="beforeAction" pointcut-ref="pointCut" ></aop:before>
        <!--后置通知-->
        <aop:after-returning method="afterAction" pointcut-ref="pointCut"></aop:after-returning>
        <!--异常通知-->
        <aop:after-throwing method="afterThrow" pointcut-ref="pointCut"></aop:after-throwing>
        <!--最终通知-->
        <aop:after method="finalAction" pointcut-ref="pointCut"></aop:after>
    </aop:aspect>
</aop:config>
  • 环绕通知:整个Invoke方法在执行就是环绕通知
<aop:aspect id="logAdvice" ref="logger">
<!--配置环绕通知-->
    <aop:around method="arroundAction" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
    public Object arroundAction(ProceedingJoinPoint pjp){
        Object res = null;
        try {
            Object[] args = pjp.getArgs();
            System.out.println("arroundAction");//前置
            res = pjp.proceed(args);
            System.out.println("arroundAction");//后置
            return res;
        } catch (Throwable throwable) {
            System.out.println("arroundAction");//异常
            throw new RuntimeException();
        }finally {
            System.out.println("arroundAction");//最终
        }
    }

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

    <context:component-scan base-package="cn.doubly" ></context:component-scan>

    <!--开启aop注解-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
package cn.doubly.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component("logger")
@Aspect //表示当前类是一个通知类
public class Logger {

    @Pointcut("execution(* cn.doubly.service.impl.*.*(..))")
    private void ptl(){}

    @Before("ptl()")
    public void beforeAction(){
        System.out.println("before");
    }

    @AfterReturning("ptl()")
    public void afterAction(){
        System.out.println("after");
    }

    @AfterThrowing("ptl()")
    public void afterThrow(){
        System.out.println("afterThrow");
    }

    @After("ptl()")
    public void finalAction(){
        System.out.println("finalAction");
    }

    @Around("ptl()")
    public Object arroundAction(ProceedingJoinPoint pjp){
        Object res = null;
        try {
            Object[] args = pjp.getArgs();
            System.out.println("arroundAction");//前置
            res = pjp.proceed(args);
            System.out.println("arroundAction");//后置
            return res;
        } catch (Throwable throwable) {
            System.out.println("arroundAction");//异常
            throw new RuntimeException();
        }finally {
            System.out.println("arroundAction");//最终
        }
    }

}

基于xml的声明式事务控制

spring中基于xml的声明式事务控制配置步骤
1.配置事务管理器
2.配置事务的通知
此时我们需要导入事务的约束,tx命名空间和约束,同时也要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
3.配置AOP中的通用切入点表达式
4.建立事务通知和切入点表达式的对应关系
5.配置事务的属性
是在事务的通知tx:advice标签的内部

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

    <context:component-scan base-package="cn.doubly"></context:component-scan>


    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url"    value="jdbc:mysql://localhost:3306/study?serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password"   value="123456"/>
    </bean>

    <bean id="accountDao" class="cn.doubly.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>


    <!--1.配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--2.配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--5.配置事务的属性-->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <!--配置aop-->
    <aop:config>
    	<!--3.配置AOP中的通用切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* cn.doubly.service.impl.*.*(..))"/>
        <!--4.建立事务通知和切入点表达式的对应关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
    </aop:config>
</beans>

事务属性配置
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS.
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值,表示任何异常都回滚。

基于注解的声明式事务控制

spring中基于注解的声明式事务控制配置步骤
1.配置事务管理器
2.开启Spring对注解事务的支持
3.在需要事务支持的地方使用@Transactional注解

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

    <context:component-scan base-package="cn.doubly"></context:component-scan>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url"    value="jdbc:mysql://localhost:3306/study?serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password"   value="123456"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--开启spring对注解事务的支持-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

</beans>
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true) //注解配置事务
public class AccountServiceImpl implements IAccountService {

    @Resource(name = "accountDao")
    private IAccountDao accountDao;

    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }

    @Transactional(propagation = Propagation.REQUIRED,readOnly = false) //注解不同属性配置
    public void transfer(String source, String target, Float money) {
        System.out.println("transfer...");

        Account sourceAccount = accountDao.findAccountByName(source);
        Account targetAccount = accountDao.findAccountByName(target);
        sourceAccount.setMoney(sourceAccount.getMoney() - money);
        targetAccount.setMoney(targetAccount.getMoney() + money);
        accountDao.updateAccount(sourceAccount);
//        int i = 1/0;
        accountDao.updateAccount(targetAccount);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值