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

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

Related

where does spring boot set proxy as CGLib [duplicate]

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)

How inject DataSource(HikariCP) in Auto Configuration class?

I'm trying to use Spring Boot 2 + Spring Data + Custom Auto Configuration Classes but for some reason can't inject DataSource(provided by HikariCP) in the third class.
#Configuration
#AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
public class InitialAutoConfiguration {
//Beans to load in theory first.
}
#Configuration
#AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
#AutoConfigureAfter(InitialAutoConfiguration.class)
#EntityScan(basePackageClasses = Asset.class)
#EnableJpaRepositories(basePackageClasses = AssetRepository.class,
repositoryBaseClass = BaseRepositoryImpl.class,
repositoryFactoryBeanClass = ExtendedJpaRepositoryFactoryBean.class)
public class JpaAutoConfiguration { //Load Jpa Classes
}
#Configuration
#AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
#AutoConfigureAfter(JpaAutoConfiguration.class)
#ComponentScan(basePackages = {"com.xxx"})
public class ServiceConfiguration {
#Inject
private DataSource dataSource; //Datasource is null
#Bean
public DbPropertySourcesPlaceholderConfigurer dbPropertySourcesPlaceholderConfigurer() {
DbPropertySourcesPlaceholderConfigurer placeholderConfigurer = new DbPropertySourcesPlaceholderConfigurer(dataSource);
placeholderConfigurer.setPlaceholderPrefix("%{");
placeholderConfigurer.setPlaceholderSuffix("}");
return placeholderConfigurer;
}
}
META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xetec.autoconfigure.InitialAutoConfiguration,\
com.xetec.autoconfigure.JpaAutoConfiguration,\
com.xetec.autoconfigure.ServiceConfiguration
Looks like my classes are loading before the Spring Data Classes(DataSourceAutoConfiguration.Hikari).
Is there one way to first load the Spring Boot Starters Classes then after my custom ones please?
Thanks
I am no sure if the following change can solve your issue, but i meet with the similar problem
add static in your method
#Bean
public static DbPropertySourcesPlaceholderConfigurer dbPropertySourcesPlaceholderConfigurer() {
....
}
Your use of highest and lowest precedence is the wrong way round. Your ServiceConfiguration is ordered with highest precedence which means that it will be evaluated first.
Rather than using absolute ordering, I would use #AutoConfigureAfter(DataSourceAutoConfiguration.class)

Spring conditional component scan configuration

I have a configuration class which registers beans based on a very simple condition (checking a property value in application.properties). The configuration class and the condition are the following:
#Configuration
#Conditional(DatabaseConfigurationCondition.class)
#ComponentScan(basePackageClasses = DBConfigComponents.class)
public class DatabaseConfigurationLoader {
#Bean
public DatabaseConfigurationRepository databaseConfigurationRepository() {
return new DatabaseConfigurationRepository();
}
}
and
public class DatabaseConfigurationCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return conditionContext.getEnvironment().getProperty("configuration.type").contains("db");
}
}
In addition of the beans registered in this configuration class I have component scan which scans for other components. When the condition is not met, I expect the beans which are defined in the configuration class not to be registered (which happens to be a case), but also I expect other classes which are annotated with #Component (or #Repository, #Service, etc.. ) and are in same folder as DBConfigComponents.class marker interface not to be registered, which does not happen. Beans which are scanned are always registered, no matter if the condition is fulfilled or not.
When I put the #Conditional(DatabaseConfigurationCondition.class) on each #Component annotated class, than it's working correctly, but I don't want to put it on each class separately.
Any suggestion?
Fortunately, I managed to fix this. The problem in my case was that I had another #ComponentScan annotation placed in other configuration class in other Maven module - not conditional on any property. The components which are in same package as DBConfigComponents marker interface were actually scanned by the other configuration class.
The way #ComponentScan works is on package level. Although, in different Maven modules, both configuration classes were in same package. #ComponentScan works perfectly fine with #Conditional. No need #Conditional to be placed on each component separately.
The best way to achieve this is not to annotate these beans using #Component / #Service and #Repository annotations. Instead you should return these as part of the configuration you have setup which would be DatabaseConfigurationLoader. See sample below.
#Configuration
#Conditional(DatabaseConfigurationCondition.class)
public class DatabaseConfigurationLoader {
#Bean
public DatabaseConfigurationRepository databaseConfigurationRepository() {
return new DatabaseConfigurationRepository();
}
#Bean
public SomeService someService() {
return new SomeService();
}
#Bean
public SomeComponent someComponent() {
return new SomeComponent();
}
}
Note: Typically #Configuration with #Conditional are used in libraries that you want to include in your spring boot application. Such libraries should not share the same package as your spring boot application. Thus they should not be picked up by #ComponentScan annotation. Beans from libraries should not be annotated with #Component / #Service / #Repository annotations. Spring suggests using AutoConfiguration for that. See https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html & https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html
No need to implement Condition interface, you need to use '#ConditionalOnProperty' annotation:
#Configuration
#ComponentScan(basePackageClasses = DBConfigComponents.class)
#ConditionalOnProperty(name = "configuration.type", havingValue = "db")
public class DatabaseConfigurationLoader {
#Bean
public DatabaseConfigurationRepository databaseConfigurationRepository() {
return new DatabaseConfigurationRepository();
}
}
you can use 'prefix' instead of 'havingValue' depending on your needs.

Spring: Properly setup #ComponentScan

I have following set up for my Spring Application Context.
#Configuration
public class RmiContext {
#Bean
public RmiProxyFactoryBean service() {
RmiProxyFactoryBean rmiProxy = new RmiProxyFactoryBean();
rmiProxy.setServiceUrl("rmi://127.0.1.1:1099/Service");
rmiProxy.setServiceInterface(Service.class);
return rmiProxy;
}
}
#Configuration
public class LocalContext {
#Bean
public Controller Controller() {
return new ControllerImpl();
}
}
#Configuration
#Import({RmiContext.class, LocalContext.class})
public class MainContext {
}
The above setup works fine, but I want to enable #ComponentScan annotating Controllers with #Component as there are many Controllers in my application which is tedious when declared one by one using #Bean.
#Configuration
#ComponentScan(basePackageClasses = {Controller.class})
public class LocalContext {
/* ... */
}
The problem is that when I do #ComponentScan(basePackageClasses = {Controller.class}), the previously fine working RmiProxyFactoryBean are not recognized or can't be created.
So, How do I configure my MainContext so that both beans via RMI and local beans are created?
#Configuration is also a candidate for component scan, so you can scan all the beans in RmiContext and all controllers in your controller package by:
#Configuration
#ComponentScan(basePackages = {"org.example.controllers", "package.of.RmiContext"})
public class MainContext {
}
--edit--
#Configuration is a candidate for component scan, here is the test case that works in my pc:
package scan.controllers;
#Controller
public class ExampleController {
}
package scan;
public interface RMIService {
}
package scan;
#Configuration
public class RmiContext {
#Bean
public RmiProxyFactoryBean service() {
RmiProxyFactoryBean rmiProxy = new RmiProxyFactoryBean();
rmiProxy.setServiceUrl("rmi://127.0.1.1:1099/Service");
rmiProxy.setServiceInterface(RMIService.class);
rmiProxy.setLookupStubOnStartup(false);
return rmiProxy;
}
}
package scan;
#Configuration
//MainContext will auto scan RmiContext in package scan and all controllers in package scan.controllers
#ComponentScan(basePackages = {"scan", "scan.controllers"})
public class MainContext {
}
package scan;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={MainContext.class})
public class TestContext {
#Autowired private RMIService rmi;
#Autowired private ExampleController controller;
#Test
public void test() {
//both controller and rmi service are autowired as expected
assertNotNull(controller);
assertNotNull(rmi);
}
}
May be you could try using the base packages of your classes (RMI, Controller):
#ComponentScan(basePackages = {"your controller package", "your rmi package"})
If the RMI classes package is different than controller then they will fail to instantiate by spring.
If I understand you correctly, you use "#ComponentScan(basePackageClasses" but it is not detecting and registering your spring beans?
I had the same issue a few minutes ago. I did not give up and tried all funny guesses. One guess did it.
I had to add an XML component-scan entry in XML. I just put a dummy package there, like below:
component-scan base-package="dummy.filler.to.enable.component.scan"
It seems that the component-scan in XML enables the #ComponentScan.
[Late Edit: I noticed, my spring xml schema is spring 2.5. Anyway, I dont know if this matters. Best Regards.]

Spring #Configuration bean created in #Bean method not enhanced by CGLIB

I'm trying to create a MainConfig that imports another Config by using an #Bean method instead of #Import like this :
#Configuration
public class MainConfig {
#Bean
public Service service() {
return new Service(infrastructureConfig().database());
}
#Bean
public OtherService otherService() {
return new OtherService(infrastructureConfig().database());
}
#Bean
public InfrastructureConfig intrastructureConfig() {
return new InfrastructureConfig();
}
}
#Configuration
public class InfrastructureConfig {
#Bean
public Database database() {
return new Database();
}
...
}
When using this technique, the Database is created twice because Spring doesn't seem to consider the #Configuration annotation on InfrastructureConfig. When using #Import, it works fine.
I don't want to use #Import because I want to mock my InfrastructureConfig like this :
#Configuration
public class TestConfig extends MainConfig {
#Override
public InfrastructureConfig infrastructureConfig() {
return mock(InfrastructureConfig.class);
}
}
Am I missing something or it is not supported ?
Thanks
When I first tried out Spring Java configuration I think I made the same assumption and was surprised when it didn't work.
I'm not sure this is the neatest way of solving this but I have used the following approach successfully.
To include that #Configuration class you can add this annotation to your MainConfig:
#ComponentScan(basePackages = "org.foo", includeFilters = {#Filter(filterType = ANNOTATION, value = CONFIGURATION)}, excludeFilters = {#Filter(filterType = ASSIGNABLE_TYPE, value = MainConfig)})
Since #Configuration classes are also candidates for component scanning this allows you to scan for all classes annotated with #Configuration. Since you're putting this annotation on MainConfig you need to exclude that with the ASSIGNABLE_TYPE filter since you'll get a circular reference.
I opened a Spring ticket SpringSource JIRA and they said that it is a known limitation and it is working as designed.

Resources