Browse Source

Introduce @Proxyable annotation for bean-specific proxy type

Closes gh-35296
See gh-35293
pull/35303/head
Juergen Hoeller 4 months ago
parent
commit
df86a9973d
  1. 2
      spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java
  2. 15
      spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java
  3. 3
      spring-context/src/test/java/example/scannable/FooServiceImpl.java
  4. 46
      spring-context/src/test/java/example/scannable/OtherFooService.java
  5. 31
      spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java
  6. 4
      spring-context/src/test/java/org/springframework/context/annotation/EnableAspectJAutoProxyTests.java
  7. 89
      spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java
  8. 7
      spring-context/src/test/resources/example/scannable/spring.components

2
spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java

@ -464,7 +464,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport @@ -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
spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java

@ -22,6 +22,7 @@ import java.util.function.Predicate; @@ -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 { @@ -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(

3
spring-context/src/test/java/example/scannable/FooServiceImpl.java

@ -33,6 +33,7 @@ import org.springframework.context.ConfigurableApplicationContext; @@ -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; @@ -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

46
spring-context/src/test/java/example/scannable/OtherFooService.java

@ -0,0 +1,46 @@ @@ -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;
}
}

31
spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java

@ -41,6 +41,7 @@ import example.scannable.JakartaNamedComponent; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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

4
spring-context/src/test/java/org/springframework/context/annotation/EnableAspectJAutoProxyTests.java

@ -47,6 +47,7 @@ class EnableAspectJAutoProxyTests { @@ -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 { @@ -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 { @@ -124,7 +126,7 @@ class EnableAspectJAutoProxyTests {
}
@Import({ ServiceInvocationCounter.class, StubFooDao.class })
@Import({ServiceInvocationCounter.class, StubFooDao.class})
@EnableAspectJAutoProxy(exposeProxy = true)
static class ConfigWithExposedProxy {

89
spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java

@ -22,9 +22,13 @@ import org.aspectj.lang.annotation.Aspect; @@ -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; @@ -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 { @@ -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 { @@ -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 {

7
spring-context/src/test/resources/example/scannable/spring.components

@ -1,12 +1,13 @@ @@ -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

Loading…
Cancel
Save