手写Spring(2):实现AOP与JdbcTemplate

实现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:

  1. 原始Bean,即需要被代理的Bean;
  2. 拦截器,即拦截了目标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管理

相关推荐
云烟成雨TD1 天前
Spring AI Alibaba 1.x 系列【6】ReactAgent 同步执行 & 流式执行
java·人工智能·spring
Java成神之路-1 天前
SpringMVC 响应实战指南:页面、文本、JSON 返回全流程(Spring系列13)
java·spring·json
砍材农夫1 天前
spring-ai 第六模型介绍-聊天模型
java·人工智能·spring
云烟成雨TD1 天前
Spring AI Alibaba 1.x 系列【5】ReactAgent 构建器深度源码解析
java·人工智能·spring
Flittly1 天前
【SpringAIAlibaba新手村系列】(15)MCP Client 调用本地服务
java·笔记·spring·ai·springboot
Flittly1 天前
【SpringAIAlibaba新手村系列】(14)MCP 本地服务与工具集成
java·spring boot·笔记·spring·ai
mfxcyh1 天前
基于xml、注解、JavaConfig实现spring的ioc
xml·java·spring
Flittly1 天前
【SpringAIAlibaba新手村系列】(13)Tool Calling 函数工具调用技术
java·spring boot·spring·ai
xdscode1 天前
Spring 依赖注入方式全景解析
java·后端·spring