荐 小白体验:spring入门——做一个AOP小Demo

漫天蒲公英 9天前   阅读数 6 0

Spring+AOP一个小Demo记录

开发环境准备

  • idea2020 + MySQL8.0.19 + maven3 + java1.8

项目需求

  • 使用spring管理对象
  • 实现转账功能
  • 保持转账业务的一致性(使用事务)
  • 使用AOP技术完成事务控制

数据准备

  • 数据表一个,3个主要字段,ID、name、money;
create table account(
	id int primary key auto_increment,
	name varchar(40),
	money float
)ENGINE=InnoDB character set utf8 collate utf8_general_ci;
  • 插入两条测试数据
insert into account(name,money) values('zhangsan',1000);
insert into account(name,money) values('lisi',1000);
insert into account(name,money) values('wangwu',1000);

技术选型

  • 数据库连接池使用 druid 配合 QeuryRunner () + mysql
        <!--druid-->
		<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.20</version>
        </dependency>
        <!--QueryRunner-->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>
        <!--MySQL连接-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>
  • spring核心+aop解析+junit整合
        <!--spring核心-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <!--junit整合-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--AOP解析-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
  • 添加maven编译字符集
<properties>
    <!-- 文件拷贝时的编码 -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <!-- 编译时的编码 -->
    <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>

到这里基本上pom.xml基本搞定,个人还可以添加一些自己常用的包的坐标,有这些包就能正常运行今天要做的项目了。

项目部分

创建项目

  • 创建一个空的maven项目
  • 将上述坐标导入pom.xml中
  • 创建一个SpringConfiguration.xml
  • 创建所需要的几个包 如下文件目录

在这里插入图片描述

在pojo中创建实体类

public class Account {
    private Integer id;
    private String name;
    private Double money;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

配置SpringConfiguration.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">
    
    
</beans>

这里推荐官网去看看:https://docs.spring.io/spring/docs/5.2.6.RELEASE/spring-framework-reference/core.html#resources 里面有很多介绍

加入xml相关配置

<!--开启注解配置-->
<context:annotation-config/>
<!--扫描范围-->
<context:component-scan base-package="com.moro"/>
<!--引入外部文件-->
<context:property-placeholder location="db.properties"/>
<!--开启注解配置aop-->
<aop:aspectj-autoproxy/>
<!--配置QueryRunner-->
<bean id="querRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"/>

<!--配置数据源-->
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
    <!--驱动-->
    <property name="driverClassName" value="${jdbc.driver}"/>
    <!--数据库地址-->
    <property name="url" value="${jdbc.url}"/>
    <!--用户名-->
    <property name="username" value="${jdbc.username}"/>
    <!--密码-->
    <property name="password" value="${jdbc.password}"/>
    <!--初始化数据源数量-->
    <property name="initialSize" value="3"/>
    <!--最大连接-->
    <property name="maxActive" value="10" />
    <!--最大等待-->
    <property name="maxWait" value="30000" />
</bean>

db.properties这个文件里面就是我们的数据库池的相关配置,可以不引用直接写,也可以自己配置,这里方便演示,两种都有用到:

db.properties文件如下:

jdbc.driver=com.mysql.cj.jdbc.Driver 
jdbc.url=jdbc:mysql://localhost:3306/[你的数据库名称]?
serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
jdbc.username=root
jdbc.password=root

创建一个工具类让每次的查询使用的连接都是同一个

/** * @author Moro * @createTime 2020/5/22 -12:00 * @projectName Moro_Demo_Spring05 * 控制connection的唯一性 */
@Component("connectionUtil")
public class ConnectionUtil {

    //使用ThreadLocal控制connection的独立性
    private final ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

    //注入数据源
    @Autowired
    private final DataSource dataSource = null;
    
    private Connection connection = null;
    
    /** * 获取一个线程连接 * @return 安全的线程连接 */
    @Bean("threadConnection")
    public Connection getConnection(){
        //每次获取连接时会去判断ThreadLocal内是否有一个连接
        if (threadLocal.get() == null){
            try {
                //如果没有连接就从连接池中拿出一个连接
                connection = dataSource.getConnection();
                //放入ThreadLocal中
                threadLocal.set(connection);
                System.out.println("已获取连接:"+threadLocal.get());
                //并将获取的连接返回
                return threadLocal.get();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
                throw new RuntimeException("获取连接失败");
            }
        }
        //如果有连接直接返回连接
        return threadLocal.get();
    }

    /** * 使用完后解除连接和线程的绑定 */
    public void removeConnect(){
        threadLocal.remove();
        System.out.println("线程与事务已经解除绑定");
    }

创建AccountDao和AccountDaoImpl

  • AccountDao 接口
public interface AccountDao {
    /** * 查询所有 * @return Account列表 */
    List<Account> findAll();

    /** * 使用名称查询 * @return 一个Account */
    Account findAccountByName(String name);
    
    /** * 保存账户信息 * */
    void saveAccount(Account account);
}
  • AccountDaoImpl 实现接口AccountDao
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Autowired
    QueryRunner runner = null;

    @Autowired
    ConnectionUtil connectionUtil = null;


    /** * 查询所有 * * @return Account列表 */
    public List<Account> findAll() {
        try {
            //每次进行数据库操作都要去获取同一个连接对象,以保持数据是正常的
            return runner.query(connectionUtil.getConnection(), "select * from account", new BeanListHandler<Account>(Account.class));
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException("sql异常---------------");
        }
    }


    /** * @param name 账户名称 * @return 账户 */
    public Account findAccountByName(String name) {
        try {
            return runner.query(connectionUtil.getConnection(), "select * from account where name = ?", new BeanHandler<Account>(Account.class),name);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException("sql异常---------------");
        }
    }


    /** * @param account 一个账户 */
    public void saveAccount(Account account) {
        try {
            runner.update(connectionUtil.getConnection(), "update account set money =? where id= ?",account.getMoney(),account.getId());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException("sql异常---------------");
        }
    }
}

创建AccountService和AccountServiceImpl

  • AccountService 接口
public interface AccountService {

    /** * 查询所有 * @return Account列表 */
    List<Account> findAll();

    /** * 转账 * @return 是否转账成功 */
    boolean transferAccount(String targetName, String sourceName, Double transferMoney);
}
  • AccountServiceImpl 实现接口AccountService
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private final AccountDao dao = null;


    /** * @return 所有用户 */
    public List<Account> findAll() {
        return dao.findAll();
    }


    /** * 转账 * @param targetName 转出账户 * @param sourceName 接收账户 * @param transferMoney 转的数量 * @return 是否正常 */
    public boolean transferAccount(String targetName,String sourceName,Double transferMoney) {
        try {
            //获取两个需要操作的账户
            Account targetAccount = dao.findAccountByName(targetName);
            Account sourceAccount = dao.findAccountByName(sourceName);
            //进行转账
            targetAccount.setMoney(targetAccount.getMoney() - transferMoney);
            sourceAccount.setMoney(sourceAccount.getMoney() + transferMoney);
            //保存账户信息
            dao.saveAccount(targetAccount);
            dao.saveAccount(sourceAccount);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("有异常情况");
            return false;
        }
        return true;
    }
}

创建事务管理工具类

@Component("transactionUtil")
public class TransactionUtil {

    //获取连接工具类
    @Autowired
    private final ConnectionUtil connectionUtil = null;
    //获取当前使用的连接保持跟dao中使用的一致
    @Autowired
    private Connection connection = null;

    /** * 开启事务 */
    public void startTransaction(){
        try {
            //如果连接已经归还则重新获取一个
            if (connection == null){
                connection = connectionUtil.getConnection();
            }
            connection.setAutoCommit(false);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException("事务开启失败");
        }
    }

    /** * 提交事务 */
    public void commitTransaction(){
        try {
            connection.commit();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException("事务提交失败");
        }
    }

    /** * 回顾事务 */
    public void rollbackTransaction(){
        try {
            connection.rollback();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException("事务回滚失败");
        }
    }

    /** *释放资源 */
    public void releaseTransaction(){
        try {
            //回收连接
            connection.close();
            //置空连接对象
            connection = null;
            //解除连接绑定
            connectionUtil.removeConnect();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException("资源释放失败");
        }
    }
}

创建切面增强类

@Aspect
@Component("accountServiceAop")
public class AccountServiceAop {

    //指出切点
    @Pointcut("execution(* com..impl.AccountServiceImpl.*(..))")
    private void pointCut(){}

    @Autowired
    private final TransactionUtil transactionUtil = null;

    Object result = null;
    @Around("pointCut()")
    public Object aroundAdviceForAccountService(ProceedingJoinPoint pjp){
        try {
            //获取方法执行所需要的参数
            Object[] agrs = pjp.getArgs();
            System.out.println("开启事务");
            transactionUtil.startTransaction();
            //执行方法
            result =  pjp.proceed(agrs);
            transactionUtil.commitTransaction();
            System.out.println("提交事务");
            return result;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            transactionUtil.rollbackTransaction();
            throw new RuntimeException("有异常状况");
        } finally {
            if (pjp.getSignature().getName() != null){
                System.out.println("当前执行的方法是:"+pjp.getSignature().getName());
            }
            transactionUtil.releaseTransaction();
            System.out.println("释放资源");
        }
    }
}

测试

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:SpringConfiguration.xml")
public class TestAop {

    @Autowired
    private AccountService service = null;

    @Test
    public void testTransfer(){
        boolean flag = service.transferAccount("zhangsan", "lisi", 200.00);
        if (flag){
            System.out.println("转账成功!!!!");
            List<Account> accountList = service.findAll();
            for (Account account : accountList) {
                System.out.println(account);
            }
        }else {
            System.out.println("转账失败!!!!");
        }
    }
}

注意:本文归作者所有,未经作者允许,不得转载

全部评论: 0

    我有话说: