SpringAOP源码分析之expose-proxy="true"

本文详细解析了Spring AOP框架中动态代理的工作机制,包括AspectJ自动代理的配置与实现过程,以及如何通过配置使动态代理对象可被方法内部访问。探讨了@Async与@Transactional注解下动态代理的不同行为,提供了多种自定义动态代理暴露逻辑的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

aspectj-autoproxy标签
注册AspectJAutoProxyBeanDefinitionParser

public class AopNamespaceHandler extends NamespaceHandlerSupport {
    public AopNamespaceHandler() {
    }

    public void init() {
        this.registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        this.registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        this.registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
        this.registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }

aspectj-autoproxy对应进入类AspectJAutoProxyBeanDefinitionParser

class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {
    AspectJAutoProxyBeanDefinitionParser() {
    }

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
        this.extendBeanDefinition(element, parserContext);
        return null;
    }

...

}

然后进入AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);

public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {
        BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));
        useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
        registerComponentIfNecessary(beanDefinition, parserContext);
    }

然后进入 AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary 方法中

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }

接着进入

    private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {
            BeanDefinition apcDefinition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) {
                    apcDefinition.setBeanClassName(cls.getName());
                }
            }

            return null;
        } else {
            RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
            beanDefinition.setSource(source);
            beanDefinition.getPropertyValues().add("order", -2147483648);
            beanDefinition.setRole(2);
            registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition);
            return beanDefinition;
        }
    }
registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition);这句是重点。

然后回到registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中,里面会进入到useClassProxyingIfNecessary

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) {
        if (sourceElement != null) {
            boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute("proxy-target-class"));
            if (proxyTargetClass) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }

            boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute("expose-proxy"));
            if (exposeProxy) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }

    }

然后进入到AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);

public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
        if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {
            BeanDefinition definition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
            definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
        }

    }

现在可以看到exposeProxy的值最终都被设置到了org.springframework.aop.config.internalAutoProxyCreator身上

然后看AOP最后创建的JDK动态代理JdkDynamicAopProxy的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object oldProxy = null;
        boolean setProxyContext = false;
        TargetSource targetSource = this.advised.targetSource;
        Class<?> targetClass = null;
        Object target = null;

        Integer var10;
        try {
            if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
                Boolean var20 = this.equals(args[0]);
                return var20;
            }

            if (this.hashCodeDefined || !AopUtils.isHashCodeMethod(method)) {
                if (method.getDeclaringClass() == DecoratingProxy.class) {
                    Class var18 = AopProxyUtils.ultimateTargetClass(this.advised);
                    return var18;
                }

                Object retVal;
                if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                    retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
                    return retVal;
                }

                if (this.advised.exposeProxy) {
                    oldProxy = AopContext.setCurrentProxy(proxy);
                    setProxyContext = true;
                }

                target = targetSource.getTarget();
                if (target != null) {
                    targetClass = target.getClass();
                }

                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
                if (chain.isEmpty()) {
                    Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                    retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
                } else {
                    MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                    retVal = invocation.proceed();
                }

                Class<?> returnType = method.getReturnType();
                if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                    retVal = proxy;
                } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                    throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
                }

                Object var13 = retVal;
                return var13;
            }

            var10 = this.hashCode();
        } finally {
            if (target != null && !targetSource.isStatic()) {
                targetSource.releaseTarget(target);
            }

            if (setProxyContext) {
                AopContext.setCurrentProxy(oldProxy);
            }

        }

        return var10;
    }

通过上面的动态代理执行源码的地方可以看到逻辑:

if (this.advised.exposeProxy) {
       oldProxy = AopContext.setCurrentProxy(proxy);
       setProxyContext = true;
}

而在ProxyConfig类中,有如下注释用来说明exposeProxy的作用,就是用于在方法中获取动态代理的对象的。

/**
 * Set whether the proxy should be exposed by the AOP framework as a
 * ThreadLocal for retrieval via the AopContext class. This is useful
 * if an advised object needs to call another advised method on itself.
 * (If it uses {@code this}, the invocation will not be advised).
 * <p>Default is "false", in order to avoid unnecessary extra interception.
 * This means that no guarantees are provided that AopContext access will
 * work consistently within any method of the advised object.
 */
public void setExposeProxy(boolean exposeProxy) {
	this.exposeProxy = exposeProxy;
}

即只有exposeProxy为true时,才会把proxy动态代理对象设置到AopContext上下文中。

在xml时代,我们可以通过配置:

<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>  

来修改全局的暴露逻辑。

在基于注解的配置中,我们需要使用

@EnableAspectJAutoProxy(proxyTargteClass = true, exposeProxy = true)

来配置。

遗憾的是,对于@Async,如此配置下依然不能生效。因为@Async使用的不是AspectJ的自动代理,而是使用代码中固定的创建代理方式进行代理创建的。

如果是@Transactional事务注解的话, 则是生效的。具体生效机制是通过@EnableTransactionManagement注解中的TransactionManagementConfigurationSelector类声明,其中声明导入了AutoProxyRegistrar类,该类获取注解中proxy相关注解配置,并根据配置情况,在BeanDefinition中注册一个可用于自动生成代理对象的AutoProxyCreator:

AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
	return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}

而在@EnableAspectJAutoProxy注解中,@Import的AspectJAutoProxyRegistrar类又把这个BeanDefinition修改了类,同时修改了其中的exposeProxy属性。

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
	return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
	return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

后面替换掉了前面的AutoProxyCreator,替换逻辑是使用优先级替换,优先级分别为:

APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);

这个逻辑都在registerOrEscalateApcAsRequired中,读者可以自己再看一下。

因为@Transactional注解和AspectJ相关注解的生成动态代理类都是使用的同一个Bean即上面的AutoProxyCreator处理的,该bean的name是org.springframework.aop.config.internalAutoProxyCreator,他们公用相同的属性,故对于@Transactional来说,@EnableAspectJAutoProxy的属性exposeProxy=true也是生效的。但是@Async的注解生成的代理类并不是通过这个autoProxyCreator来生成的,故不能享受到上面的配置。

  1. 基于上面的源码,我们可以得到第三种处理方法

在某个切入时机,手动执行AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);静态方法,当然前提是有一个BeanDefinitionRegistry,且时机要在BeanDefinition已经创建且动态代理对象还没有生成时调用。

使用这种方式,无需使用@EnableAspectJAutoProxy即可。

这种方式同样不适用于@Async,适用于@Transactional。

  1. 手动修改各种BeanPostProcessor的属性

以@Async为例,其通过AsyncAnnotationBeanPostProcessor来生成动态代理类,我们只要在合适时机即该BPP已创建,但是还未被使用时,修改其中的exposeProxy属性,使用AsyncAnnotationBeanPostProcessor.setExposeProxy(true)即可。

这种方式要针对性的设置特定的bean的exposeProxy属性true。适用于@Async,观察原理可以知道3和4其实核心都是相同的,就是设置AutoProxyCreater的exposed属性为true。AsyncAnnotationBeanPostProcessor其实也是一个AutoProxyCreater,他是ProxyProcessorSupport的子类。

对于@Async可以使用1、4方式,对于@Transactional则可以使用这四种任意方式。

 

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:task="http://www.springframework.org/schema/task" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd " > <context:property-placeholder location="classpath:application.properties"></context:property-placeholder> <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan" value="com.aiearth.drone.dao,com.aiearth.pojo" /> <property name="hibernateProperties"> <props> <!-- <prop key="hibernate.hbm2ddl.auto"> update </prop> --> <prop key="hibernate.dialect"> <!-- org.hibernate.spatial.dialect.postgis.PostgisDialect--> org.hibernate.spatial.dialect.postgis.PostgisPG10Dialect <!-- org.hibernate.dialect.PostgreSQLDialect--> </prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.format_sql">false</prop> <prop key="hibernate.use_sql_comments">false</prop> <prop key="hibernate.temp.use_jdbc_metadata_defaults">false</prop> <prop key="hibernate.query.escape_like_parameter">false</prop> <prop key="hibernate.query.sql.escape-identifier">false</prop> <prop key="hibernate.logger.org.hibernate.mapping">DEBUG</prop> </props> </property> </bean> <bean id="dataSource" class="com.aiearth.drone.security.DataSource.UmspscDataSource"> <property name="driverClassName" value="org.postgresql.Driver"/> <property name="jdbcUrl" value="${drone.jdbc.url}?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&stringtype=unspecified"/> <property name="username" value="${drone.jdbc.username}"/> <property name="password" value="${drone.jdbc.password}"/> <property name="connectionTimeout" value="30000"/> <property name="idleTimeout" value="180000"/> <property name="maximumPoolSize" value="30"/> <property name="minimumIdle" value="10"/> <property name="maxLifetime" value="1200000"/> <property name="connectionTestQuery" value="SELECT 1"/> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager" primary="true"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="get*" propagation="REQUIRED" /> <tx:method name="count*" propagation="REQUIRED" read-only="true" /> <tx:method name="find*" propagation="REQUIRED" read-only="true" /> <tx:method name="list*" propagation="REQUIRED" read-only="true" /> <tx:method name="*" /> </tx:attributes> </tx:advice> <aop:config expose-proxy="true"> <!-- 只对业务逻辑层实施事务 --> <aop:pointcut id="txPointcut" expression="execution(* com.aiearth..*.service..*Service.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" /> </aop:config> </beans>这样配对吗,如果不对请给我完整的修改后配置
最新发布
07-31
### 配置分析与修正建议 #### 1. 数据源配置(`dataSource`) 当前配置使用了自定义的 `UmspscDataSource`,该类可能基于 HikariCP 或其他连接池实现。配置中设置了 JDBC URL、用户名、密码、连接池参数等,并输出了日志信息,有助于排查连接问题。然而,若未正确继承或实现标准的 `DataSource` 接口,可能导致 Spring 无法识别或管理连接池,从而影响事务行为。 **建议修正:** - 如果使用的是 HikariCP,应直接使用 `HikariDataSource` 并确保引入了相关依赖。 - 若 `UmspscDataSource` 是自定义实现,应确保其正确实现了 `DataSource` 接口,并能被 Spring 管理。 ```java @Bean(name = "dataSource") public DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(env.getProperty("drone.jdbc.url") + "?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&stringtype=unspecified"); dataSource.setUsername(env.getProperty("drone.jdbc.username")); dataSource.setPassword(env.getProperty("drone.jdbc.password")); dataSource.setMaximumPoolSize(30); dataSource.setMinimumIdle(10); dataSource.setIdleTimeout(180000); dataSource.setMaxLifetime(1200000); dataSource.setConnectionTestQuery("SELECT 1"); return dataSource; } ``` #### 2. 实体管理工厂(`entityManagerFactory`) 当前配置使用了 `LocalContainerEntityManagerFactoryBean`,并设置了 JPA 供应商适配器为 `HibernateJpaVendorAdapter`。配置中指定了实体类扫描路径、数据源、JPA 属性等。但存在以下问题: - 使用了 `org.hibernate.dialect.HSQLDialect`,而实际数据库为 PostgreSQL,应使用 `org.hibernate.spatial.dialect.postgis.PostgisPG10Dialect`。 - `hibernate.query.sql.escape-identifier` 设置为 `false`,但未确保 Hibernate 版本支持此配置,可能导致未生效。 **建议修正:** - 确保使用正确的方言。 - 若使用 Hibernate 6.2 或以上版本,可尝试启用 `hibernate.query.escape_like_parameter`。 ```java Properties properties = new Properties(); properties.setProperty("hibernate.dialect", "org.hibernate.spatial.dialect.postgis.PostgisPG10Dialect"); properties.setProperty("hibernate.show_sql", "true"); properties.setProperty("hibernate.query.escape_like_parameter", "false"); em.setJpaProperties(properties); ``` #### 3. SessionFactory 配置 当前配置使用了 `LocalSessionFactoryBean`,并设置了数据源、扫描路径及 Hibernate 属性。但未启用 JPA 事务管理器,可能导致事务行为不一致。 **建议修正:** - 若使用 JPA,应优先使用 `JpaTransactionManager` 而非 `HibernateTransactionManager`。 - 若需同时使用 Hibernate 和 JPA,应配置多个事务管理器,并通过 `@Transactional` 注解指定使用哪一个。 ```java @Bean(name = "transactionManager") @Primary public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { return new JpaTransactionManager(entityManagerFactory); } ``` #### 4. 事务管理器(`transactionManager`) 当前配置使用了 `HibernateTransactionManager`,并绑定到 `sessionFactory`。但在 JPA 环境中,应使用 `JpaTransactionManager` 以确保事务一致性。 **建议修正:** - 若使用 JPA,应替换为 `JpaTransactionManager`。 - 若需兼容 Hibernate,可保留 `HibernateTransactionManager`,但需确保所有 DAO 使用 Hibernate API。 ```java @Bean(name = "transactionManager") @Primary public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { return new JpaTransactionManager(entityManagerFactory); } ``` #### 5. 事务拦截器(`txAdvice`)与 AOP 配置 当前配置使用了 `TransactionInterceptor` 和 `DefaultPointcutAdvisor` 来定义事务规则。但未明确指定事务管理器,可能导致事务未正确应用。 **建议修正:** - 确保 `txAdvice` 中使用的 `transactionManager` 与主事务管理器一致。 - 可简化为使用 `@EnableTransactionManagement` 注解自动配置。 ```java @Configuration @EnableTransactionManagement public class HibernatePostgisConfig { // 其他配置不变 } ``` --- ### 总结 当前配置中存在多个潜在问题,包括数据源实现、Hibernate 方言、事务管理器类型等。建议根据上述修正方案调整配置,以确保 Hibernate、数据源和事务管理正常工作。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值