From 374a62eb5063a3dd5e6dcaeafdbe402366a65909 Mon Sep 17 00:00:00 2001 From: Kazuki Shimizu Date: Sun, 21 Jun 2020 14:05:05 +0900 Subject: [PATCH] Support scoped proxy on mapper scan feature Fixes gh-476 --- .../mybatis/spring/annotation/MapperScan.java | 18 +++- .../annotation/MapperScannerRegistrar.java | 10 ++- .../MapperScannerBeanDefinitionParser.java | 12 +-- .../spring/mapper/ClassPathMapperScanner.java | 65 ++++++++++++-- .../mapper/MapperScannerConfigurer.java | 21 +++++ .../mybatis/spring/config/mybatis-spring.xsd | 11 ++- .../spring/annotation/MapperScanTest.java | 50 ++++++++++- .../annotation/mapper/ds1/Ds1Mapper.java | 5 +- .../annotation/mapper/ds2/Ds2Mapper.java | 8 +- .../mybatis/spring/config/NamespaceTest.java | 58 +++++++++++-- .../spring/config/default-scope.properties | 17 ++++ .../mybatis/spring/config/default-scope.xml | 39 +++++++++ .../spring/mapper/AnnotatedMapper.java | 7 +- .../mapper/MapperScannerConfigurerTest.java | 87 ++++++++++++++++++- .../spring/mapper/ScopedProxyMapper.java | 28 ++++++ 15 files changed, 401 insertions(+), 35 deletions(-) create mode 100644 src/test/java/org/mybatis/spring/config/default-scope.properties create mode 100644 src/test/java/org/mybatis/spring/config/default-scope.xml create mode 100644 src/test/java/org/mybatis/spring/mapper/ScopedProxyMapper.java diff --git a/src/main/java/org/mybatis/spring/annotation/MapperScan.java b/src/main/java/org/mybatis/spring/annotation/MapperScan.java index 2d8e7d1c3e..ebeed88007 100644 --- a/src/main/java/org/mybatis/spring/annotation/MapperScan.java +++ b/src/main/java/org/mybatis/spring/annotation/MapperScan.java @@ -25,13 +25,14 @@ import org.mybatis.spring.mapper.MapperFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.context.annotation.Import; /** * Use this annotation to register MyBatis mapper interfaces when using Java Config. It performs when same work as * {@link MapperScannerConfigurer} via {@link MapperScannerRegistrar}. - * + * *

* Either {@link #basePackageClasses} or {@link #basePackages} (or its alias {@link #value}) may be specified to define * specific packages to scan. Since 2.0.4, If specific packages are not defined, scanning will occur from the package of @@ -40,7 +41,7 @@ *

* Configuration example: *

- * + * *
  * @Configuration
  * @MapperScan("org.mybatis.spring.sample.mapper")
@@ -165,10 +166,21 @@
    * 

* Default is {@code false}. *

- * + * * @return set {@code true} to enable lazy initialization * @since 2.0.2 */ String lazyInitialization() default ""; + /** + * Specifies the default scope of scanned mappers. + * + *

+ * Default is {@code ""} (equiv to singleton). + *

+ * + * @return the default scope + */ + String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT; + } diff --git a/src/main/java/org/mybatis/spring/annotation/MapperScannerRegistrar.java b/src/main/java/org/mybatis/spring/annotation/MapperScannerRegistrar.java index aaf9ceb8d0..6c6d925246 100644 --- a/src/main/java/org/mybatis/spring/annotation/MapperScannerRegistrar.java +++ b/src/main/java/org/mybatis/spring/annotation/MapperScannerRegistrar.java @@ -25,6 +25,7 @@ import org.mybatis.spring.mapper.MapperFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; @@ -53,7 +54,7 @@ public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, Re /** * {@inheritDoc} - * + * * @deprecated Since 2.0.2, this method not used never. */ @Override @@ -130,6 +131,11 @@ void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes a builder.addPropertyValue("lazyInitialization", lazyInitialization); } + String defaultScope = annoAttrs.getString("defaultScope"); + if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) { + builder.addPropertyValue("defaultScope", defaultScope); + } + builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); @@ -146,7 +152,7 @@ private static String getDefaultBasePackage(AnnotationMetadata importingClassMet /** * A {@link MapperScannerRegistrar} for {@link MapperScans}. - * + * * @since 2.0.0 */ static class RepeatingRegistrar extends MapperScannerRegistrar { diff --git a/src/main/java/org/mybatis/spring/config/MapperScannerBeanDefinitionParser.java b/src/main/java/org/mybatis/spring/config/MapperScannerBeanDefinitionParser.java index ad775e21c2..004afb67f9 100644 --- a/src/main/java/org/mybatis/spring/config/MapperScannerBeanDefinitionParser.java +++ b/src/main/java/org/mybatis/spring/config/MapperScannerBeanDefinitionParser.java @@ -1,5 +1,5 @@ /** - * Copyright 2010-2019 the original author or authors. + * Copyright 2010-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ import java.lang.annotation.Annotation; -import org.mybatis.spring.mapper.MapperFactoryBean; import org.mybatis.spring.mapper.ClassPathMapperScanner; +import org.mybatis.spring.mapper.MapperFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.support.AbstractBeanDefinition; @@ -33,7 +33,7 @@ /** * A {#code BeanDefinitionParser} that handles the element scan of the MyBatis. namespace - * + * * @author Lishu Luo * @author Eduardo Macarron * @@ -53,10 +53,11 @@ public class MapperScannerBeanDefinitionParser extends AbstractBeanDefinitionPar private static final String ATTRIBUTE_FACTORY_REF = "factory-ref"; private static final String ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS = "mapper-factory-bean-class"; private static final String ATTRIBUTE_LAZY_INITIALIZATION = "lazy-initialization"; + private static final String ATTRIBUTE_DEFAULT_SCOPE = "default-scope"; /** * {@inheritDoc} - * + * * @since 2.0.2 */ @Override @@ -100,6 +101,7 @@ protected AbstractBeanDefinition parseInternal(Element element, ParserContext pa builder.addPropertyValue("sqlSessionTemplateBeanName", element.getAttribute(ATTRIBUTE_TEMPLATE_REF)); builder.addPropertyValue("sqlSessionFactoryBeanName", element.getAttribute(ATTRIBUTE_FACTORY_REF)); builder.addPropertyValue("lazyInitialization", element.getAttribute(ATTRIBUTE_LAZY_INITIALIZATION)); + builder.addPropertyValue("defaultScope", element.getAttribute(ATTRIBUTE_DEFAULT_SCOPE)); builder.addPropertyValue("basePackage", element.getAttribute(ATTRIBUTE_BASE_PACKAGE)); return builder.getBeanDefinition(); @@ -107,7 +109,7 @@ protected AbstractBeanDefinition parseInternal(Element element, ParserContext pa /** * {@inheritDoc} - * + * * @since 2.0.2 */ @Override diff --git a/src/main/java/org/mybatis/spring/mapper/ClassPathMapperScanner.java b/src/main/java/org/mybatis/spring/mapper/ClassPathMapperScanner.java index 05ba5a0bef..983b36ae20 100644 --- a/src/main/java/org/mybatis/spring/mapper/ClassPathMapperScanner.java +++ b/src/main/java/org/mybatis/spring/mapper/ClassPathMapperScanner.java @@ -1,5 +1,5 @@ /** - * Copyright 2010-2019 the original author or authors. + * Copyright 2010-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,26 +15,30 @@ */ package org.mybatis.spring.mapper; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Optional; +import java.util.Set; + import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.logging.Logger; import org.mybatis.logging.LoggerFactory; import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.aop.scope.ScopedProxyFactoryBean; +import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.util.StringUtils; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Set; - /** * A {@link ClassPathBeanDefinitionScanner} that registers Mappers by {@code basePackage}, {@code annotationClass}, or * {@code markerInterface}. If an {@code annotationClass} and/or {@code markerInterface} is specified, only the @@ -45,7 +49,7 @@ * * @author Hunter Presnall * @author Eduardo Macarron - * + * * @see MapperFactoryBean * @since 1.2.0 */ @@ -71,6 +75,8 @@ public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { private Class mapperFactoryBeanClass = MapperFactoryBean.class; + private String defaultScope; + public ClassPathMapperScanner(BeanDefinitionRegistry registry) { super(registry, false); } @@ -136,6 +142,20 @@ public void setMapperFactoryBeanClass(Class mapperF this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass; } + /** + * Set the default scope of scanned mappers. + *

+ * Default is {@code null} (equiv to singleton). + *

+ * + * @param defaultScope + * the scope + * @since 2.0.6 + */ + public void setDefaultScope(String defaultScope) { + this.defaultScope = defaultScope; + } + /** * Configures parent scanner to search for the right interfaces. It can search for all interfaces or just for those * that extends a markerInterface or/and those annotated with the annotationClass @@ -191,9 +211,18 @@ public Set doScan(String... basePackages) { } private void processBeanDefinitions(Set beanDefinitions) { - GenericBeanDefinition definition; + AbstractBeanDefinition definition; + BeanDefinitionRegistry registry = getRegistry(); for (BeanDefinitionHolder holder : beanDefinitions) { - definition = (GenericBeanDefinition) holder.getBeanDefinition(); + definition = (AbstractBeanDefinition) holder.getBeanDefinition(); + boolean scopedProxy = false; + if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) { + definition = (AbstractBeanDefinition) Optional + .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition()) + .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException( + "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]")); + scopedProxy = true; + } String beanClassName = definition.getBeanClassName(); LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface"); @@ -236,7 +265,25 @@ private void processBeanDefinitions(Set beanDefinitions) { LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } + definition.setLazyInit(lazyInitialization); + + if (scopedProxy) { + continue; + } + + if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) { + definition.setScope(defaultScope); + } + + if (!definition.isSingleton()) { + BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true); + if (registry.containsBeanDefinition(proxyHolder.getBeanName())) { + registry.removeBeanDefinition(proxyHolder.getBeanName()); + } + registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition()); + } + } } diff --git a/src/main/java/org/mybatis/spring/mapper/MapperScannerConfigurer.java b/src/main/java/org/mybatis/spring/mapper/MapperScannerConfigurer.java index 1369856dad..578db89da0 100644 --- a/src/main/java/org/mybatis/spring/mapper/MapperScannerConfigurer.java +++ b/src/main/java/org/mybatis/spring/mapper/MapperScannerConfigurer.java @@ -119,6 +119,8 @@ public class MapperScannerConfigurer private BeanNameGenerator nameGenerator; + private String defaultScope; + /** * This property lets you set the base package for your mapper interface files. *

@@ -311,6 +313,20 @@ public void setNameGenerator(BeanNameGenerator nameGenerator) { this.nameGenerator = nameGenerator; } + /** + * Sets the default scope of scanned mappers. + *

+ * Default is {@code null} (equiv to singleton). + *

+ * + * @param defaultScope + * the default scope + * @since 2.0.6 + */ + public void setDefaultScope(String defaultScope) { + this.defaultScope = defaultScope; + } + /** * {@inheritDoc} */ @@ -352,6 +368,9 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (StringUtils.hasText(lazyInitialization)) { scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); } + if (StringUtils.hasText(defaultScope)) { + scanner.setDefaultScope(defaultScope); + } scanner.registerFilters(); scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); @@ -387,6 +406,7 @@ private void processPropertyPlaceHolders() { this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values); this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values); this.lazyInitialization = updatePropertyValue("lazyInitialization", values); + this.defaultScope = updatePropertyValue("defaultScope", values); } this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null); this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName) @@ -395,6 +415,7 @@ private void processPropertyPlaceHolders() { .map(getEnvironment()::resolvePlaceholders).orElse(null); this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders) .orElse(null); + this.defaultScope = Optional.ofNullable(this.defaultScope).map(getEnvironment()::resolvePlaceholders).orElse(null); } private Environment getEnvironment() { diff --git a/src/main/resources/org/mybatis/spring/config/mybatis-spring.xsd b/src/main/resources/org/mybatis/spring/config/mybatis-spring.xsd index fcd5843065..a15e5f7cc7 100644 --- a/src/main/resources/org/mybatis/spring/config/mybatis-spring.xsd +++ b/src/main/resources/org/mybatis/spring/config/mybatis-spring.xsd @@ -1,7 +1,7 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/mybatis/spring/mapper/AnnotatedMapper.java b/src/test/java/org/mybatis/spring/mapper/AnnotatedMapper.java index b823b19734..03aedce06b 100644 --- a/src/test/java/org/mybatis/spring/mapper/AnnotatedMapper.java +++ b/src/test/java/org/mybatis/spring/mapper/AnnotatedMapper.java @@ -1,5 +1,5 @@ /** - * Copyright 2010-2019 the original author or authors. + * Copyright 2010-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,4 +22,9 @@ @Component public interface AnnotatedMapper { void method(); + + default String test() { + return Thread.currentThread().getName(); + } + } diff --git a/src/test/java/org/mybatis/spring/mapper/MapperScannerConfigurerTest.java b/src/test/java/org/mybatis/spring/mapper/MapperScannerConfigurerTest.java index 393cd4c1f5..05e453aa2e 100644 --- a/src/test/java/org/mybatis/spring/mapper/MapperScannerConfigurerTest.java +++ b/src/test/java/org/mybatis/spring/mapper/MapperScannerConfigurerTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2010-2019 the original author or authors. + * Copyright 2010-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import com.mockrunner.mock.jdbc.MockDataSource; + +import java.util.List; import java.util.Properties; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSessionFactory; import org.junit.jupiter.api.AfterEach; @@ -39,10 +45,9 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.context.support.SimpleThreadScope; import org.springframework.stereotype.Component; -import com.mockrunner.mock.jdbc.MockDataSource; - class MapperScannerConfigurerTest { private GenericApplicationContext applicationContext; @@ -57,6 +62,7 @@ void setupContext() { definition.setBeanClass(MapperScannerConfigurer.class); definition.getPropertyValues().add("basePackage", "org.mybatis.spring.mapper"); applicationContext.registerBeanDefinition("mapperScanner", definition); + applicationContext.getBeanFactory().registerScope("thread", new SimpleThreadScope()); setupSqlSessionFactory("sqlSessionFactory"); @@ -99,6 +105,12 @@ void testInterfaceScan() { applicationContext.getBean("mapperSubinterface"); applicationContext.getBean("mapperChildInterface"); applicationContext.getBean("annotatedMapper"); + applicationContext.getBean("scopedProxyMapper"); + applicationContext.getBean("scopedTarget.scopedProxyMapper"); + + assertThat(Stream.of(applicationContext.getBeanDefinitionNames()).filter(x -> x.startsWith("scopedTarget"))) + .hasSize(1); + } @Test @@ -164,6 +176,75 @@ void testMarkerInterfaceAndAnnotationScan() { assertBeanNotLoaded("mapperInterface"); } + @Test + void testScopedProxyMapperScan() { + applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("annotationClass", Mapper.class); + + startContext(); + { + BeanDefinition definition = applicationContext.getBeanDefinition("scopedProxyMapper"); + assertThat(definition.getBeanClassName()).isEqualTo("org.springframework.aop.scope.ScopedProxyFactoryBean"); + assertThat(definition.getScope()).isEqualTo(""); + } + { + BeanDefinition definition = applicationContext.getBeanDefinition("scopedTarget.scopedProxyMapper"); + assertThat(definition.getBeanClassName()).isEqualTo("org.mybatis.spring.mapper.MapperFactoryBean"); + assertThat(definition.getScope()).isEqualTo("thread"); + } + { + ScopedProxyMapper mapper = applicationContext.getBean(ScopedProxyMapper.class); + assertThat(mapper.test()).isEqualTo("test"); + } + { + ScopedProxyMapper mapper = applicationContext.getBean("scopedTarget.scopedProxyMapper", ScopedProxyMapper.class); + assertThat(mapper.test()).isEqualTo("test"); + } + { + ScopedProxyMapper mapper = applicationContext.getBean("scopedProxyMapper", ScopedProxyMapper.class); + assertThat(mapper.test()).isEqualTo("test"); + } + + SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class); + assertEquals(1, sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers().size()); + } + + @Test + void testScopedProxyMapperScanByDefault() { + applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("defaultScope", "thread"); + + startContext(); + + List scopedProxyTargetBeans = Stream.of(applicationContext.getBeanDefinitionNames()) + .filter(x -> x.startsWith("scopedTarget")).collect(Collectors.toList()); + assertThat(scopedProxyTargetBeans).hasSize(6).contains("scopedTarget.scopedProxyMapper", + "scopedTarget.annotatedMapper", "scopedTarget.annotatedMapperZeroMethods", "scopedTarget.mapperInterface", + "scopedTarget.mapperSubinterface", "scopedTarget.mapperChildInterface"); + + for (String scopedProxyTargetBean : scopedProxyTargetBeans) { + { + BeanDefinition definition = applicationContext.getBeanDefinition(scopedProxyTargetBean); + assertThat(definition.getBeanClassName()).isEqualTo("org.mybatis.spring.mapper.MapperFactoryBean"); + assertThat(definition.getScope()).isEqualTo("thread"); + } + { + BeanDefinition definition = applicationContext.getBeanDefinition(scopedProxyTargetBean.substring(13)); + assertThat(definition.getBeanClassName()).isEqualTo("org.springframework.aop.scope.ScopedProxyFactoryBean"); + assertThat(definition.getScope()).isEqualTo(""); + } + } + { + ScopedProxyMapper mapper = applicationContext.getBean(ScopedProxyMapper.class); + assertThat(mapper.test()).isEqualTo("test"); + } + { + AnnotatedMapper mapper = applicationContext.getBean(AnnotatedMapper.class); + assertThat(mapper.test()).isEqualTo("main"); + } + + SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class); + assertEquals(2, sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers().size()); + } + @Test void testScanWithExplicitSqlSessionFactory() { setupSqlSessionFactory("sqlSessionFactory2"); diff --git a/src/test/java/org/mybatis/spring/mapper/ScopedProxyMapper.java b/src/test/java/org/mybatis/spring/mapper/ScopedProxyMapper.java new file mode 100644 index 0000000000..8330d4143f --- /dev/null +++ b/src/test/java/org/mybatis/spring/mapper/ScopedProxyMapper.java @@ -0,0 +1,28 @@ +/** + * Copyright 2010-2020 the original author or authors. + * + * 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 org.mybatis.spring.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; + +@Mapper +@Scope(scopeName = "thread", proxyMode = ScopedProxyMode.TARGET_CLASS) +public interface ScopedProxyMapper { + default String test() { + return "test"; + } +}