Spring AOP 从入门到入坑。
Spring AOP 从入门到入坑。
文章目录
从事务引入。
转账操作。
没加事务控制的情况。
package com.geek.service.impl;
import com.geek.dao.IAccountDao;
import com.geek.domain.Account;
import com.geek.service.IAccountService;
import java.util.List;
/**
* 账户的业务层实现类。
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
/**
* 转账操作。
*
* @param sourceName
* @param targetName
* @param money
*/
@Override
public void transfer(String sourceName, String targetName, Float money) {
// 根据名称查询转出账户。
Account source = accountDao.findByName(sourceName);
// 根据名称查询转入账户。
Account target = accountDao.findByName(targetName);
// 转出账户减钱。
source.setMoney(source.getMoney() - money);
// 转入账户加钱。
target.setMoney(target.getMoney() + money);
// 更新转出账户。
accountDao.updateAccount(source);
int a = 1 / 0;
// 更新转入账户。
accountDao.updateAccount(target);
}
/**
* 查找全部。
*
* @return
*/
@Override
public List<Account> FindAllAccount() {
return accountDao.FindAllAccount();
}
/**
* 根据 id 查找一个。
*
* @param accountId
* @return
*/
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
/**
* 保存。
*
* @param account
*/
@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
/**
* 修改。
*
* @param account
*/
@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
/**
* 删除。
*
* @param accountId
*/
@Override
public void deleteAccount(Integer accountId) {
accountDao.deleteAccount(accountId);
}
}
手动实现 ThreadLocal 线程池、连接池。
/utils/ConnectionUtils.java
package com.geek.utils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 连接工具类。用于从数据库中获取一个连接,并且实现和线程的绑定。
*/
public class ConnectionUtils {
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接。
*
* @return
*/
public Connection getThreadConnection() {
try {
// 先从 ThreadLocal 中获取。
Connection connection = threadLocal.get();
// 判断当前线程上是否有连接。
if (connection == null) {
// 从数据源中获取一个连接,并且写入 ThreadLocal 中。
connection = dataSource.getConnection();
threadLocal.set(connection);
}
// 返回当前线程的上的连接。
return connection;
} catch (SQLException e) {
// e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑。
*/
public void removeConnection() {
threadLocal.remove();
}
}
/utils/TransactinManager.java
package com.geek.utils;
import java.sql.SQLException;
/**
* 和事务管理相关的工具类。
* 开启事务、提交事务、回滚事务和释放连接。
*/
public class TransactinManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务。
*/
public void beginTransaction() {
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 提交事务。
*/
public void commitTransaction() {
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 回滚事务。
*/
public void rollbackTransaction() {
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 释放连接。
*/
public void releaseTransaction() {
try {
connectionUtils.getThreadConnection().close();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 把连接和线程解绑。
*/
public void removeConnection() {
try {
connectionUtils.getThreadConnection().close();
connectionUtils.removeConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
连接池的好处。
把消耗时间获取 / 创建连接的过程放在加载应用(一开始),启动 Tomcat,加载应用就创建连接,在后续项目运行阶段不再找数据库获取连接,提升使用 Connection 的效率。
服务器也有“池”的技术——线程池。Tomcat 启动时会初始化一堆线程放入容器中,每次使用直接从容器中拿线程直接使用。
线程池中的连接调用 close(); 方法并不是关闭连接,而是还回连接池中。(线程中绑定了一个连接)。我们把连接关闭,把线程还回线程池中时,线程上还是有连接的,只不过是关闭的连接。下一次获取这个线程,要判断此线程中是否有连接时,if (connection == null) {} ——> false。但这个连接是 closed。
dao 层加入事务控制(手动版)。
package com.geek.service.impl;
import com.geek.dao.IAccountDao;
import com.geek.domain.Account;
import com.geek.service.IAccountService;
import com.geek.utils.TransactinManager;
import java.util.List;
/**
* 账户的业务层实现类。
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
private TransactinManager transactinManager;
public void setTransactinManager(TransactinManager transactinManager) {
this.transactinManager = transactinManager;
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
/**
* 转账操作。
*
* @param sourceName
* @param targetName
* @param money
*/
@Override
public void transfer(String sourceName, String targetName, Float money) {
try {
// 开启事务。
transactinManager.beginTransaction();
// 执行操作。
// 根据名称查询转出账户。
Account source = accountDao.findByName(sourceName);
// 根据名称查询转入账户。
Account target = accountDao.findByName(targetName);
// 转出账户减钱。
source.setMoney(source.getMoney() - money);
// 转入账户加钱。
target.setMoney(target.getMoney() + money);
// 更新转出账户。
accountDao.updateAccount(source);
int a = 1 / 0;
// 更新转入账户。
accountDao.updateAccount(target);
// 提交事务。
transactinManager.commitTransaction();
} catch (Exception e) {
// 回滚操作。
transactinManager.rollbackTransaction();
} finally {
// 释放连接。
transactinManager.release();
}
}
/**
* 查找全部。
*
* @return
*/
@Override
public List<Account> FindAllAccount() {
try {
// 开启事务。
transactinManager.beginTransaction();
// 执行操作。
List<Account> accountList = accountDao.FindAllAccount();
// 提交事务。
transactinManager.commitTransaction();
// 返回结果。
return accountList;
} catch (Exception e) {
// 回滚操作。
transactinManager.rollbackTransaction();
throw new RuntimeException(e);
} finally {
// 释放连接。
transactinManager.release();
}
}
/**
* 根据 id 查找一个。
*
* @param accountId
* @return
*/
@Override
public Account findAccountById(Integer accountId) {
try {
// 开启事务。
transactinManager.beginTransaction();
// 执行操作。
Account account = accountDao.findAccountById(accountId);
// 提交事务。
transactinManager.commitTransaction();
// 返回结果。
return account;
} catch (Exception e) {
// 回滚操作。
transactinManager.rollbackTransaction();
throw new RuntimeException(e);
} finally {
// 释放连接。
transactinManager.release();
}
}
/**
* 保存。
*
* @param account
*/
@Override
public void saveAccount(Account account) {
try {
// 开启事务。
transactinManager.beginTransaction();
// 执行操作。
accountDao.saveAccount(account);
// 提交事务。
transactinManager.commitTransaction();
} catch (Exception e) {
// 回滚操作。
transactinManager.rollbackTransaction();
} finally {
// 释放连接。
transactinManager.release();
}
}
/**
* 修改。
*
* @param account
*/
@Override
public void updateAccount(Account account) {
try {
// 开启事务。
transactinManager.beginTransaction();
// 执行操作。
accountDao.updateAccount(account);
// 提交事务。
transactinManager.commitTransaction();
} catch (Exception e) {
// 回滚操作。
transactinManager.rollbackTransaction();
} finally {
// 释放连接。
transactinManager.release();
}
}
/**
* 删除。
*
* @param accountId
*/
@Override
public void deleteAccount(Integer accountId) {
try {
// 开启事务。
transactinManager.beginTransaction();
// 执行操作。
accountDao.deleteAccount(accountId);
// 提交事务。
transactinManager.commitTransaction();
} catch (Exception e) {
// 回滚操作。
transactinManager.rollbackTransaction();
} finally {
// 释放连接。
transactinManager.release();
}
}
}
这时不再希望注入 dataSource。
<!-- 配置 QueryRunner 对象。-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"
scope="prototype"><!-- prototype,每来一个连接创建一个对象,线程安全。-->
<!-- 注入数据源。-->
<constructor-arg name="ds" ref="dataSource"/>
</bean>
dao 中,
private ConnectionUtils connectionUtils;
// 让 Spring 注入。
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
并且每个 queryRunner 方法指定参数:连接。
package com.geek.dao.impl;
import com.geek.dao.IAccountDao;
import com.geek.domain.Account;
import com.geek.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.SQLException;
import java.util.List;
/**
* 账户的持久层实现类。
*/
public class AccountDaoImpl implements IAccountDao {
private QueryRunner queryRunner;
private ConnectionUtils connectionUtils;
// 让 Spring 注入。
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
/**
* 根据名称查询账户。
*
* @param name
* @return 如果有唯一结果就返回。如果没有结果返回 null。
* 如果结果超过一个,就抛异常。
*/
@Override
public Account findByName(String name) {
try {
// List<Account> accountList = queryRunner.query("select * from account where name = ?", new BeanListHandler<>(Account.class), name);
List<Account> accountList = queryRunner.query(connectionUtils.getThreadConnection(), "select * from account where name = ?", new BeanListHandler<>(Account.class), name);
if (accountList == null || accountList.size() == 0) {
return null;
}
return accountList.get(0);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public List<Account> FindAllAccount() {
try {
return queryRunner.query(connectionUtils.getThreadConnection(), "select * from account", new BeanListHandler<Account>(Account.class));
} catch (SQLException e) {
// e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public Account findAccountById(Integer accountId) {
try {
return queryRunner.query(connectionUtils.getThreadConnection(), "select * from account where id = ?", new BeanHandler<Account>(Account.class), accountId);
} catch (SQLException e) {
// e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public void saveAccount(Account account) {
try {
queryRunner.update(connectionUtils.getThreadConnection(), "insert into account(name, money) values (?, ?)", account.getName(), account.getMoney());
} catch (SQLException e) {
// e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public void updateAccount(Account account) {
try {
queryRunner.update(connectionUtils.getThreadConnection(), "update account set name = ?, money= ? where id = ?", account.getName(), account.getMoney(), account.getId());
} catch (SQLException e) {
// e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public void deleteAccount(Integer accountId) {
try {
queryRunner.update(connectionUtils.getThreadConnection(), "delete from account where id = ?", accountId);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
重新配置注入。
<?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">
<!-- 配置 service 对象。-->
<bean id="accountService" class="com.geek.service.impl.AccountServiceImpl">
<!-- 注入 dao。-->
<property name="accountDao" ref="accountDao"/>
<!-- 注入事务管理器。-->
<property name="transactionManager" ref="txManager"/>
</bean>
<!-- 配置 dao 对象。-->
<bean id="accountDao" class="com.geek.dao.impl.AccountDaoImpl">
<!-- 注入 QueryRunner。-->
<property name="queryRunner" ref="queryRunner"/>
<!-- 注入 ConnectionUtils。-->
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
<!-- <!– 配置 QueryRunner 对象。–>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"
scope="prototype"><!– prototype,每来一个连接创建一个对象,线程安全。–>
<!– 注入数据源。–>
<constructor-arg name="ds" ref="dataSource"/>
</bean>-->
<!-- 配置 QueryRunner 对象。-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"
scope="prototype"><!-- prototype,每来一个连接创建一个对象,线程安全。-->
</bean>
<!-- 配置数据源对象。-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 连接数据库的必备信息。-->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://192.168.223.128:3306/geek_spring_dbutils"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- 配置 Connection 的工具类。-->
<bean id="connectionUtils" class="com.geek.utils.ConnectionUtils">
<!-- 注入数据源。-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置业务层的管理器。-->
<bean id="txManager" class="com.geek.utils.TransactionManager">
<!-- 注入 ConnectionUtils。-->
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
</beans>
事务控制住了。
但配置相当麻烦。
Spring AOP 该出场了。
AOP 正式出场。
动态代理。
从买电脑引入。
最开始直接从生产厂家购买。
-~ ~ ~ ↓ ↓ ↓ ~ ~ ~ 生产厂家生产的电脑太多,库房存不下。
生产厂家自己的销售和售后。
-~ ~ ~ ↓ ↓ ↓ ~ ~ ~ 要养活销售部和售后部,成本高。
代理商 / 经销商。
Java 动态代理机制。
生产者。
package com.geek.proxy;
public interface IProducer {
/**
* 销售。
*
* @param money
*/
void saleProduct(float money);
/**
* 售后。
*
* @param money
*/
void afterMarket(float money);
}
package com.geek.proxy;
/**
* 生产者。
*/
public class Producer implements IProducer {
/**
* 销售。
*
* @param money
*/
public void saleProduct(float money) {
System.out.println("销售产品,并拿到钱:" + money);
}
/**
* 售后。
*
* @param money
*/
public void afterMarket(float money) {
System.out.println("提供售后服务,并拿到钱:" + money);
}
}
消费者。
package com.geek.proxy;
/**
* 模拟一个消费者。
*/
public class Client {
public static void main(String[] args) {
Producer producer = new Producer();
producer.saleProduct(10000F);
}
}
动态代理。
-
特点:字节码随用随创建,随用随加载。
-
作用:不修改源码的基础上对方法增强。
-
分类。
基于接口的动态代理。
基于子类的动态代理。
基于接口的动态代理。
-
涉及的类:Proxy。
-
提供者:JDK 官方。
-
如何创建代理对象。
使用 Proxy 类中的 newProxyInstance(); 方法。
- 创建代理对象的要求。
被代理类至少实现一个接口,如果没有则不能使用。
- newInstance() 方法的参数。
loader: ClassLoader ——> 类加载器。
~ 用于加载代理对象的字节码。和被代理对象使用相同的类加载器。(固定写法。.getClass().getClassLoader())。interfaces: Class<?>[]
~ 用于让代理对象和被代理对象有相同的方法。(有相同的方法:实现同一接口)。(固定写法。.getClass().getInterfaces())。h: InvocationHandler
~ 让我们写:如何代理。(用于提供增强的方法)。
一般写一个该接口的实现类(一般是匿名内部类,但不是必须的)。此接口的实现类是谁用谁写。
package com.geek.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 模拟一个消费者。
*/
public class Client {
public static void main(String[] args) {
Producer producer = new Producer();
// producer.saleProduct(10000F);
/**
* 动态代理。
*
* 特点:字节码随用随创建,随用随加载。
* 作用:不修改源码的基础上对方法增强。
* 分类。
* 基于接口的动态代理。
* 基于子类的动态代理。
*
* 基于接口的动态代理。
* 设计的类:Proxy。
* 提供者:JDK 官方。
*
* 如何创建代理对象。
* 使用 Proxy 类中的 newProxyInstance(); 方法。
*
* 创建代理对象的要求。
* 被代理类至少实现一个接口,如果没有则不能使用。
*
* newInstance() 方法的参数。
* + newInstance() 方法的参数。
*
* > + loader: ClassLoader ——> 类加载器。
* > ~ 用于加载代理对象的字节码。和被代理对象使用相同的类加载器。(固定写法。.getClass().getClassLoader())。
* >
* > + interfaces: Class<?>[]
* > ~ 用于让代理对象和被代理对象有相同的方法。(有相同的方法:实现同一接口)。(固定写法。.getClass().getInterfaces())。
* >
* > + h: InvocationHandler
* > ~ 让我们写:如何代理。(用于提供增强的方法)。
* > 一般写一个该接口的实现类(一般是匿名内部类,但不是必须的)。此接口的实现类是谁用谁写。
*/
IProducer proxyInstance = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.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 returnValue = null;
// 增强的代码。
// 获取方法执行的参数。
float money = (float) args[0];
// 判断当前方法是不是销售。
if ("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);// 经销商拿走 20%。
}
// return null;
// return method.invoke(producer, args);
return returnValue;
}
});
proxyInstance.saleProduct(10000f);
}
}
~~~
销售产品,并拿到钱:9600.0
基于子类的动态代理~cglib 动态代理。
如果一个类不 implement 任何接口。==>
- cglib 动态代理。
第三方 cglib 库。
<dependencies>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
</dependencies>
- create(); 方法的参数。
public Object create(Class[] argumentTypes, Object[] arguments) {
~
- Class[] argumentTypes
指定被代理对象的字节码。
package com.geek.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 模拟一个消费者。
*/
public class Client {
public static void main(String[] args) {
Producer producer = new Producer();
// producer.saleProduct(10000F);
/**
* 动态代理。
*
* 特点:字节码随用随创建,随用随加载。
* 作用:不修改源码的基础上对方法增强。
* 分类。
* 基于接口的动态代理。
* 基于子类的动态代理。
*
* 基于子类口的动态代理。
* 涉及的类:Enhancer。
* 提供者:第三方 cglib 库。
*
* 如何创建代理对象。
* 使用 Enhancer 类中的 create(); 方法。
*
* 创建代理对象的要求。
* 被代理类不能是最终类。(需要有子类)。
*
* create() 方法的参数。
* > ~ 让我们写:如何代理。(用于提供增强的方法)。
* > 一般写一个该接口的实现类(一般是匿名内部类,但不是必须的)。此接口的实现类是谁用谁写。
* > 我们一般写的都是该接口的子接口实现类:new MethodInterceptor() {
*
*/
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过此方法。
* @param o proxy。
* @param method
* @param objects args。
* ~ ~ ~ ~ ~ ~ ~ 以上三个参数和基于接口的动态代理 invoke(); 方法的参数一样。
* @param methodProxy 当前执行方法的代理对象。
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object returnValue = null;
// 增强的代码。
// 获取方法执行的参数。
float money = (float) objects[0];
// 判断当前方法是不是销售。
if ("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);// 经销商拿走 20%。
}
// return null;
// return method.invoke(producer, args);
return returnValue;
}
});
cglibProducer.saleProduct(12000F);
}
}
~~~
销售产品,并拿到钱:9600.0
上一篇: 一个简单的信息管理系统