Differences between #MapperScan and #Bean MapperScannerConfigurer - java

I have a multi-datasource web application with following technique:
Spring boot 1.5.12
Mybats-Spring-boot-starter 1.3.2
And I prefered Java based configuration. Therefore, I have Datasource1Config.java and Datasource2Config.java.
I defined SqlSessionTemplate respectively, and using MapperScannerConfigure to inject my mapper. Following is for datasource1, and the datasource2 just substitute the number.
#Bean(name = "dataSource1MapperScannerConfigurer")
public MapperScannerConfigurer msc() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setSqlSessionFactoryBeanName("dataSource1SqlSessionFactory");
msc.setSqlSessionTemplateBeanName("dataSource1SqlSessionFactory");
msc.setBasePackage("demo.mybatisspring.mapper.ds1");
return msc;
}
And then, the error happend
file [C:\...target\classes\demo\mybatisspring\mapper\ds1\UserMapper.class] required a single bean, but 2 were found:
- dataSource1SqlSessionFactory: defined by method 'sqlSessionFactoryBean' in class path resource [demo/mybatisspring/config/DataSource1Config.class]
- dataSource2SqlSessionFactory: defined by method 'sqlSessionFactoryBean2' in class path resource [demo/mybatisspring/config/DataSource2Config.class]
However, if I inject mappers with #MapperScan as following, everything will work fine. (also worked fine when one using #MapperScan and the other using #Bean MapperScannerConfigurer)
#MapperScan(basePackages = "demo.mybatisspring.mapper.ds1", sqlSessionTemplateRef = "dataSource1SqlSessionFactory")
public class DataSource1Config {...}
#MapperScan(basePackages = "demo.mybatisspring.mapper.ds2", sqlSessionTemplateRef = "dataSource2SqlSessionFactory")
public class DataSource2Config {...}
I've tried to trace with debug mode and search so many articles on internet, still can not get the answer instead. So if anyone can help me?
Thanks for your time.

I think answer is here. https://mybatis.org/spring/mappers.html
Scanning for mappers There is no need to register all your mappers one
by one. Instead, you can let MyBatis-Spring scan your classpath for
them.
There are three different ways to do it:
Using the element. Using the annotation #MapperScan
Using a classic Spring xml file and registering the
MapperScannerConfigurer Both and #MapperScan are
features introduced in MyBatis-Spring 1.2.0. #MapperScan requires
Spring 3.1+.
Since 2.0.2, mapper scanning feature support a option
(lazy-initialization) that control lazy initialization
enabled/disabled of mapper bean. The motivation for adding this option
is supporting a lazy initialization control feature supported by
Spring Boot 2.2. The default of this option is false (= not use lazy
initialization). If developer want to use lazy initialization for
mapper bean, it should be set to the true expressly.

Related

How to load beans according to Spring java conf from jar dynamically?

I have such app conf in, jar which will be added to the classpath after startup:
#Configuration
#ComponentScan("com.transportexchangegroup.testConf")
public class AppConf {
}
How to load beans dynamically? I saw solutionsm when it is required to write and add bean definitions, but if we do not know everything about new beans and just want to load them automatically?
Class conf = jarService.loadClass("com.x.testConf.AppConf");
((AnnotationConfigServletWebServerApplicationContext) applicationContext).register(conf);
((AnnotationConfigServletWebServerApplicationContext) applicationContext).refresh();
As I see, refresh() turns off web app started locally from IDE.
Do you know any other solutions or what is wrong? Will this work for spring rest controllers from jar?
I'm not sure what do you mean "dynamically".
In general you can load beans if some condition applies, usually depending on the configuration. So you can do something like this:
application.properties: // or yaml it doesn't matter
feature.enabled=true
#Component
#ConditionalOnProperty(name="feature.enabled", havingValue="true", matchIfMissing="true" / "false") // matchIfMissing depends on whether you want the bean to be loaded if the property is not defined
public MyBean {
}
Some caveats:
If you have many beans that depend on "business" feature in order to avoid placing #ConditionalOnProperty you can do one of the following:
Define your own #Component annotation:
// runtime retention, place on class
#Component
#ConditionalOnProperty(...)
#MyFeatureComponent
... and use it in all the beans that define the feature:
#MyFeatureComponent
public class MyBean
{}
Use Java Configuration instead of annotations:
#Configuration
#ConditionalOnProperty(...)
public class MyFeatureConfiguration {
#Bean
public MyBean myBean(){return new MyBean();}
#Bean
public MyAnotherBean myAnotherBean(){return new MyAnotherBean();}
}
In this case you don't need to place any annotation on MyBean at all.
Spring also has a concept of profiles which is just the same, something that it under the hood implemented with these conditionals.
It allows however to define configuration files per profile, so you might want to read about #Profile annotation as well.
As for the bean definitions - this is way more advanced stuff, in general when spring loads it "recognizes" which bean should be loaded and in which order and for doing that it creates a bean definition before loading the bean. So if you hook into this process you can define your own bean definitions if you want and spring will create beans based on these definitions as well. So basically its a hook that allows altering the bean defitions / create new one during the startup process and hence affect the actual beans that will be loaded into the application context.
I doubt, but if you really need that, read about Bean Factory Post Processors in spring.

Spring Boot 2 - Wire Two LDAP Templates

I need to configure multiple LDAP data sources / LdapTemplates in my Spring Boot 2 application. The first LdapTemplate will be used for most of the work, while the second will be used for a once-in-a-while subset of data (housed elsewhere).
I have read these StackOverflow questions regarding doing that, but they seem to be for Spring Boot 1.
Can a spring ldap repository project access two different ldap directories?
Multiple LDAP repositories with Spring LDAP Repository
From what I can gather, much of that configuration/setup had to be done anyway, even for just one LDAP data source, back in Spring Boot 1. With Spring Boot 2, I just put the properties in my config file like so
ldap.url=ldap://server.domain.com:389
ldap.base:DC=domain,DC=com
ldap.username:domain\ldap.svc.acct
ldap.password:secret
and autowire the template in my repository like so
#Autowired
private final LdapTemplate ldapTemplate;
and I'm good to go. (See: https://stackoverflow.com/a/53474188/3669288)
For a second LDAP data source, can I just add the properties and configuration elements for "ldap2" and be done (see linked questions)? Or does adding this configuration cause Spring Boot 2's auto configuration to think I'm overriding it and so now I lose my first LdapTemplate, meaning I now need to go explicitly configure that as well?
If so, do I need to configure everything, or will only a partial configuration work? For example, if I add the context source configuration and mark it as #Primary (does that work for LDAP data sources?), can I skip explicitly assigning it to the first LdapTemplate? On a related note, do I still need to add the #EnableLdapRepositories annotation, which is otherwise autoconfigured by Spring Boot 2?
TLDR: What's the minimum configuration I need to add in Spring Boot 2 to wire in a second LdapTemplate?
This takes what I've learned over the weekend and applies it as an answer to my own question. I'm still not an expert in this so I welcome more experienced answers or comments.
The Explanation
First, I still don't know for certain if I need the #EnableLdapRepositories annotation. I don't yet make use of those features, so I can't say if not having it matters, or if Spring Boot 2 is still taking care of that automatically. I suspect Spring Boot 2 is, but I'm not certain.
Second, Spring Boot's autoconfigurations all happen after any user configurations, such as my code configuring a second LDAP data source. The autoconfiguration is using a couple of conditional annotations for whether or not it runs, based on the existence of a context source or an LdapTemplate.
This means that it sees my "second" LDAP context source (the condition is just that a context source bean exists, regardless of what its name is or what properties it is using) and skips creating one itself, meaning that I no longer have that piece of my primary data source configured.
It will also see my "second" LdapTemplate (again, the condition is just that an LdapTemplate bean exists, regardless of what its name is or what context source or properties it is using) and skip creating one itself, so I again no longer have that piece of my primary data source configured.
Unfortunately, those conditions mean that in this case there is no in-between either (where I can manually configure the context source, for example, and then allow the autoconfiguration of the LdapTemplate to still happen). So the solution is to either make my configuration run after the autoconfiguration, or to not leverage the autoconfiguration at all and set them both up myself.
As for making my configuration run after the autoconfiguration: the only way to do that is to make my configuration an autoconfiguration itself and specify its order to be after Spring's built-in autoconfiguration (see: https://stackoverflow.com/a/53474188/3669288). That's not appropriate for my use case, so for my situation (because Spring Boot's setup does make sense for a standard single-source situation) I'm stuck forgoing the autoconfiguration and setting them both up myself.
The Code
Setting up two data sources is pretty well covered in the following two answers (though partly for other reasons), as linked in my question, but I'll also detail my setup here.
Can a spring ldap repository project access two different ldap directories?
Multiple LDAP repositories with Spring LDAP Repository
First up, the configuration class needs to be created, as one was not previously needed at all with Spring Boot 2. Again, I left out the #EnableLdapRepositories annotation partly because I don't use it yet, and partly because I think Spring Boot 2 will still cover that for me. (Note: All of this code was typed up in the Stack Overflow answer box as I don't have a development environment where I'm writing this, so imports are skipped and the code may not be perfectly compilable and function correctly, though I hope it's good.)
#Configuration
public class LdapConfiguration {
}
Second is manually configuring the primary data source; the one that used to be autoconfigured but no longer will be. There is one piece of Spring Boot's autoconfiguration that can be leveraged here, and that is its reading in of the standard spring.ldap.* properties (into a properties object), but since it wasn't given a name, you have to reference it by its fully qualified class name. This means you can skip straight to setting up the context source for the primary data source. This code is not quite as full featured as the actual autoconfiguration code (See: Spring Code)
I marked this LdapTemplate as #Primary because for my use, this is the primary data source and so it's what all other autowired calls should default to. This also means you don't need a #Qualifier where you autowire this source up (as seen later).
#Configuration
public class LdapConfiguration {
#Bean(name="contextSource")
public LdapContextSource ldapContextSource(#Qualifier("spring.ldap-org.springframework.boot.autoconfigure.ldap.LdapProperties") LdapProperties properties) {
LdapContextSource source = new LdapContextSource();
source.setUrls(properties.getUrls());
source.setUserDn(properties.getUsername());
source.setPassword(properties.getPassword());
source.setBaseEnvironmentProperties(Collections.unmodifiableMap(properties.getBaseEnvironment()));
return source;
}
#Bean(name="ldapTemplate")
#Primary
public LdapTemplate ldapTemplate(#Qualifier("contextSource") LdapContextSource source) {
return new LdapTemplate(source);
}
}
Third is to manually configure the secondary data source, the one that caused all of this to begin with. For this one, you do need to configure the reading of your properties into an LdapProperties object. This code builds on the previous code, so you can see the complete class for context.
#Configuration
public class LdapConfiguration {
#Bean(name="contextSource")
public LdapContextSource ldapContextSource(#Qualifier("spring.ldap-org.springframework.boot.autoconfigure.ldap.LdapProperties") LdapProperties properties) {
LdapContextSource source = new LdapContextSource();
source.setUrls(properties.getUrls());
source.setUserDn(properties.getUsername());
source.setPassword(properties.getPassword());
source.setBaseEnvironmentProperties(Collections.unmodifiableMap(properties.getBaseEnvironment()));
return source;
}
#Bean(name="ldapTemplate")
#Primary
public LdapTemplate ldapTemplate(#Qualifier("contextSource") LdapContextSource source) {
return new LdapTemplate(source);
}
#Bean(name="ldapProperties2")
#ConfigurationProperties("app.ldap2")
public LdapProperties ldapProperties2() {
return new LdapProperties();
}
#Bean(name="contextSource2")
public LdapContextSource ldapContextSource2(#Qualifier("ldapProperties2") LdapProperties properties) {
LdapContextSource source = new LdapContextSource();
source.setUrls(properties.getUrls());
source.setUserDn(properties.getUsername());
source.setPassword(properties.getPassword());
source.setBaseEnvironmentProperties(Collections.unmodifiableMap(properties.getBaseEnvironment()));
return source;
}
#Bean(name="ldapTemplate2")
public LdapTemplate ldapTemplate2(#Qualifier("contextSource2") LdapContextSource source) {
return new LdapTemplate(source);
}
}
Finally, in your class that uses these LdapTemplates, you can autowire them as normal. This uses constructor autowiring instead of the field autowiring the other two answers used. Either is technically valid though constructor autowiring is recommended.
#Component
public class LdapProcessing {
protected LdapTemplate ldapTemplate;
protected LdapTemplate ldapTemplate2;
#Autowired
public LdapProcessing(LdapTemplate ldapTemplate, #Qualifier("ldapTemplate2") LdapTemplate ldapTemplate2) {
this.ldapTemplate = ldapTemplate;
this.ldapTemplate2 = ldapTemplate2;
}
}
TLDR: Defining a "second" LDAP data source stops the autoconfiguration of the first LDAP data source, so both must be (nearly fully) manually configured if using more than one; Spring's autoconfiguration can not be leveraged even for the first LDAP data source.

Spring-boot with Tapestry configuration

Does anyone have any reference project with Spring-boot and Tapestry5? I can't seem to find any samples.
I have diificulties in configuring the tapestry bean & Context using a #configuration class without an web.xml.
This should be what you're looking for https://github.com/trust-wenderson/tapestry-spring
I've been looking for such an example myself, and I happen to have found one today:
https://github.com/code8/tapestry-boot
The tapestry-boot library implements following features:
bootstraps tapestry framework inside embedded servlet container managed by spring-boot
provides injection of spring-services into tapestry-services
provides injection of tapestry-services into spring-services
To use tapestry-boot simple marks tapestry application module with
#TapestryApplication annotation.
See DemoApplicationTests.
This works for me:
Register TapestrySpringFilter
#Bean
public FilterRegistrationBean tapesSpringFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new TapestrySpringFilter());
registration.setName("app");
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
return registration;
}
Add context aprameters to application.yml
server:
context_parameters:
tapestry:
use-external-spring-context: true
app-package: com.test.admin
development-modules: ...
qa-modules: ...
I also had to use executable War to avoid problems with loading static files.
Perhaps this approach will not allow you to access the tapestry beans from the spring context.

How do I specify multiple templateLoaderPaths for Freemarker in Spring Boot?

I need to specify multiple template loader paths for FreeMarker in a Spring Boot web application but the FreeMarkerAutoConfigurationClass only let me specify one path using the spring.freemarker.templateLoaderPath property, which uses the setTemplateLoaderPath method in the FreeMarkerConfigurationFactory. However, this class allows me to set multiple path using the setTemplateLoaderPaths method. Which is the best way to override this auto-configuration class and specify multiple loader paths? I don't really understand well the Spring Java config classes and I want an example for this before write the code I need. I'm using Spring Boot 1.1.2. Thanks in advance.
You'll need to provide your own bean of type org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer with your desired configuration. To do so, add something similar to the following to one of your application's Java configuration classes:
#Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPaths("one", "two", "three");
// Apply further configuration as needed
return configurer;
}
Update: the latest Spring Boot 1.2 snapshots now accept a comma-separated list for the spring.freemarker.templateLoaderPath property allowing you to specify multiple paths without declaring a custom FreeMarkerConfigurer bean.

Using placeholders in Annotations on Spring Java Configurations

I'm a little bit lost in Spring's Property Replacement mechanism. Lets say I have this Java Config
#Configuration
#ComponentScan(basePackageClasses = Application.class)
#PropertySources({
#PropertySource("classpath:/config/default.properties")
})
public class ApplicationConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
return pspc;
}
Now I want to add a Spring-Data Annotation #EnableMongoRepositories and define a custom basepackage for scanning, using a custom placeholder like follows
#EnableMongoRepositories("${my.custom.repobasepackage}"). The Placeholder is defined in my default.properties.
However, this property cannot be resolved here. When diving deeper into Spring's property replacement, I can see that it tries to resolve the property, so it is possible to do so.
However, the underlying Environment class which is used to replace the placeholder does not know about my PropertyPlaceholderConfigurer, but only know about my SystemProperties and my VM-Props. :-(
I can see that in org.springframework.context.annotation.ClassPathBeanDefinitionScanner#getOrCreateEnvironment.java#339 (I'm using Spring 4.0.1) my "PropertyPlaceholder" is already in place, so its not a question of ordering in initialization, but is not used, because the used BeanDefinitionRegistry does not implement the Interface EnvironmentCapable. Here, my understanding of the Spring App-Context Bootstrapping is at the end.
Can anybody help me out here? Is there a BeanDefinitionRegistry out there which is capable of providing the Environment Instance which uses my Property Placeholder?
Any help is highly appreciated!! I got cookies for you! :-))
Cheers, Stefan
I imagine that the #PropertySources (you only have one by the way so you don't need the wrapper) are added to the Environment after the config classes are processed, so it will be too late to resolve on one of the annotations of those classes themselves. You can verify this by setting my.custom.repobasepackage as a System property.
As an alternative I encourage you to try out Spring Boot (where application.properties is added to the Environment before any configuration is processed).

Categories