实现AOP
相关知识
拦截器的invoke方法
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
参数说明
-
Object proxy:代理对象本身(动态生成的子类实例) -
Method method:被调用的方法 (如hello(),morning()) -
Object[] args:方法参数(调用时传入的参数)
思路:通过注解的方式实现AOP,客户端自定义一个拦截器,例如Around拦截器,处理增强方法的逻辑,定义一个ProxyResolver传入原始Bean和自定义的拦截器进行代理类的创建;然后实现完整的AOP,定义一个AroundProxyBeanPostProcessor类实现BeanPostProcessor接口,在其中识别指定的注解并创建代理类,最后通过配置类将其放在IOC容器当中
实现ProxyResolver
在IoC容器中,实现动态代理需要用户提供两个Bean:
- 原始Bean,即需要被代理的Bean;
- 拦截器,即拦截了目标Bean的方法后,会自动调用拦截器实现代理功能。
拦截器需要定义接口,这里我们直接用Java标准库的InvocationHandler,免去了自定义接口。
假定我们已经从IoC容器中获取了原始Bean与实现了InvocationHandler的拦截器Bean,那么就可以编写一个ProxyResolver来实现AOP代理。
从ByteBuddy的官网上搜索很容易找到相关代码,我们整理为createProxy()方法:
typescript
public class ProxyResolver {
// ByteBuddy实例:
ByteBuddy byteBuddy = new ByteBuddy();
// 传入原始Bean、拦截器,返回代理后的实例:
public <T> T createProxy(T bean, InvocationHandler handler) {
// 目标Bean的Class类型:
Class<?> targetClass = bean.getClass();
// 动态创建Proxy的Class:
Class<?> proxyClass = this.byteBuddy
// 子类用默认无参数构造方法:
.subclass(targetClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR)
// 拦截所有public方法:
.method(ElementMatchers.isPublic()).intercept(InvocationHandlerAdapter.of(
// 新的拦截器实例:
//这个拦截器的作用是将所有public方法调用转发给外部的handler处理,同时传递原始bean实例、方法信息和参数,实现了AOP的切面功能
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 将方法调用代理至原始Bean:
return handler.invoke(bean, method, args);
}
}))
// 生成字节码:
.make()
// 加载字节码:
.load(targetClass.getClassLoader()).getLoaded();
// 创建Proxy实例:
Object proxy;
try {
proxy = proxyClass.getConstructor().newInstance();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
return (T) proxy;
}
}
实现Around
AroundProxyBeanPostProcessor的机制非常简单:检测每个Bean实例是否带有@Around注解,如果有,就根据注解的值查找Bean作为InvocationHandler,最后创建Proxy,返回前保存了原始Bean的引用,因为IoC容器在后续的注入阶段要把相关依赖和值注入到原始Bean。
实现before和after
只需要实现拦截器接口,再实现类中定义before或after方法,后续定义拦截器的时候再实现这个抽象
类就可以了
typescript
public abstract class BeforeInvocationHandlerAdapter implements InvocationHandler {
public abstract void before(Object proxy, Method method, Object[] args);
@Override
public final Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(proxy, method, args);
return method.invoke(proxy, args);
}
}
关键组件说明
1. 核心处理器
-
AroundProxyBeanPostProcessor: AOP入口,处理@Around注解 -
AnnotationProxyBeanPostProcessor: 抽象基类,支持自定义注解
2. 拦截器类型
-
Around: 完全控制方法调用
-
Before : 方法执行前拦截 (
BeforeInvocationHandlerAdapter) -
After : 方法执行后拦截 (
AfterInvocationHandlerAdapter)
3. 执行流程特点
初始化阶段:
-
BeanPostProcessor优先创建
-
检测@Around注解创建代理
-
属性注入到原始Bean
运行时阶段:
-
方法调用被代理对象拦截
-
根据拦截器类型执行相应逻辑
-
最终调用原始Bean方法
扩展Annotation
假设我们后续编写了一个事务模块,提供注解@Transactional,那么,要启动AOP,就必须仿照AroundProxyBeanPostProcessor,提供一个TransactionProxyBeanPostProcessor,不过复制代码太麻烦了,我们可以改造一下AroundProxyBeanPostProcessor,用泛型代码处理Annotation,先抽象出一个AnnotationProxyBeanPostProcessor:
scala
public abstract class AnnotationProxyBeanPostProcessor<A extends Annotation> implements BeanPostProcessor {
Map<String, Object> originBeans = new HashMap<>();
Class<A> annotationClass;
public AnnotationProxyBeanPostProcessor() {
this.annotationClass = getParameterizedType();
}
...
}
实现AroundProxyBeanPostProcessor就一行定义:
java
public class AroundProxyBeanPostProcessor extends AnnotationProxyBeanPostProcessor<Around> {
}
后续如果我们想实现@Transactional注解,只需定义:
java
public class TransactionalProxyBeanPostProcessor extends AnnotationProxyBeanPostProcessor<Transactional> {
}
就能自动根据@Transactional启动AOP。
实现JdbcTemplate
配置DataSource
使用JdbcTemplate之前,我们需要配置JDBC数据源。Spring本身只提供了基础的DriverManagerDataSource,但Spring Boot有一个默认配置的数据源,并采用HikariCP作为连接池。
实现一个HikariCP支持的DataSource,用配置类 JdbcConfiguration管理
less
@Configuration
public class JdbcConfiguration {
@Bean(destroyMethod = "close")
DataSource dataSource(
// properties:
@Value("${summer.datasource.url}") String url,
@Value("${summer.datasource.username}") String username,
@Value("${summer.datasource.password}") String password,
@Value("${summer.datasource.driver-class-name:}") String driver,
@Value("${summer.datasource.maximum-pool-size:20}") int maximumPoolSize,
@Value("${summer.datasource.minimum-pool-size:1}") int minimumPoolSize,
@Value("${summer.datasource.connection-timeout:30000}") int connTimeout
) {
var config = new HikariConfig();
config.setAutoCommit(false);
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
if (driver != null) {
config.setDriverClassName(driver);
}
config.setMaximumPoolSize(maximumPoolSize);
config.setMinimumIdle(minimumPoolSize);
config.setConnectionTimeout(connTimeout);
return new HikariDataSource(config);
}
}
然后我们需要封装一个JdbcTemplate类,注入依赖DataSource,实现对数据库的一些操作 底层实现如下:
scss
1. queryForObject(sql, rowMapper, 123)
↓
2. execute(psc, pscAction) // 中间层execute
↓
3. execute(connectionCallback) // 底层execute
↓
4. dataSource.getConnection() // 获取连接
↓
5. conn.setAutoCommit(true) // 临时启用自动提交
↓
6. psc.createPreparedStatement(conn)
│ ↓
│ 7. conn.prepareStatement("SELECT...")
│ 8. bindArgs(ps, 123) → ps.setObject(1, 123)
│
↓
7. pscAction.doInPreparedStatement(ps)
│ ↓
│ 10. ps.executeQuery() → ResultSet
│ 11. rs.next() → 遍历结果集
│ 12. rowMapper.mapRow(rs, rowNum) → User对象
│
↓
8. 自动关闭ResultSet (try-with-resources)
9. 自动关闭PreparedStatement (try-with-resources)
10. conn.setAutoCommit(false) // 恢复原始设置
11. 自动关闭Connection (try-with-resources)
最后把JdbcTemplate类写到配置类即可
实现声明式事务
Spring提供的声明式事务管理能极大地降低应用程序的事务代码。如果使用基于Annotation配置的声明式事务,则一个与数据库操作相关的类只需加上@Transactional注解,就实现了事务支持,非常方便
定义@Transactional注解时,需要指定默认的拦截器名称,默认值platformTransactionManager(接口)表示用名字为platformTransactionManager的Bean来管理事务
我们现在要做的就是实现该接口以及 InvocationHandler接口进行事务拦截器的开发
java
public class DataSourceTransactionManager implements
PlatformTransactionManager, InvocationHandler
{
static final ThreadLocal<TransactionStatus> transactionStatus = new ThreadLocal<>();
final DataSource dataSource;
public DataSourceTransactionManager(DataSource dataSource) {
this.dataSource = dataSource;
}
}
在invoke函数内部编写事务开启,事务进行以及事务关闭的逻辑
java
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
TransactionStatus ts = transactionStatus.get();
if (ts == null) {
// 当前无事务,开启新事务:
try (Connection connection = dataSource.getConnection()) {
final boolean autoCommit = connection.getAutoCommit();
if (autoCommit) {
connection.setAutoCommit(false);
}
try {
// 设置ThreadLocal状态:
transactionStatus.set(new TransactionStatus(connection));
// 调用业务方法:
Object r = method.invoke(proxy, args);
// 提交事务:
connection.commit();
// 方法返回:
return r;
} catch (InvocationTargetException e) {
// 回滚事务:
TransactionException te = new TransactionException(e.getCause());
try {
connection.rollback();
} catch (SQLException sqle) {
te.addSuppressed(sqle);
}
throw te;
} finally {
// 删除ThreadLocal状态:
transactionStatus.remove();
if (autoCommit) {
connection.setAutoCommit(true);
}
}
}
} else {
// 当前已有事务,加入当前事务执行:
return method.invoke(proxy, args);
}
}
但是开启事务以后,如何让其他在当前事务的方法加入进来呢?
我们需要编写一个有关事务的工具类,获取当前事务的连接
csharp
public class TransactionalUtils {
@Nullable
public static Connection getCurrentConnection() {
TransactionStatus ts = DataSourceTransactionManager.transactionStatus.get();
return ts == null ? null : ts.connection;
}
}
改造当前Template加入事务的逻辑
java
public class JdbcTemplate {
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
// 尝试获取当前事务连接:
Connection current = TransactionalUtils.getCurrentConnection();
if (current != null) {
try {
return action.doInConnection(current);
} catch (SQLException e) {
throw new DataAccessException(e);
}
}
// 无事务,从DataSource获取新连接:
try (Connection newConn = dataSource.getConnection()) {
return action.doInConnection(newConn);
} catch (SQLException e) {
throw new DataAccessException(e);
}
}
...
}
最后把拦截器, TransactionalBeanPostProcessor,Template加入到配置类中,即可开启AOP管理