How work with 3rd-party #ConfigurationProperties #Bean? - java

I use IntellijIdea and gradle. Gradle config:
...
apply plugin: 'propdeps'
apply plugin: 'propdeps-idea'
apply plugin: 'propdeps-maven'
buildscript {
repositories {
maven { url 'http://repo.spring.io/plugins-release' }
}
dependencies {
classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.7'
}
}
compileJava.dependsOn(processResources)
dependencies {
...
optional group: 'org.springframework.boot', name: 'spring-boot-configuration-processor', version: '1.4.0.RELEASE'
}
Ok, for creating my own properties i need:
#Component
#ConfigurationProperties("own.prefix")
#Data
public class TestProps {
public String field;
}
#Configuration
#EnableConfigurationProperties(TestProps.class)
public class AppConf {}
And after i rebuild project spring-boot-configuration-processor genereate new META-INFO, so in application.properties i can use own.prefix.field= and Spring see it.
But what should i do with 3rd party configuration class?
Docs http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html say:
As well as using #ConfigurationProperties to annotate a class, you
can also use it on #Bean methods. This can be particularly useful
when you want to bind properties to third-party components that are
outside of your control.
To configure a bean from the Environment properties, add
#ConfigurationProperties to its bean registration:
#ConfigurationProperties(prefix = "foo")
#Bean
public FooComponent fooComponent() {
...
}
Any property defined with the foo prefix will be mapped onto that
FooComponent bean in a similar manner as the ConnectionProperties
example above.
Ok. Lets try. For example I declare bean like in gide (https://spring.io/guides/tutorials/spring-boot-oauth2/):
#Configuration
#EnableOAuth2Sso
public class SocialConfig {
#Bean
#ConfigurationProperties("facebook.client")
OAuth2ProtectedResourceDetails facebook() {
return new AuthorizationCodeResourceDetails();
}
#Bean
#ConfigurationProperties("facebook.resource")
ResourceServerProperties facebookResource() {
return new ResourceServerProperties();
}
}
But after rebuilding project property facebook.client and facebook.resource do not exist in my application.properties.
Also i tried add SocialConfig.class to
#Configuration
#EnableConfigurationProperties(SocialConfig.class)
public class AppConf {}
After rebuild it still not work.
And like this:
#Configuration
#EnableConfigurationProperties
public class AppConf {}
Still the same.
What am I doing wrong?
Also sorry for my English :)

What you're doing wrong is that the methods in your #Configuration class are not public. Just add public to facebook and facebookResource and you'll be fine. I've just polished that doc in c4cb8317 and I've submitted a PR to fix the tutorial you were using as a base.
Also the generated metadata were mostly empty since nested objects were not flagged with #NestedConfigurationProperty. I've submitted another pull request to fix that.

Related

Autowiring an interface implementation from an external library

I have written a common library for future SpringBoot applications and made it into a jar. Inside, there is a service that autowires an interface:
#Service
public class MyService {
protected #Autowired AbstractUserService abstractUserService;
}
public interface AbstractUserService {
Optional<? extends AbstractUser> findByPrincipal(Principal principal);
}
In my main application, in build.gradle I load the library mentioned above as follows:
...
repositories {
flatDir {
dirs "$rootDir/libs"
}
}
...
dependencies {
...
implementation name: "my-library"
...
}
Then I have implemented the interface:
#Service
public class UserService implements AbstractUserService {
#Override public Optional<? extends AbstractUser> findByPrincipal(Principal principal) {
// do something
}
However, it seems that the library is unable to find it and I get the following exception:
Parameter 1 of constructor in MyService required a bean of type 'AbstractUserService' that could not be found.
In my main application I have also added the following annotation in order to #Autowire the services exposed by this library:
#SpringBootApplication(scanBasePackages = {"the.root.package.of.my-library"})
Is there something that can be done? Also, is there a better way of autowiring the exposed services without the explicit scanBasePackage description?
I have managed to fix it.
First, I have removed the explicit scanBasePackage description from #SpringBootApplication.
Secondly, I have annotated the configuration file from the library with #ComponentScan and created the following file: resources/META-INF/spring.factories with the following content:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
my-library.package.MyLibraryConfig
This is my complete library config file:
#Configuration
#ComponentScan
public class MyConfig {
...
}

How to add a bean dependency from another module in Spring?

I have two modules: module1 and module2.
module2 depends on module1.
Configuration in the module1:
#Configuration
public class ConfigurationInModule1 {
#Bean
public FirstBean firstBean() {
return new FirstBean();
}
#Bean
public SecondBean secondBean(FirstBean firstBean) {
return new SecondBean(firstBean);
}
}
Configuration in the module2:
#Configuration
public class ConfigurationInModule2 {
#Bean
public SomeBeanForModule2 beanForModule2(FirstBean firstBean) {
return new SomeBeanForModule2(firstBean);
}
}
As you can see both beans secondBean and beanForModule2 depends on firstBean.
I need to make sure that when the project is compiled with module2 then beanForModule2 should be initialized before secondBean. If there is no module2 then secondBean should be initialized in a standard flow.
Is it possible to configure it in Spring?
P.S. I need to control the order of been initialization. I know that there is a special annotation #DependsOn which can be used to setup indirect dependency, but in my case I cannot use it on secondBean because the dependency beanForModule2 is optional and is placed in another module.
Spring takes care of order of bean initialization, So if one bean depends on other then Spring will first initialize dependency Beans then it will initialize dependent Beans.
In your case FirstBean will always initialize prior than SomeBeanForModule2 without any additional config.
And if Dependency Bean which is FirstBean in your case is not declared(i.e module1 is not there) then Spring will throw org.springframework.beans.factory.NoSuchBeanDefinitionException. So module2 cannot initialized without module1.
EDIT:-
For Ordering of Bean Initialization, you can use #DependsOn even if Beans are in seperate File.
Just add #import(ConfigurationInModule2.class) in ConfigurationInModule1 class in your module1.
And use #DependsOn("beanForModule2") on secondBean.
This will help:- https://stackoverflow.com/a/16297827/4720870
Found the solution by using BeanFactoryPostProcessor. We need to define our custom BeanFactoryPostProcessor and setup necessary dependencies there.
Spring won't execute beans initialization before calling postProcessBeanFactory method.
To solve the above problem we should define our custom BeanFactoryPostProcessor like this one:
public class JBCDependencyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("secondBean");
beanDefinition.setDependsOn("beanForModule2");
}
}
After that we should make a static bean with our BeanFactoryPostProcessor. Something like this:
#Configuration
public class ConfigurationInModule2 {
#Bean
public static BeanFactoryPostProcessor dependencyBeanFactoryPostProcessor() {
return new JBCDependencyBeanFactoryPostProcessor();
}
#Bean
public SomeBeanForModule2 beanForModule2(FirstBean firstBean) {
return new SomeBeanForModule2(firstBean);
}
}
Spring will search for all beans. Then it will execute postProcessBeanFactory in our BeanFactoryPostProcessor. We will make a dependency from secondBean to beanForModule2 and then spring will call bean initialization by following our dependencies.
P.S. Thanks to #Tarun for sharing the link.

Why are my dependency Spring beans instantiated twice ? Once from the jar dependency and once from the classpath

I have the most puzzling Spring error message I ever had, well, I had a few over the years, but this one is one to remember.
The error message is:
Field orderService in com.thalasoft.butik.rest.config.FixtureService required a single bean, but 2 were found:
- com.thalasoft.butik.data.service.OrderServiceImpl: defined in URL [jar:file:/home/stephane/.m2/repository/com/thalasoft/butik-data/0.0.1-SNAPSHOT/butik-data-0.0.1-SNAPSHOT.jar!/com/thalasoft/butik/data/service/OrderServiceImpl.class]
- OrderService: defined by method 'OrderService' in class path resource [com/thalasoft/butik/data/config/JpaService.class]
The butik application is composed of 2 Spring projects, one being the butik-data project and the other one being the butik-rest project.
The error occurs when running the integration tests in the butik-rest project
mvn clean install -Denv="test" -Ddb="h2"
The very same error occurs when running the application and not running the integration tests:
mvn clean spring-boot:run
The dependency is only present once in the pom.xml file:
<dependency>
<groupId>com.thalasoft</groupId>
<artifactId>butik-data</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
My butik-rest project configuration looks like:
#EnvProd
#SpringBootApplication
#Slf4j
public class Application implements CommandLineRunner {
#Component
#ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.butik.rest.service", "com.thalasoft.butik.data" })
public class ApplicationConfiguration {
}
#Component
#EnableWebMvc
#EnableSpringDataWebSupport
#ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.butik.rest.exception",
"com.thalasoft.butik.rest.controller", "com.thalasoft.butik.rest.assembler" })
public class WebConfiguration implements WebMvcConfigurer {
The integration test configuration:
#RunWith(SpringRunner.class)
#Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = {
"classpath:mysql/clean-up-before-each-test.sql" })
public abstract class BaseTest {
#Configuration
#EnableAutoConfiguration
#ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.butik.rest.config",
"com.thalasoft.butik.rest.service", "com.thalasoft.butik.data" })
public class TestConfiguration {
}
#EnableWebSecurity
#ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.butik.rest.filter" })
public class NoSecurityConfiguration extends WebSecurityConfigurerAdapter {
The service beans are instantiated explicitly in the dependency project:
#Configuration
public class JpaService {
#Bean
public ProductServiceImpl ProductService() {
return new ProductServiceImpl();
}
#Bean
public OrderServiceImpl OrderService() {
return new OrderServiceImpl();
}
}
Could it be that Spring gets one bean from the explicit instantiation above in the butik-data project, and another one from the "com.thalasoft.butik.data" scanning in the dependant butik-rest project ?
UPDATE: Even when changing the 2 instances of "com.thalasoft.butik.data" (there is one to run the application and another one to run the integration tests) into "com.thalasoft.butik.data.config" I still get the same error.
UPDATE: I see I had 2 mistakes compounding making the whole issue a bit tricky. I had to remove the "com.thalasoft.butik.data.config" instance from the integration tests also. And now the issue is gone.
It looks like you've scanned both of those locations. You need to investigate places which are currently scanned and which should be scanned.
If you think that current way of scanning (which include two beans which are suitable for autowiring 'orderService' field), you can mark one of those beans by annotation #Primary ( docs: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Primary.html ).
Bean marked by this annotation will be prefered over another ones, which should solve your problem.
Good luck :)

Is there a way to register a repository base class with a spring boot auto configuration?

We have a base JPA repository class with some additional utility methods that we use in our projects. Following the Spring Data JPA documentation we created the class and use the #EnableJpaRepositories annotation in a configuration class as in the following example:
#Configuration
#EnableJpaRepositories(basePackageClasses = MyApplication.class,
repositoryBaseClass = MyJpaRepositoryImpl.class)
public class SpringDataJpaMyRepositoryConfiguration {
}
We also set the basePackageClasses attribute so our repositories are found, as the configuration class is not in the application root package. Everything works as expected, so no problems so far.
Now we would like to create a spring boot starter to add the repository base class to our projects without further configuration, but we don't know how to do it. If we create an AutoConfiguration class with the EnableJpaRepositories annotation setting the repositoryBaseClass attribute, the automatic repository lookup strategy which looks for repositories under the class annotated with #SpringBootApplication doesn't work anymore.
And we can't use the basePackageClasses attribute as we don't know the main class or package of the project using the autoconfiguration.
Is there any way to do this? Maybe by redefining some bean in our autoconfiguration?
The ideal way would be something that allows to set the repository base class without having to define all the Spring Data JPA autoconfiguration again.
This question has driven me crazy at the time, so I thought I could help you on this.
Basically, the idea is to:
Create a configuration class for your Jpa config
Add #EntityScan and #EnableJpaRepositories referencing the same configuration class as the basePackageClass
Import this configuration class in your autoconfiguration
Create an annotation that you can then reuse where you need your Jpa config
In your example, you're using your Spring application class as your base for scannning.
I've put up a sample project to POC the main ideas at https://github.com/rdlopes/custom-jpa-demo
In the example, there's a project for the JPA entities/repositories exposing a JPA configuration:
#Configuration
#EntityScan(basePackageClasses = JpaConfiguration.class)
#EnableJpaRepositories(basePackageClasses = JpaConfiguration.class,
repositoryBaseClass = BaseRepositoryImpl.class)
public class JpaConfiguration {
}
Be careful with the common implementation for your repositories, you need to show a special signature:
#NoRepositoryBean
public class BaseRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID>
implements BaseRepository<T, ID> {
public BaseRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
}
#Override
public String someCustomMethod(ID id) {
return "Class for entity of id " + id + " is: " + getDomainClass().getSimpleName();
}
}
You can then create your auto configuration as such:
#Configuration
#ConditionalOnClass(CustomJpaRepositories.class)
#Import(JpaConfiguration.class)
public class JpaCustomAutoConfiguration {
}
Providing an annotation to keep things tidy and use it where you need JPA:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Inherited
public #interface CustomJpaRepositories {
}
Using your JPA classes will be as simple as having this annotation where you call your JPA repositories:
#SpringBootApplication
#CustomJpaRepositories
public class CustomJpaSampleApplication {
public static void main(String[] args) {
SpringApplication.run(CustomJpaSampleApplication.class, args);
}
#Bean
public CommandLineRunner dataInitializer(UserRepository userRepository) {
return args -> {
User user1 = new User();
user1.setName("user 1");
userRepository.save(user1);
User user2 = new User();
user2.setName("user 2");
userRepository.save(user2);
userRepository.findAll()
.forEach(user -> System.out.println(
userRepository.someCustomMethod(user.getId())));
};
}
}
Hope this helps you getting passed the head scratching moments :-)
EDIT: I've pretty much rewritten my answer - I misunderstood the original question
It's not the nicest solution but the only way I can see this working is by using SpEL inside #EnableJpaRepositories.
This can then go in your auto-configuration and use #ConditionalOnProperty to only auto-configure if the base package property is set
#Configuration
#ConditionalOnProperty("repositories-base-packages")
public class BaseRepositoryAutoConfiguration {
#Configuration
#EnableJpaRepositories(
repositoryBaseClass = MyJpaRepositoryImpl.class,
basePackages = "${repositories-base-packages}"
)
public static class JpaRepositoriesConfig { }
}
Then make sure you have a application.properties or application.yml which defines repositories-base-packages inside your application.
Not sure how you'd declare multiple base packages, my SpEL knowledge is primitive so not sure if it would even be possible.

exclude #Component from #ComponentScan

I have a component that I want to exclude from a #ComponentScan in a particular #Configuration:
#Component("foo") class Foo {
...
}
Otherwise, it seems to clash with some other class in my project. I don't fully understand the collision, but if I comment out the #Component annotation, things work like I want them to. But other projects that rely on this library expect this class to be managed by Spring, so I want to skip it only in my project.
I tried using #ComponentScan.Filter:
#Configuration
#EnableSpringConfigured
#ComponentScan(basePackages = {"com.example"}, excludeFilters={
#ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=Foo.class)})
public class MySpringConfiguration {}
but it doesn't appear to work. If I try using FilterType.ASSIGNABLE_TYPE, I get a strange error about being unable to load some seemingly random class:
Caused by: java.io.FileNotFoundException: class path resource [junit/framework/TestCase.class] cannot be opened because it does not exist
I also tried using type=FilterType.CUSTOM as following:
class ExcludeFooFilter implements TypeFilter {
#Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
return metadataReader.getClass() == Foo.class;
}
}
#Configuration #EnableSpringConfigured
#ComponentScan(basePackages = {"com.example"}, excludeFilters={
#ComponentScan.Filter(type=FilterType.CUSTOM, value=ExcludeFooFilter.class)})
public class MySpringConfiguration {}
But that doesn't seem to exclude the component from the scan like I want.
How do I exclude it?
The configuration seem alright, except that you should use excludeFilters instead of excludes:
#Configuration #EnableSpringConfigured
#ComponentScan(basePackages = {"com.example"}, excludeFilters={
#ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=Foo.class)})
public class MySpringConfiguration {}
Using explicit types in scan filters is ugly for me. I believe more elegant approach is to create own marker annotation:
#Retention(RetentionPolicy.RUNTIME)
public #interface IgnoreDuringScan {
}
Mark component that should be excluded with it:
#Component("foo")
#IgnoreDuringScan
class Foo {
...
}
And exclude this annotation from your component scan:
#ComponentScan(excludeFilters = #Filter(IgnoreDuringScan.class))
public class MySpringConfiguration {}
Another approach is to use new conditional annotations.
Since plain Spring 4 you can use #Conditional annotation:
#Component("foo")
#Conditional(FooCondition.class)
class Foo {
...
}
and define conditional logic for registering Foo component:
public class FooCondition implements Condition{
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// return [your conditional logic]
}
}
Conditional logic can be based on context, because you have access to bean factory. For Example when "Bar" component is not registered as bean:
return !context.getBeanFactory().containsBean(Bar.class.getSimpleName());
With Spring Boot (should be used for EVERY new Spring project), you can use these conditional annotations:
#ConditionalOnBean
#ConditionalOnClass
#ConditionalOnExpression
#ConditionalOnJava
#ConditionalOnMissingBean
#ConditionalOnMissingClass
#ConditionalOnNotWebApplication
#ConditionalOnProperty
#ConditionalOnResource
#ConditionalOnWebApplication
You can avoid Condition class creation this way. Refer to Spring Boot docs for more detail.
In case you need to define two or more excludeFilters criteria, you have to use the array.
For instances in this section of code I want to exclude all the classes in the org.xxx.yyy package and another specific class, MyClassToExclude
#ComponentScan(
excludeFilters = {
#ComponentScan.Filter(type = FilterType.REGEX, pattern = "org.xxx.yyy.*"),
#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = MyClassToExclude.class) })
I had an issue when using #Configuration, #EnableAutoConfiguration and #ComponentScan while trying to exclude specific configuration classes, the thing is it didn't work!
Eventually I solved the problem by using #SpringBootApplication, which according to Spring documentation does the same functionality as the three above in one annotation.
Another Tip is to try first without refining your package scan (without the basePackages filter).
#SpringBootApplication(exclude= {Foo.class})
public class MySpringConfiguration {}
In case of excluding test component or test configuration, Spring Boot 1.4 introduced new testing annotations #TestComponent and #TestConfiguration.
I needed to exclude an auditing #Aspect #Component from the app context but only for a few test classes. I ended up using #Profile("audit") on the aspect class; including the profile for normal operations but excluding it (don't put it in #ActiveProfiles) on the specific test classes.
Your custom filer has wrong logic.
Because metadataReader.getClass() is always not equal to Foo.class, metadataReader.getClass() return "org.springframework.core.type.classreading.MetadataReader". It'd use className for comparation.
public class CustomFilter implements TypeFilter {
#Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return metadataReader.getClassMetadata().getClassName().equals(Foo.class.getName());
}
}
#ComponentScan.Filter is belong every #ComponentScan and only filter compononets from the specific scan path.
If you find it doesn't work, you could check whether there's another #ComponentScan or <context:component-scan base-package="xxx"/> in your code

Categories

Resources