mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:39:29 +00:00
Introduce @Proxyable annotation for bean-specific proxy type
Closes gh-35296 See gh-35293
This commit is contained in:
+1
-1
@@ -464,7 +464,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
|
||||
proxyFactory.addInterface(ifc);
|
||||
}
|
||||
}
|
||||
else if (!proxyFactory.isProxyTargetClass()) {
|
||||
if (ifcs != null ? ifcs.length == 0 : !proxyFactory.isProxyTargetClass()) {
|
||||
evaluateProxyInterfaces(beanClass, proxyFactory);
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -22,6 +22,7 @@ import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
@@ -258,6 +259,20 @@ public abstract class AnnotationConfigUtils {
|
||||
if (description != null) {
|
||||
abd.setDescription(description.getString("value"));
|
||||
}
|
||||
|
||||
AnnotationAttributes proxyable = attributesFor(metadata, Proxyable.class);
|
||||
if (proxyable != null) {
|
||||
ProxyType mode = proxyable.getEnum("value");
|
||||
if (mode == ProxyType.TARGET_CLASS) {
|
||||
abd.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
|
||||
}
|
||||
else {
|
||||
Class<?>[] ifcs = proxyable.getClassArray("interfaces");
|
||||
if (ifcs.length > 0 || mode == ProxyType.INTERFACES) {
|
||||
abd.setAttribute(AutoProxyUtils.EXPOSED_INTERFACES_ATTRIBUTE, ifcs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static BeanDefinitionHolder applyScopedProxyMode(
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
@@ -43,7 +44,7 @@ import org.springframework.util.Assert;
|
||||
* @author Mark Fisher
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
@Service @Lazy @DependsOn("myNamedComponent")
|
||||
@Service @Primary @Lazy @DependsOn("myNamedComponent")
|
||||
public abstract class FooServiceImpl implements FooService {
|
||||
|
||||
// Just to test ASM5's bytecode parsing of INVOKESPECIAL/STATIC on interfaces
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2002-present 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
|
||||
*
|
||||
* https://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 example.scannable;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.springframework.context.annotation.Proxyable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
@Service @Proxyable(interfaces = FooService.class)
|
||||
public class OtherFooService implements FooService {
|
||||
|
||||
@Override
|
||||
public String foo(int id) {
|
||||
return "" + id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<String> asyncFoo(int id) {
|
||||
return CompletableFuture.completedFuture("" + id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitCalled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+19
-12
@@ -41,6 +41,7 @@ import example.scannable.JakartaNamedComponent;
|
||||
import example.scannable.MessageBean;
|
||||
import example.scannable.NamedComponent;
|
||||
import example.scannable.NamedStubDao;
|
||||
import example.scannable.OtherFooService;
|
||||
import example.scannable.ScopedProxyTestBean;
|
||||
import example.scannable.ServiceInvocationCounter;
|
||||
import example.scannable.StubFooDao;
|
||||
@@ -85,13 +86,13 @@ class ClassPathScanningCandidateComponentProviderTests {
|
||||
|
||||
private static final Set<Class<?>> springComponents = Set.of(
|
||||
DefaultNamedComponent.class,
|
||||
NamedComponent.class,
|
||||
FooServiceImpl.class,
|
||||
StubFooDao.class,
|
||||
NamedComponent.class,
|
||||
NamedStubDao.class,
|
||||
OtherFooService.class,
|
||||
ServiceInvocationCounter.class,
|
||||
BarComponent.class
|
||||
);
|
||||
StubFooDao.class,
|
||||
BarComponent.class);
|
||||
|
||||
|
||||
@Test
|
||||
@@ -213,7 +214,8 @@ class ClassPathScanningCandidateComponentProviderTests {
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
assertScannedBeanDefinitions(candidates);
|
||||
// Interfaces/Abstract class are filtered out automatically.
|
||||
assertBeanTypes(candidates, AutowiredQualifierFooService.class, FooServiceImpl.class, ScopedProxyTestBean.class);
|
||||
assertBeanTypes(candidates,
|
||||
AutowiredQualifierFooService.class, FooServiceImpl.class, OtherFooService.class, ScopedProxyTestBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -237,7 +239,8 @@ class ClassPathScanningCandidateComponentProviderTests {
|
||||
provider.addExcludeFilter(new AnnotationTypeFilter(Repository.class));
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
assertScannedBeanDefinitions(candidates);
|
||||
assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class);
|
||||
assertBeanTypes(candidates,
|
||||
NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -282,7 +285,8 @@ class ClassPathScanningCandidateComponentProviderTests {
|
||||
private void testExclude(ClassPathScanningCandidateComponentProvider provider) {
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
assertScannedBeanDefinitions(candidates);
|
||||
assertBeanTypes(candidates, FooServiceImpl.class, StubFooDao.class, ServiceInvocationCounter.class,
|
||||
assertBeanTypes(candidates,
|
||||
FooServiceImpl.class, OtherFooService.class, ServiceInvocationCounter.class, StubFooDao.class,
|
||||
BarComponent.class);
|
||||
}
|
||||
|
||||
@@ -301,7 +305,8 @@ class ClassPathScanningCandidateComponentProviderTests {
|
||||
provider.addExcludeFilter(new AnnotationTypeFilter(Service.class));
|
||||
provider.addExcludeFilter(new AnnotationTypeFilter(Controller.class));
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class);
|
||||
assertBeanTypes(candidates,
|
||||
NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -334,8 +339,9 @@ class ClassPathScanningCandidateComponentProviderTests {
|
||||
provider.addIncludeFilter(new AnnotationTypeFilter(Component.class));
|
||||
provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class));
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, FooServiceImpl.class,
|
||||
BarComponent.class, DefaultNamedComponent.class, NamedStubDao.class, StubFooDao.class);
|
||||
assertBeanTypes(candidates,
|
||||
DefaultNamedComponent.class, FooServiceImpl.class, NamedComponent.class, NamedStubDao.class,
|
||||
OtherFooService.class, ServiceInvocationCounter.class, StubFooDao.class, BarComponent.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -345,8 +351,9 @@ class ClassPathScanningCandidateComponentProviderTests {
|
||||
provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class));
|
||||
provider.addExcludeFilter(new AssignableTypeFilter(FooService.class));
|
||||
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
|
||||
assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class,
|
||||
DefaultNamedComponent.class, NamedStubDao.class, StubFooDao.class);
|
||||
assertBeanTypes(candidates,
|
||||
DefaultNamedComponent.class, NamedComponent.class, NamedStubDao.class,
|
||||
ServiceInvocationCounter.class, StubFooDao.class, BarComponent.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-1
@@ -47,6 +47,7 @@ class EnableAspectJAutoProxyTests {
|
||||
|
||||
aspectIsApplied(ctx);
|
||||
assertThat(AopUtils.isJdkDynamicProxy(ctx.getBean(FooService.class))).isTrue();
|
||||
assertThat(AopUtils.isJdkDynamicProxy(ctx.getBean("otherFooService"))).isTrue();
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@@ -56,6 +57,7 @@ class EnableAspectJAutoProxyTests {
|
||||
|
||||
aspectIsApplied(ctx);
|
||||
assertThat(AopUtils.isCglibProxy(ctx.getBean(FooService.class))).isTrue();
|
||||
assertThat(AopUtils.isJdkDynamicProxy(ctx.getBean("otherFooService"))).isTrue();
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@@ -124,7 +126,7 @@ class EnableAspectJAutoProxyTests {
|
||||
}
|
||||
|
||||
|
||||
@Import({ ServiceInvocationCounter.class, StubFooDao.class })
|
||||
@Import({ServiceInvocationCounter.class, StubFooDao.class})
|
||||
@EnableAspectJAutoProxy(exposeProxy = true)
|
||||
static class ConfigWithExposedProxy {
|
||||
|
||||
|
||||
+85
-4
@@ -22,9 +22,13 @@ import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
|
||||
import org.springframework.beans.testfixture.beans.IOther;
|
||||
import org.springframework.beans.testfixture.beans.ITestBean;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
@@ -32,10 +36,13 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.context.annotation.Proxyable;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.context.annotation.ProxyType.INTERFACES;
|
||||
import static org.springframework.context.annotation.ProxyType.TARGET_CLASS;
|
||||
|
||||
/**
|
||||
* System tests covering use of AspectJ {@link Aspect}s in conjunction with {@link Configuration} classes.
|
||||
@@ -62,18 +69,40 @@ class ConfigurationClassAspectIntegrationTests {
|
||||
assertAdviceWasApplied(ConfigurationWithAspect.class);
|
||||
}
|
||||
|
||||
private void assertAdviceWasApplied(Class<?> configClass) {
|
||||
@Test
|
||||
void configurationIncludesAspectAndProxyable() {
|
||||
assertAdviceWasApplied(ConfigurationWithAspectAndProxyable.class, TestBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void configurationIncludesAspectAndProxyableInterfaces() {
|
||||
assertAdviceWasApplied(ConfigurationWithAspectAndProxyableInterfaces.class, TestBean.class, Comparable.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void configurationIncludesAspectAndProxyableTargetClass() {
|
||||
assertAdviceWasApplied(ConfigurationWithAspectAndProxyableTargetClass.class);
|
||||
}
|
||||
|
||||
private void assertAdviceWasApplied(Class<?> configClass, Class<?>... notImplemented) {
|
||||
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(factory).loadBeanDefinitions(
|
||||
new ClassPathResource("aspectj-autoproxy-config.xml", ConfigurationClassAspectIntegrationTests.class));
|
||||
GenericApplicationContext ctx = new GenericApplicationContext(factory);
|
||||
ctx.addBeanFactoryPostProcessor(new ConfigurationClassPostProcessor());
|
||||
ctx.registerBeanDefinition("config", new RootBeanDefinition(configClass));
|
||||
ctx.registerBeanDefinition("config",
|
||||
new RootBeanDefinition(configClass, AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR, false));
|
||||
ctx.refresh();
|
||||
|
||||
TestBean testBean = ctx.getBean("testBean", TestBean.class);
|
||||
ITestBean testBean = ctx.getBean("testBean", ITestBean.class);
|
||||
if (notImplemented.length > 0) {
|
||||
assertThat(testBean).isNotInstanceOfAny(notImplemented);
|
||||
}
|
||||
else {
|
||||
assertThat(testBean).isInstanceOf(TestBean.class);
|
||||
}
|
||||
assertThat(testBean.getName()).isEqualTo("name");
|
||||
testBean.absquatulate();
|
||||
((IOther) testBean).absquatulate();
|
||||
assertThat(testBean.getName()).isEqualTo("advisedName");
|
||||
ctx.close();
|
||||
}
|
||||
@@ -120,6 +149,58 @@ class ConfigurationClassAspectIntegrationTests {
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class ConfigurationWithAspectAndProxyable {
|
||||
|
||||
@Bean
|
||||
@Proxyable(INTERFACES)
|
||||
public TestBean testBean() {
|
||||
return new TestBean("name");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NameChangingAspect nameChangingAspect() {
|
||||
return new NameChangingAspect();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration()
|
||||
static class ConfigurationWithAspectAndProxyableInterfaces {
|
||||
|
||||
@Bean
|
||||
@Proxyable(interfaces = {ITestBean.class, IOther.class})
|
||||
public TestBean testBean() {
|
||||
return new TestBean("name");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NameChangingAspect nameChangingAspect() {
|
||||
return new NameChangingAspect();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class ConfigurationWithAspectAndProxyableTargetClass {
|
||||
|
||||
public ConfigurationWithAspectAndProxyableTargetClass(AbstractAutoProxyCreator autoProxyCreator) {
|
||||
autoProxyCreator.setProxyTargetClass(false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Proxyable(TARGET_CLASS)
|
||||
public TestBean testBean() {
|
||||
return new TestBean("name");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NameChangingAspect nameChangingAspect() {
|
||||
return new NameChangingAspect();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Aspect
|
||||
static class NameChangingAspect {
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
example.scannable.AutowiredQualifierFooService=example.scannable.FooService
|
||||
example.scannable.DefaultNamedComponent=org.springframework.stereotype.Component
|
||||
example.scannable.NamedComponent=org.springframework.stereotype.Component
|
||||
example.scannable.FooService=example.scannable.FooService
|
||||
example.scannable.FooServiceImpl=org.springframework.stereotype.Component,example.scannable.FooService
|
||||
example.scannable.ScopedProxyTestBean=example.scannable.FooService
|
||||
example.scannable.StubFooDao=org.springframework.stereotype.Component
|
||||
example.scannable.NamedComponent=org.springframework.stereotype.Component
|
||||
example.scannable.NamedStubDao=org.springframework.stereotype.Component
|
||||
example.scannable.OtherFooService=org.springframework.stereotype.Component,example.scannable.FooService
|
||||
example.scannable.ScopedProxyTestBean=example.scannable.FooService
|
||||
example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component
|
||||
example.scannable.StubFooDao=org.springframework.stereotype.Component
|
||||
example.scannable.sub.BarComponent=org.springframework.stereotype.Component
|
||||
example.scannable.JakartaManagedBeanComponent=jakarta.annotation.ManagedBean
|
||||
example.scannable.JakartaNamedComponent=jakarta.inject.Named
|
||||
|
||||
Reference in New Issue
Block a user