diff --git a/script/client/spring/application.properties b/script/client/spring/application.properties index 550b2aa86a6..e0fd9725866 100755 --- a/script/client/spring/application.properties +++ b/script/client/spring/application.properties @@ -15,6 +15,7 @@ # seata.enabled=true +seata.excludes-for-auto-proxying=firstClassNameForExclude,secondClassNameForExclude seata.application-id=applicationName seata.tx-service-group=my_test_tx_group seata.enable-auto-data-source-proxy=true diff --git a/script/client/spring/application.yml b/script/client/spring/application.yml index 0c4c4527b46..a08db613992 100755 --- a/script/client/spring/application.yml +++ b/script/client/spring/application.yml @@ -4,6 +4,7 @@ seata: tx-service-group: my_test_tx_group enable-auto-data-source-proxy: true use-jdk-proxy: false + excludes-for-auto-proxying: firstClassNameForExclude,secondClassNameForExclude client: rm: async-commit-buffer-limit: 1000 @@ -101,4 +102,4 @@ seata: session-timeout: 6000 connect-timeout: 2000 username: "" - password: "" \ No newline at end of file + password: "" diff --git a/seata-spring-boot-starter/src/main/java/io/seata/spring/boot/autoconfigure/SeataAutoConfiguration.java b/seata-spring-boot-starter/src/main/java/io/seata/spring/boot/autoconfigure/SeataAutoConfiguration.java index 9a2b416a0ab..6cc03d20133 100644 --- a/seata-spring-boot-starter/src/main/java/io/seata/spring/boot/autoconfigure/SeataAutoConfiguration.java +++ b/seata-spring-boot-starter/src/main/java/io/seata/spring/boot/autoconfigure/SeataAutoConfiguration.java @@ -16,7 +16,7 @@ package io.seata.spring.boot.autoconfigure; import io.seata.spring.annotation.GlobalTransactionScanner; -import io.seata.spring.annotation.datasource.SeataDataSourceBeanPostProcessor; +import io.seata.spring.annotation.datasource.SeataAutoDataSourceProxyCreator; import io.seata.spring.boot.autoconfigure.properties.SeataProperties; import io.seata.spring.boot.autoconfigure.provider.SpringApplicationContextProvider; import org.slf4j.Logger; @@ -30,7 +30,7 @@ import org.springframework.context.annotation.DependsOn; import static io.seata.common.Constants.BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER; -import static io.seata.spring.annotation.datasource.AutoDataSourceProxyRegistrar.BEAN_NAME_SEATA_DATA_SOURCE_BEAN_POST_PROCESSOR; +import static io.seata.spring.annotation.datasource.AutoDataSourceProxyRegistrar.BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR; /** * @author xingfudeshi@gmail.com @@ -58,10 +58,10 @@ public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataPr return new GlobalTransactionScanner(seataProperties.getApplicationId(), seataProperties.getTxServiceGroup()); } - @Bean(BEAN_NAME_SEATA_DATA_SOURCE_BEAN_POST_PROCESSOR) + @Bean(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR) @ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = {"enableAutoDataSourceProxy", "enable-auto-data-source-proxy"}, havingValue = "true", matchIfMissing = true) - @ConditionalOnMissingBean(SeataDataSourceBeanPostProcessor.class) - public SeataDataSourceBeanPostProcessor seataDataSourceBeanPostProcessor(SeataProperties seataProperties) { - return new SeataDataSourceBeanPostProcessor(seataProperties.isUseJdkProxy()); + @ConditionalOnMissingBean(SeataAutoDataSourceProxyCreator.class) + public SeataAutoDataSourceProxyCreator seataAutoDataSourceProxyCreator(SeataProperties seataProperties) { + return new SeataAutoDataSourceProxyCreator(seataProperties.isUseJdkProxy(),seataProperties.getExcludesForAutoProxying()); } } diff --git a/seata-spring-boot-starter/src/main/java/io/seata/spring/boot/autoconfigure/properties/SeataProperties.java b/seata-spring-boot-starter/src/main/java/io/seata/spring/boot/autoconfigure/properties/SeataProperties.java index fd7d2c30eda..f63ca6125b1 100644 --- a/seata-spring-boot-starter/src/main/java/io/seata/spring/boot/autoconfigure/properties/SeataProperties.java +++ b/seata-spring-boot-starter/src/main/java/io/seata/spring/boot/autoconfigure/properties/SeataProperties.java @@ -47,6 +47,10 @@ public class SeataProperties { * Whether use JDK proxy instead of CGLIB proxy */ private boolean useJdkProxy = false; + /** + * Specifies which datasource bean are not eligible for auto-proxying + */ + private String[] excludesForAutoProxying = {}; @Autowired private SpringCloudAlibabaConfiguration springCloudAlibabaConfiguration; @@ -101,4 +105,13 @@ public SeataProperties setUseJdkProxy(boolean useJdkProxy) { this.useJdkProxy = useJdkProxy; return this; } + + public String[] getExcludesForAutoProxying() { + return excludesForAutoProxying; + } + + public SeataProperties setExcludesForAutoProxying(String[] excludesForAutoProxying) { + this.excludesForAutoProxying = excludesForAutoProxying; + return this; + } } diff --git a/spring/src/main/java/io/seata/spring/annotation/datasource/AutoDataSourceProxyRegistrar.java b/spring/src/main/java/io/seata/spring/annotation/datasource/AutoDataSourceProxyRegistrar.java index 62a19cd5066..e86b02097c4 100644 --- a/spring/src/main/java/io/seata/spring/annotation/datasource/AutoDataSourceProxyRegistrar.java +++ b/spring/src/main/java/io/seata/spring/annotation/datasource/AutoDataSourceProxyRegistrar.java @@ -27,16 +27,20 @@ */ public class AutoDataSourceProxyRegistrar implements ImportBeanDefinitionRegistrar { private static final String ATTRIBUTE_KEY_USE_JDK_PROXY = "useJdkProxy"; - public static final String BEAN_NAME_SEATA_DATA_SOURCE_BEAN_POST_PROCESSOR = "seataDataSourceBeanPostProcessor"; + private static final String ATTRIBUTE_KEY_EXCLUDES = "excludes"; + public static final String BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR = "seataAutoDataSourceProxyCreator"; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { - if (!registry.containsBeanDefinition(BEAN_NAME_SEATA_DATA_SOURCE_BEAN_POST_PROCESSOR)) { - boolean useJdkProxy = Boolean.valueOf(importingClassMetadata.getAnnotationAttributes(EnableAutoDataSourceProxy.class.getName()).get(ATTRIBUTE_KEY_USE_JDK_PROXY).toString()); + if (!registry.containsBeanDefinition(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR)) { + boolean useJdkProxy = Boolean.parseBoolean(importingClassMetadata.getAnnotationAttributes(EnableAutoDataSourceProxy.class.getName()).get(ATTRIBUTE_KEY_USE_JDK_PROXY).toString()); + String[] excludes = (String[]) importingClassMetadata.getAnnotationAttributes(EnableAutoDataSourceProxy.class.getName()).get(ATTRIBUTE_KEY_EXCLUDES); AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder - .genericBeanDefinition(SeataDataSourceBeanPostProcessor.class) - .addConstructorArgValue(useJdkProxy).getBeanDefinition(); - registry.registerBeanDefinition(BEAN_NAME_SEATA_DATA_SOURCE_BEAN_POST_PROCESSOR, beanDefinition); + .genericBeanDefinition(SeataAutoDataSourceProxyCreator.class) + .addConstructorArgValue(useJdkProxy) + .addConstructorArgValue(excludes) + .getBeanDefinition(); + registry.registerBeanDefinition(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR, beanDefinition); } } diff --git a/spring/src/main/java/io/seata/spring/annotation/datasource/EnableAutoDataSourceProxy.java b/spring/src/main/java/io/seata/spring/annotation/datasource/EnableAutoDataSourceProxy.java index 61b14e7237f..280968fb38f 100644 --- a/spring/src/main/java/io/seata/spring/annotation/datasource/EnableAutoDataSourceProxy.java +++ b/spring/src/main/java/io/seata/spring/annotation/datasource/EnableAutoDataSourceProxy.java @@ -38,4 +38,11 @@ * @return useJdkProxy */ boolean useJdkProxy() default false; + + /** + * Specifies which datasource bean are not eligible for auto-proxying + * + * @return + */ + String[] excludes() default {}; } diff --git a/spring/src/main/java/io/seata/spring/annotation/datasource/SeataAutoDataSourceProxyAdvice.java b/spring/src/main/java/io/seata/spring/annotation/datasource/SeataAutoDataSourceProxyAdvice.java new file mode 100644 index 00000000000..3dcd9da4296 --- /dev/null +++ b/spring/src/main/java/io/seata/spring/annotation/datasource/SeataAutoDataSourceProxyAdvice.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.spring.annotation.datasource; + +import javax.sql.DataSource; +import java.lang.reflect.Method; + +import io.seata.rm.datasource.DataSourceProxy; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.IntroductionInfo; +import org.springframework.beans.BeanUtils; + +/** + * @author xingfudeshi@gmail.com + */ +public class SeataAutoDataSourceProxyAdvice implements MethodInterceptor, IntroductionInfo { + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + DataSourceProxy dataSourceProxy = DataSourceProxyHolder.get().putDataSource((DataSource) invocation.getThis()); + Method method = invocation.getMethod(); + Object[] args = invocation.getArguments(); + Method m = BeanUtils.findDeclaredMethod(DataSourceProxy.class, method.getName(), method.getParameterTypes()); + if (null != m) { + return m.invoke(dataSourceProxy, args); + } else { + return invocation.proceed(); + } + } + + @Override + public Class[] getInterfaces() { + return new Class[]{SeataProxy.class}; + } + +} diff --git a/spring/src/main/java/io/seata/spring/annotation/datasource/SeataAutoDataSourceProxyCreator.java b/spring/src/main/java/io/seata/spring/annotation/datasource/SeataAutoDataSourceProxyCreator.java new file mode 100644 index 00000000000..d30874dbbce --- /dev/null +++ b/spring/src/main/java/io/seata/spring/annotation/datasource/SeataAutoDataSourceProxyCreator.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.spring.annotation.datasource; + +import javax.sql.DataSource; +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.Advisor; +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator; +import org.springframework.aop.support.DefaultIntroductionAdvisor; +import org.springframework.beans.BeansException; + +/** + * @author xingfudeshi@gmail.com + */ +public class SeataAutoDataSourceProxyCreator extends AbstractAutoProxyCreator { + private static final Logger LOGGER = LoggerFactory.getLogger(SeataAutoDataSourceProxyCreator.class); + private final String[] excludes; + private final Advisor advisor = new DefaultIntroductionAdvisor(new SeataAutoDataSourceProxyAdvice()); + + public SeataAutoDataSourceProxyCreator(boolean useJdkProxy, String[] excludes) { + this.excludes = excludes; + setProxyTargetClass(!useJdkProxy); + } + + @Override + protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource customTargetSource) throws BeansException { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Auto proxy of [{}]", beanName); + } + return new Object[]{advisor}; + } + + @Override + protected boolean shouldSkip(Class beanClass, String beanName) { + return SeataProxy.class.isAssignableFrom(beanClass) || + !DataSource.class.isAssignableFrom(beanClass) || + Arrays.asList(excludes).contains(beanClass.getName()); + } +} diff --git a/spring/src/main/java/io/seata/spring/annotation/datasource/SeataDataSourceBeanPostProcessor.java b/spring/src/main/java/io/seata/spring/annotation/datasource/SeataDataSourceBeanPostProcessor.java deleted file mode 100644 index f2dfe1ce529..00000000000 --- a/spring/src/main/java/io/seata/spring/annotation/datasource/SeataDataSourceBeanPostProcessor.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 1999-2019 Seata.io Group. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.seata.spring.annotation.datasource; - -import javax.sql.DataSource; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; - -import io.seata.common.exception.ShouldNeverHappenException; -import io.seata.rm.datasource.DataSourceProxy; -import io.seata.spring.util.SpringProxyUtils; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.MethodInterceptor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; - -/** - * @author xingfudeshi@gmail.com - * The type seata data source bean post processor - */ -public class SeataDataSourceBeanPostProcessor implements BeanPostProcessor { - private static final Logger LOGGER = LoggerFactory.getLogger(SeataDataSourceBeanPostProcessor.class); - private final boolean useJdkProxy; - - public SeataDataSourceBeanPostProcessor(boolean useJdkProxy) { - this.useJdkProxy = useJdkProxy; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof DataSource && !(bean instanceof DataSourceProxy)) { - if (LOGGER.isInfoEnabled()) { - LOGGER.info("Auto proxy of [{}]", beanName); - } - return proxyDataSource(bean); - } - return bean; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) { - if (bean instanceof DataSourceProxy) { - throw new ShouldNeverHappenException("Auto proxy of DataSource can't be enabled as you've created a DataSourceProxy bean." + - "Please consider removing DataSourceProxy bean or disabling auto proxy of DataSource."); - } - return bean; - } - - /** - * proxy data source - * - * @param originBean - * @return proxied datasource - */ - private Object proxyDataSource(Object originBean) { - DataSourceProxy dataSourceProxy = DataSourceProxyHolder.get().putDataSource((DataSource) originBean); - if (this.useJdkProxy) { - return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), SpringProxyUtils.getAllInterfaces(originBean), (proxy, method, args) -> handleMethodProxy(dataSourceProxy, method, args, originBean)); - } else { - return Enhancer.create(originBean.getClass(), (MethodInterceptor) (proxy, method, args, methodProxy) -> handleMethodProxy(dataSourceProxy, method, args, originBean)); - } - - } - - /** - * handle method proxy - * - * @param dataSourceProxy - * @param method - * @param args - * @param originBean - * @return proxied datasource - * @throws InvocationTargetException - * @throws IllegalAccessException - */ - private Object handleMethodProxy(DataSourceProxy dataSourceProxy, Method method, Object[] args, Object originBean) throws InvocationTargetException, IllegalAccessException { - Method m = BeanUtils.findDeclaredMethod(DataSourceProxy.class, method.getName(), method.getParameterTypes()); - if (null != m) { - return m.invoke(dataSourceProxy, args); - } else { - boolean oldAccessible = method.isAccessible(); - try { - method.setAccessible(true); - return method.invoke(originBean, args); - } finally { - //recover the original accessible for security reason - method.setAccessible(oldAccessible); - } - } - } -} diff --git a/spring/src/main/java/io/seata/spring/annotation/datasource/SeataProxy.java b/spring/src/main/java/io/seata/spring/annotation/datasource/SeataProxy.java new file mode 100644 index 00000000000..39622d0f610 --- /dev/null +++ b/spring/src/main/java/io/seata/spring/annotation/datasource/SeataProxy.java @@ -0,0 +1,22 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.spring.annotation.datasource; + +/** + * @author xingfudeshi@gmail.com + */ +public interface SeataProxy { +}