基于注解的AOP开发

需求:使用AOP对转账案例进行优化,业务层仅保留核心业务,事务的控制使用AOP来完成

步骤分析

1. 新建maven项目,创建以下包类

2. 导入依赖(pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring03_aop_xml_anno</artifactId>
    <version>1.0-SNAPSHOT</version>

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

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>

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

        <!--AOP-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>

        <!-- Java单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <!-- 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <!-- DBUtils: 简化持久层(Dao)的代码 -->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.7</version>
        </dependency>

        <!--Spring对持久层的支持-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>

    </dependencies>
</project>

3. pojo实体类、dao(impl)持久层、service(impl)业务层

package com.bjpowernode.pojo;
public class Account {
    private Integer id;
    private String name;
    private Double money;
    // 省略了 getter、setter、有参构造、无参构造、toString()方法
}
package com.bjpowernode.dao;
import com.bjpowernode.pojo.Account;

public interface AccountDao {
    Account findById(Integer id);
    void updateMoney(String name,int money);
}
package com.bjpowernode.dao.impl;
import com.bjpowernode.dao.AccountDao;
import com.bjpowernode.pojo.Account;
import com.bjpowernode.utils.JDBCUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;

@Repository  // 数据访问层
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private QueryRunner queryRunner;

    @Autowired
    private JDBCUtils jdbcUtils;

    @Override
    public Account findById(Integer id){
        String sql = "select * from account where id = ?";
        try {
            return queryRunner.query(jdbcUtils.getConnection(),sql,new BeanHandler<Account>(Account.class),id);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("查找失败!");
        }
    }

    @Override
    public void updateMoney(String name, int money) {
        String sql = "update account set money = money + ? where name = ?";
        try {
            queryRunner.update(jdbcUtils.getConnection(),sql,money,name);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("修改失败!");
        }
    }
}
package com.bjpowernode.service;
import com.bjpowernode.pojo.Account;

public interface AccountService {
    Account findById(Integer id);
    void transfer(String fromUser,String toUser,int money);
}
package com.bjpowernode.service.impl;
import com.bjpowernode.dao.AccountDao;
import com.bjpowernode.pojo.Account;
import com.bjpowernode.service.AccountService;
import com.bjpowernode.utils.JDBCUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service // 业务层,相当于Bean了一个id
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Override
    public Account findById(Integer id){
        return accountDao.findById(id);
    }

    @Override
    public void transfer(String fromUser, String toUser, int money) {
        accountDao.updateMoney(fromUser, -money);
        //int a = 1 / 0;
        accountDao.updateMoney(toUser, money);
    }
}

4. utils工具类

package com.bjpowernode.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@Component
public class JDBCUtils {
    @Autowired
    private DataSource dataSource;

    // ThreadLocal和当前线程相关的对象,可以将对象绑定到线程上,在同一个线程中,获取到的始终是同一个对象
    private static ThreadLocal<Connection> TL = new ThreadLocal<>();

    public Connection getConnection() {
        Connection conn = null;
        try {
            conn = TL.get(); // 从线程上获取同一连接对象
            if ( conn == null ) {
                // 当前线程第一次调用getConnection方法
                conn = dataSource.getConnection();
                // 将连接对象和线程进行绑定
                TL.set(conn);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        // System.out.println(conn);
        return conn;
    }

    public void begin() {
        try {
            // 自动连接?:否,改手动
            getConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void rollback() {
        try {
            getConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void commit() {
        try {
            getConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void release() {
        try {
            // 不是真正的关闭,而是将连接对象返回给连接池,底层是动态代理技术
            getConnection().close();
            // 将连接对象从当前线程移除
            TL.remove();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

5. 通知类(对目标方法进行增强的类)

package com.bjpowernode.advice;
import com.bjpowernode.utils.JDBCUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.SQLException;

// 事务管理器,对业务方法提供事务的增强处理
@Component
@Aspect
public class TransactionManager {
    @Autowired
    private JDBCUtils jdbcUtils;

    public void begin() {
        System.out.println("开启事务");
        try {
            jdbcUtils.getConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

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

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

    //抽取切入点表达式
    @Pointcut("execution(* com.bjpowernode.service..*.transfer(..))")
    private void pc_transfer(){}

    // 环绕通知:整合前面的通知
    @Around("pc_transfer()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object result = null;
        try {
            begin(); // 开启事务
            result = pjp.proceed(); // 执行目标方法

        } catch (Throwable throwable) {
            throwable.printStackTrace();
            rollback();
            // 必须将异常抛出去,否则其它的通知类无法捕获异常
            throw throwable;
        } finally {
            commit(); // 提交事务
            jdbcUtils.release();
        }
        return result;
    }
}
// 所有包下的service包及其子包下的所有类中的所有方法,返回值任意,修饰符任意
    //抽取切入点表达式
    @Pointcut("execution(* *..service..*.*(..))")
    private void any_method(){}

    // 环绕通知:整合前面的通知
    @Around("any_method()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object result = null;
        Signature signature = pjp.getSignature(); // 方法签名对象
        String methodName = signature.getName(); // 方法名

        // 查询方法直接执行,不用事务:query*、get*、find*、select*
        if (methodName.startsWith("query") || methodName.startsWith("get")
                || methodName.startsWith("find") || methodName.startsWith("select")){
            return pjp.proceed(); // 执行目标方法
        } else {
            try {
                begin(); // 开启事务
                result = pjp.proceed(); // 执行目标方法

            } catch (Throwable throwable) {
                throwable.printStackTrace();
                rollback();
                // 必须将异常抛出去,否则其它的通知类无法捕获异常
                throw throwable;
            } finally {
                commit(); // 提交事务
                jdbcUtils.release();
            }
            return result;
        }
    }

假如有多个通知类,@Around注解方式不能控制哪个通知类先执行。所以建议用xml形式配置。

6. 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
       http://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">

    <!--开启组件扫描-->
    <context:component-scan base-package="com.bjpowernode"/>

    <!--配置数据源-->
    <!-- 方法一: 导入jdbc.properties -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!-- EL表达式 -->
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

    <!--DBUtils的核心类:QueryRunner-->
    <bean id="qr" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource" />
    </bean>

    <!-- 开启aop注解支持 -->
    <aop:aspectj-autoproxy />
</beans>

7. jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
jdbc.username=root
jdbc.password=root

8. 测试类

import com.bjpowernode.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@ContextConfiguration("classpath:applicationContext.xml") // 指定配置文件的所在路径
@RunWith(SpringJUnit4ClassRunner.class) // 指定使用spring编写的增强的junit类运行代码
public class Tester {
    @Autowired
    AccountService accountService;

    @Test
    public void findById(){
        System.out.println(accountService.findById(1));
    }

    @Test
    public  void testUpdateMoney(){
        accountService.transfer("jack","tom",100);
    }
}

 

posted @ 2022-11-22 01:24  鹿先森JIAN  阅读(37)  评论(0编辑  收藏  举报