where does spring boot set proxy as CGLib [duplicate] - spring

Recently i found spring documentation page that says:
Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. (JDK dynamic proxies are preferred whenever you have a choice).
If the target object to be proxied implements at least one interface then a JDK dynamic proxy will be used.
But it doesn't seem to be the case in my application. I wanted to write a small benchmark to compare the performance of both types of proxying.
There are two similar classes. Both have one method annotated with the #Transactional annotation. One class implements the interface and the other does not:
#Service
public class Cglib {
#Transactional
public void method() {}
}
public interface Dynamic {
void method();
}
#Service
public class DynamicImpl implements Dynamic {
#Override
#Transactional
public void method() {}
}
And based on the documentation for the first class, a CGLIB proxy should be created, and for the second, a JDK dynamic proxy.
But in my case CGLIB was used for both classes:
#Component
#RequiredArgsConstructor
public class Runner implements ApplicationRunner {
private final Cglib cglib;
private final Dynamic dynamic;
#Override
public void run(ApplicationArguments args) {
System.out.println(cglib.getClass());
System.out.println(dynamic.getClass());
}
}
class com.example.demo.proxy.cglib.Cglib$$EnhancerBySpringCGLIB$$767ff22
class com.example.demo.proxy.dynamic.DynamicImpl$$EnhancerBySpringCGLIB$$20a564d6
There are no additional configurations in the application. Only #SpringBootApplication class generated via spring initializr
Am I doing something wrong? The code was run on Spring Boot 2.7.2 and JDK 17.

That is due to spring-boot autoconfiguation:
#Configuration(proxyBeanMethods = false)
#ConditionalOnBean(TransactionManager.class)
#ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
public static class EnableTransactionManagementConfiguration {
#Configuration(proxyBeanMethods = false)
#EnableTransactionManagement(proxyTargetClass = false)
#ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
public static class JdkDynamicAutoProxyConfiguration {
}
#Configuration(proxyBeanMethods = false)
#EnableTransactionManagement(proxyTargetClass = true)
#ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
}
it turns #EnableTransactionManagement(proxyTargetClass = true) (Indicate whether subclass-based (CGLIB) proxies are to be created (true) as opposed to standard Java interface-based proxies (false)) when the property spring.aop.proxy-target-class is not set (matchIfMissing = true)

Related

Spring Boot: use autowired constructor with class from configuration file

I have a Spring Boot 2.3 application with a controller:
#RestController
public class StatusController {
private final ServerStatusCheck serverStatusCheck;
private final ServerStatusMapper serverStatusMapper;
#Autowired
public StatusController(AService aService, ServerStatusMapper serverStatusMapper) {
this.serverStatusCheck = aService;
this.serverStatusMapper = serverStatusMapper;
}
// (...)
}
The class AService implements the interface ServerStatusCheck. There is also a BService class, also implementing ServerStatusCheck interface.
What I need to do: the injected AService object should be configurable in a configuration file, so that the service injected is either "AService" or "BService", depending on the configuration file values. What is the best way to achieve this using Spring Boot? If possible, I would like to keep the constructor-based autowiring (instead of field-based autowiring).
You can create the different beans in a configuration class with condition like https://reflectoring.io/spring-boot-conditionals/
#Configuration
public class ServiceConfiguration {
#ConditionalOnProperty(value="service.a.enabled", havingValue = "true", matchIfMissing = true)
public ServerStatusCheck serverStatusCheckA() {
return new AService();
}
#ConditionalOnMissingBean
#ConditionalOnProperty(value="service.b.enabled", havingValue = "true", matchIfMissing = true)
public ServerStatusCheck serverStatusCheckB() {
return new BService();
}
}
and then wire the bean into the constructor

ConstraintValidator dependency injection leads to ValidationException when being validated at class level

I've encountered an unexpected behaviour when using dependency injection in a ConstraintValidator which is getting evaluated at class level.
Entity class:
#Entity
#ValidDemoEntity
public class DemoEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
Validation annotation:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {DemoEntityValidator.class})
public #interface ValidDemoEntity {
String message() default "{some.demo.validator.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator:
public class DemoEntityValidator implements ConstraintValidator<ValidDemoEntity, DemoEntity> {
private DemoEntityRepository demoEntityRepository;
public DemoEntityValidator(DemoEntityRepository demoEntityRepository) {
this.demoEntityRepository = demoEntityRepository;
}
#Override
public void initialize(ValidDemoEntity constraintAnnotation) {
}
#Override
public boolean isValid(DemoEntity demoEntity, ConstraintValidatorContext constraintValidatorContext) {
return true;
}
}
Test class:
#SpringBootTest
public class ValidatorInstantiationTest {
private Validator validator;
#Before
public void setUp() throws Exception {
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.getValidator();
}
#Test
public void shouldInitiateAndCallDemoEntityValidator() {
DemoEntity demoEntity = new DemoEntity();
validator.validate(demoEntity);
}
}
Validating the entity leads to:
javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator: com.example.demo.DemoEntityValidator.
and further down the stack trace:
Caused by: java.lang.NoSuchMethodException: com.example.demo.DemoEntityValidator.<init>()
which indicates that Hibernate tried to initiate the the class instead of letting Spring take care of that.
The strange thing about this is that dependency injection works fine for validations applied on field level.
The code is available at GitHub.
The exception says that there is no default constructor because Hibernate Validator tries to instantiate your validator.
You have to use Spring.
1 Make your validator a Spring Bean:
#Component
public class DemoEntityValidator implements ConstraintValidator<ValidDemoEntity, DemoEntity> {
2 Inject the Spring provided validator and use the SpringRunner for executing your tests:
#SpringBootTest
#RunWith(SpringRunner.class)
public class ValidatorInstantiationTest {
#Autowired
private Validator validator;
#Test
public void shouldInitiateAndCallDemoEntityValidator() {
DemoEntity demoEntity = new DemoEntity();
validator.validate(demoEntity);
}
}
1 Make your validator a Spring Bean
This site states:
The Spring framework automatically detects all classes which implement the ConstraintValidator interface. The framework instantiates them and wires all dependencies like the class was a regular Spring bean.
Which clearly works for validations applied on field level.
Nevertheless I've updated the code.
DemoEntityValidator is now a Spring component:
#Component
public class DemoEntityValidator implements ConstraintValidator<ValidDemoEntity, DemoEntity>
I've changed the test to:
#SpringBootTest
#RunWith(SpringRunner.class)
public class ValidatorInstantiationTest {
#Autowired
private DemoEntityRepository demoEntityRepository;
#Test
public void shouldInitiateAndCallDemoEntityValidator() {
DemoEntity demoEntity = new DemoEntity();
demoEntityRepository.save(demoEntity);
}
}
To make the usecase clearer, but the test still leads to the same exception.
Adding an empty constructor to the class DemoEntityValidator disables the error.
I think you answer is here:
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring
You need to declare a LocalValidatorFactoryBean in your configuration class and it will just work.
From the documentation:
By default, the LocalValidatorFactoryBean configures a
SpringConstraintValidatorFactory that uses Spring to create
ConstraintValidator instances. This lets your custom
ConstraintValidators benefit from dependency injection like any other
Spring bean.
And an example from the same place:
import javax.validation.ConstraintValidator;
public class MyConstraintValidator implements ConstraintValidator {
#Autowired;
private Foo aDependency;
...
}
And this is how I declared that bean in a #Configuration annotated class:
/**
* Provides auto-wiring capabilities for validators Checkout: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring
*/
#Bean
public LocalValidatorFactoryBean validatorFactoryBean() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(validationMessageSource());
return bean;
}
There's nothing wrong with your validator class. I got this working by making two changes to the test configuration:
1. Run test with Spring
In order to have Spring manage your beans, you need to run your test with a test runner that sets up Spring. You can specify the test runner class using junit's #RunWith-annotation:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ValidatorInstantiationTest { ... }
2. Inject a Spring managed validator bean
Since you're using Spring Boot, you can inject a Spring managed validator – it's already configured. This way, Spring will handle the initiation of your DemoEntityValidator.
#RunWith(SpringRunner.class)
#SpringBootTest
public class ValidatorInstantiationTest {
#Autowired
private Validator validator;
...
}
This is all that is needed. You should not annotate your DemoEntityValidator with #Component or similar.
Note that you need to provide Spring with a data source, since SpringRunner will set up a context based on your Spring Boot setup (I'm guessing it includes spring-boot-starter-data-jpa in your case). The easiest way to get going is just to put an in-memory DB such as h2 on the classpath.

BeanNotOfRequiredTypeException in Spring boot test runner and #Rule

I am writing an integration test using spring runner, and have created a TestRule implementation and used it through #Rule. But I try to create a bean of that implementation, I get BeanNotOfRequiredTypeException.
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'localDynamoDB' is expected to be of type 'com.wickes.dynamo.local.LocalDynamodb' but was actually of type 'com.sun.proxy.$Proxy114'
My test class is:
#ComponentScan(basePackages = "com.wickes.stock")
#Configuration
public class TestConfig {
#Bean
public LocalDynamodb localDynamoDB() {
return new LocalDynamodb();
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestConfig.class)
public class StockListenerTest {
#Rule
#Autowired
public LocalDynamodb localDynamodb;
#Test
public void test() {
}
}
and my config is:
My LocalDynamodb is
public class LocalDynamodb extends ExternalResource {
}
M. Deinum is correct: LocalDynamodb is being proxied by interfaces, which you do not want.
Thus, you have two options:
Convert LocalDynamodb to an interface, implement the interface, and register the implementation as the bean.
Switch from dynamic interface-based proxies (the default) to class-based proxies. How you perform the switch depends on how the proxies are being created, but since we can't see the rest of your Spring configuration we don't know how to advise you there.
Regards,
Sam (author of the Spring TestContext Framework)
I had a similar problem when trying to inject an AspectJ proxied object, then I found that you can add the #Scope annotation to LocalDynamodb:
#Component
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ...
More details here

Register only implementations of a interface to a Spring Application Context?

I have a set of classes as follows.
public class ServiceA implements CommonInterface
{
public void startA()
{
....
}
}
public class ServiceB implements CommonInterface
{
public void startB()
{
....
}
}
Is there any way in Spring-framework to auto-register only the classes which implement a CommonInterface to a application context and access them?
Yes its possible. If you are using Java Configuration you can filter components to those that implement a specific interface using #ComponetScan. You need to disable the default filters used by Spring (based on detecting stereotype annotated classes i.e #Component , #Service etc).
#Configuration
#ComponentScan(basePackages = {"com.mydomain.myapp.service"} , useDefaultFilters = false , includeFilters = {#Filter(type = FilterType.ASSIGNABLE_TYPE , value = CommonInterface.class)})
public class AppConfig{
//#Bean methods
}
The same can also be achieved in xml using context:component-scan

Override application properties in Spring Boot integration tests

I'm preparing a Spring Boot starter (used in tests) and I want to override a specific application property. This particular case regards enabling / disabling cache based on a property (production-code starter uses #ConditionalOnProperty). When using the test starter I want to have the caching disabled by default.
Is there a way to do that except using #TestPropertySource? Hence it is not a repeatable annotation, I don't want to use it. It's a recommended way for end-user to add properties required for the test case.
Edit:
I'm providing more details on the specific use case
Production Starter auto-configuration:
#Configuration
#ConditionalOnWebApplication
#EnableConfigurationProperties(StarterCacheProperties.class)
#EnableCaching
public class StarterCacheAutoConfiguration {
..(omitted for clarity)..
#Configuration
#ConditionalOnProperty(prefix = "neostarter.saasmgr.cache", name = "enabled", havingValue = "false")
public static class SaasMgrNoCacheConfig extends CachingConfigurerSupport {
#Bean(name = SAAS_MGR_CACHE_MANAGER)
#Override
public CacheManager cacheManager() {
return new NoOpCacheManager();
}
}
#Configuration
#ConditionalOnProperty(prefix = "neostarter.saasmgr.cache", name = "enabled", matchIfMissing = true)
public static class SaasMgrCachingConfig extends CachingConfigurerSupport {
#Autowired
SaasMgrCacheProperties saasMgrCacheProperties;
#Bean(destroyMethod = "shutdown")
public net.sf.ehcache.CacheManager ehCacheManager() {
CacheConfiguration cacheConfiguration = new CacheConfiguration(SAAS_MGR_AUTH_CACHE, 1000)
.timeToLiveSeconds(saasMgrCacheProperties.getTimeToLiveSeconds());
net.sf.ehcache.config.Configuration config = new net.sf.ehcache.config.Configuration();
config.addCache(cacheConfiguration);
return net.sf.ehcache.CacheManager.newInstance(config);
}
#Bean(name = SAAS_MGR_CACHE_MANAGER)
#Override
public CacheManager cacheManager() {
return new EhCacheCacheManager(ehCacheManager());
}
}
This gives user a choice of caching particular calls in production (or not). However, I find it reasonable to disable caching in tests by default. I am fully aware that this can still be overridden by end-user. In a test starter (different dependency) I tried the following:
#Configuration
#AutoConfigureBefore({CacheAutoConfiguration.class, SaasMgrSecurityAutoConfiguration.class})
public class DisableCacheAutoConfiguration {
#Autowired
Environment environment;
#PostConstruct
public void disableCache() {
EnvironmentTestUtils.addEnvironment((ConfigurableEnvironment)environment, "neostarter.saasmgr.cache.enabled=false");
}
}
but no matter what StarterCacheAutoConfiguration with EhCacheCacheManager is always resolved before DisableCacheAutoConfiguration. In debug in AutoConfigurationSorter.java I see proper order of the configuration:
15 = "com.neoteric.starter.auth.saasmgr.test.DisableCacheAutoConfiguration"
16 = "com.neoteric.starter.auth.SaasMgrSecurityAutoConfiguration"
36 = "org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration"
You can't change user's configuration in a starter, that would be way too opaque and invasive. You have no guarantee about it anyway: anybody could set the property value at a higher level (e.g. system property) and override what you're trying to change.
If you want to disable caching, the most easiest way to do it is to do what spring.cache.type=none does, that is providing an implementation of CacheManager that does not do anything.
Try to declare a bean of type org.springframework.cache.support.NoOpCacheManager

Resources