What is the best way to configure PropertySourcesPlaceholderConfigurer / resource folders? - java

I have a (gradle/java/eclipse) project with what seems to me to be quite a standard folder structure
...main
java
...controller
...service
resources
webapp
...resources
WEB-INF
I'm having an issue I just don't understand although I have worked around it in a very messy way. If I specify the controllers folder in the component scan of my WebMvcConfigurerAdapter then the services classes can't pick up properties using the configured PropertySourcesPlaceholderConfigurer bean. If I broaden the component scan out the jsp files don't pick up the css includes!
So with this config class all is well but the properties aren't resolved int he service implementation class
config class
#EnableWebMvc
#Configuration
#ComponentScan({ "comm.app.controller" })
#PropertySource({ "classpath:app.properties" })
public class SpringWebConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
//used to handle html put and delete rest calls
#Bean(name = "multipartResolver")
public CommonsMultipartResolver createMultipartResolver() {
CommonsMultipartResolver resolver=new CommonsMultipartResolver();
resolver.setDefaultEncoding("utf-8");
return resolver;
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfig() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Service implementation class begins
#Service("appService")
#PropertySource({ "classpath:app.properties" })
public class appServiceImpl implements appService {
private RestTemplate restTemplate = new RestTemplate();
#Value("${property.reference}")
private String propref;
...
In this case ${property.reference} is not picked up but the view page styling (picked up from ...webapp\resources... .css) is fine.
If I change
#ComponentScan({ "comm.app.controller" })
to
#ComponentScan({ "comm.app" })
The properties are picked up (presumably because the propertyConfig bean comes into scope ?) but the local styling files can't be found ? Any link references to files webapp\resources... .css fails.
In the end I came to a bad (!?) solution of
1) keeping the scope of #ComponentScan({ "comm.app.controller" })
2) hacking the serviceimplementation class as so...
#Configuration
#Service("appService")
#PropertySource({ "classpath:app.properties" })
public class appServiceImpl implements appService {
private RestTemplate restTemplate = new RestTemplate();
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfig() {
return new PropertySourcesPlaceholderConfigurer();
}
#Value("${property.reference}")
private String propref;
...
Can anyone tell me if I've configured the path the the resources file incorrectly or maybe should be doing something differently with the propertyConfig bean ? (maybe injecting it or declaring another one in a different configuration file ?)

Related

Spring : Add properties file externally

I am working on a Spring-MVC application in which we are preparing to setup application on different servers. As each server can have it's own database related configuration, we are hoping to use an external properties file(outside the war file) which can be read while project is starting. How do I go about this approach?
For making it work, I have already moved application initialization code to Java, this way, static XML reading which we had before won't be required.
But, we are unsure how to create and add a properties file dynamically which atleast has these 3 values(JDBC URL containing DB name, username, password). All tables, other data will be created automatically.
Here is the class where app is initialized :
WebConfig.java :
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.ourapp.spring"})
#EnableTransactionManagement
#EnableCaching
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
#Bean
public ReloadableResourceBundleMessageSource messageSource(){
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("../resources/locale/messages.properties");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocaleChangeInterceptor localeInterceptor(){
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang");
return interceptor;
}
#Bean
public MappingJackson2HttpMessageConverter converter() {
return new MappingJackson2HttpMessageConverter();
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/img/**").addResourceLocations("/img/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
}
#Bean
public InternalResourceViewResolver getInternalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("../webapp/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
#Bean
public DoNotTruncateMyUrls doNotTruncate(){
return new DoNotTruncateMyUrls();
}
#Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
#Bean
#Qualifier("primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
#Bean
#Qualifier("extended_tx")
public HibernateTransactionManager txName() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(getExtendedSessionFactory().getObject());
return txName;
}
#Bean
#Qualifier("sessionFactory_origin")
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(new DataSourceConfig().primaryDataSource());
sessionFactory.setPackagesToScan("com.ourapp.spring");
return sessionFactory;
}
#Bean
#Qualifier("sessionFactory_extended")
public LocalSessionFactoryBean getExtendedSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(new DataSourceConfig_Extended().secondaryDataSource());
sessionFactory.setPackagesToScan("com.ourapp.spring");
return sessionFactory;
}
}
Thank you. :-)
Probably what you are looking for is Profiles( #Profile or #Conditional )
Step1: Create a profile. The following is the example for prod profile. Similarly, you can create one for dev and qa
import javax.activation.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jndi.JndiObjectFactoryBean;
#Configuration
#Profile("prod")
#PropertySource("classpath:/com/<SomePath>/app.properties")
public class ProductionProfileConfig {
#Autowired Environment env;
#Bean
public DataSource dataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName(env.getProperty("dbName"));
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
Step2 Activate profiles
Spring honors two separate properties when determining which profiles
are active: spring.profiles.active and spring.profiles.default. If
spring.profiles.active is set, then its value determines which
profiles are active. But if spring .profiles.active isn’t set, then
Spring looks to spring.profiles.default. If neither
spring.profiles.active nor spring.profiles.default is set, then there
are no active profiles, and only those beans that aren’t defined as
being in a profile are created
UPDATE
Use PropertyConfigurator.configure(Loader.getResource(<your-file-path>)); if the file is located outside the packaged war. Later you can simply inject values using #Value or SPel
Write a config manager, which will create configuration objects based on the name of properties file. For example,
Configuration config = ConfigurationManager.getConfig("dbConfig");
so, now your config object will contain all properties related to db. When you instantiate config object read all properties into this object.
Let's say your properties file contains following fields:
user.name = "Tom"
user.password = "pass"
Next time when you need "user.name", you would just do config.getString("user.name")
You can do this using PropertySourcesPlaceholderConfigurer
Create a bean like this
#Bean
public static PropertySourcesPlaceholderConfigurer devPropertyPlaceholderConfigurer() throws IOException {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocations(new PathMatchingResourcePatternResolver().getResources("file:pathtToFile"));
configurer.setIgnoreUnresolvablePlaceholders(true);
return configurer;
}
For example if you write file:/tmp/dev/*.properties . It will load all the properties file under /tmp/dev.
If you want to customize it based on differnt environment(dev, testing, production). then you can use #Profile and create multiple beans.
#Bean
#Profile("dev")
public static PropertySourcesPlaceholderConfigurer devPropertyPlaceholderConfigurer() throws IOException {
..... // give dev properties file path
}
#Bean
#Profile("testing")
public static PropertySourcesPlaceholderConfigurer testPropertyPlaceholderConfigurer() throws IOException {
.....// give test properties file path
}
#Bean
#Profile("prod")
public static PropertySourcesPlaceholderConfigurer prodPropertyPlaceholderConfigurer() throws IOException {
.....// give prod properties file path
}

How to set InternalResourceViewResolver prefix with value from database?

I have a CMS based on Spring MVC 4 and I want user to choose from different visual themes. They are stored in separate folders.
For now theme name is hardcoded in Properties.THEME_NAME, but I want to make the value stored in database and loaded as prefix part for InternalResourceViewResolver. So user can change it and switch to another theme. Is it possible to make this changes dinamically when app is running?
My code for configuration:
#EnableWebMvc
#Configuration
#ComponentScan({ "shop.main.*" })
#Import({ SecurityConfig.class })
#PropertySource("classpath:application.properties")
public class AppContextConfig extends WebMvcConfigurerAdapter {
#Bean
public InternalResourceViewResolver internalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/pages/" + Properties.THEME_NAME + "/");
resolver.setSuffix(".jsp");
return resolver;
}
// other methods
}
One way of achieving my goal is to extend InternalResourceViewResolver and override getter method to return prefix loaded dinamicaly.
So configuration changes to:
#Bean
public ThemedResourceViewResolver internalResourceViewResolver() {
ThemedResourceViewResolver resolver = new ThemedResourceViewResolver();
resolver.setSuffix(".jsp");
return resolver;
}
And the custom resolver class:
public class ThemedResourceViewResolver extends InternalResourceViewResolver {
#Autowired
private SitePropertyDAO sitePropertyDAO;
protected String getPrefix() {
String prefix = "/pages/" + Properties.THEME_NAME + "/";
SiteProperty property = sitePropertyDAO.findOneByName(Constants.THEME);
if (property != null) {
prefix = "/pages/" + property.getContent() + "/";
}
return prefix;
}
}
I used this approach because I need not only to change the CSS files path or properties, but folders where my .jsp views are stored, so different themes have different layouts.

Add some extra configuration to beans defined in Spring starters

TL;DR
What is the best way to add some configuration to already created beans, e.g. to bean created by Spring Auto Configuration mechanism?
UseCase
I'm trying to configure a ContentNegotiatingViewResolver in a best possible way. I tried to create a new instance of that object in my MvcConfiguration class, and configure everything in that place. It worked, but I was thinking about something more elegant.
And I found a WebMvcAutoConfiguration, with viewResolver(BeanFactory beanFactory) method, that creates a ContentNegotiatingViewResolver bean. I decided I would like to use this, since it's better to use existing code, than duplicate it.
But how can I add some more configuration to that bean? I tried with something like this:
#Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter {
#Autowired
private ContentNegotiatingViewResolver contentNegotiatingViewResolver;
#Autowired
private ThymeleafViewResolver thymeleafViewResolver;
#Bean
public ViewResolver jsonViewResolver() {
return new JsonViewResolver();
}
#PostConstruct
public void postConstruct() {
contentNegotiatingViewResolver.setViewResolvers(Arrays.asList(jsonViewResolver(), thymeleafViewResolver));
}
}
and configure everything in #PostConstruct method but I wonder, if it is the best way.
Make use of Autowiring by constructor.
#Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter {
private ContentNegotiatingViewResolver contentNegotiatingViewResolver;
private ThymeleafViewResolver thymeleafViewResolver;
#Autowired
public MvcConfiguration(ContentNegotiatingViewResolver contentNegotiatingViewResolver,ThymeleafViewResolver thymeleafViewResolver){
this.contentNegotiatingViewResolver=contentNegotiatingViewResolver;
this.thymeleafViewResolver=thymeleafViewResolver;
ViewResolver jsonViewResolver= new JsonViewResolver();
this.contentNegotiatingViewResolver.setViewResolvers(Arrays.asList(jsonViewResolver, thymeleafViewResolver));
}
}
Alternate solution :
#Configuration
#EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation();
registry.viewResolver(jsonViewResolver());
super.configureViewResolvers(registry );
}
}

How to include my class into Spring context?

We're running a Spring setup where I don't see any XML config files, seems everything is done via annotation.
I've got some custom component classes in a specific package I need added to the spring context for autowiring and I annotated the class with #Component but it's not making a difference. Am I missing another annotation?
There is one loop I have where I needed to do a component scan to discover all the classes in the package, maybe I can just add them there since I'd already have a BeanDefinition handle on them. If so, what would I have to do?
for (BeanDefinition bd : scanner.findCandidateComponents("com.blah.target")) {
// how to add it to context here?
}
If you don't see any XML config file, then the project should have a package springconfig with a java file called WebConfig.java. This is exact equivalent of XML config file.
Below is a snippet of a typical Webconfig.java
package .....springconfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
<...>
#Configuration
#EnableWebMvc
#ComponentScan(basePackages="<your source package>")
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry){
String dir="/resources/";
registry.addResourceHandler("/images/**").addResourceLocations(dir + "images/");
...
}
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/view/");
resolver.setSuffix(".jsp");
return resolver;
}
#Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(100);
return resolver;
}
}
Check out this tutorial: Simple Spring MVC Web Application It is very nicely explained here.

Spring bean messageSouce cannot be in ApplicationConfig and WebMvcConfig at the same time

I need to put the #Bean SpringTemplateEngine in the ApplicationConfig so that I can #Autowire it into a #Component that generates emails. (See Thymeleaf-Spring4 unable to autowire TemplateEngine and Rich HTML email in Spring with Thymeleaf). Therefore the #Bean(name = "messageSource") needs to be in the ApplicationConfig as well. However, I have a WebMvcConfig that extends WebMvcConfigurationSupport and the method #Override Validator getValidator() needs to setValidationMessageSource with messageSource(). However, since I moved it to the ApplicationConfig I don't know how to get at it from WebMvcConfig. I started with spring-mvc-quickstart-archetype from kolorobot. Instead of finding a whole new configuration archetype, maybe someone here can point me in a helpful direction.
Relevant Code for ApplicationConfig
#Configuration
#ComponentScan(basePackageClasses = Application.class, excludeFilters = #Filter ({Controller.class, Configuration.class}))
class ApplicationConfig {
*** Incomplete Example ***
#Bean(name = "messageSource")
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename(MESSAGE_SOURCE);
messageSource.setCacheSeconds(5);
return messageSource;
}
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
Set<TemplateResolver> resolvers = new HashSet<TemplateResolver>();
resolvers.add(classLoaderTemplateResolver());
resolvers.add(mvcTemplateResolver());
templateEngine.setTemplateResolvers(resolvers);
templateEngine.setMessageSource(messageSource());
templateEngine.addDialect(new SpringSecurityDialect());
return templateEngine;
}
}
And Relevant Code for WebMvcConfig
#Configuration
#ComponentScan(basePackageClasses = {Application.class}, includeFilters = #Filter (Controller.class), useDefaultFilters = false)
class WebMvcConfig extends WebMvcConfigurationSupport {
*** Incomplete Example ***
#Override
public Validator getValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource());
return validator;
}
}
The issue seems to be that I cannot move the getValidator() to the ApplicationConfig() because it must override the WebMvcConfigurationSupport method.
I got it working by creating two separate SpringTemplateEngine configurations, one in the MvcWebConfig for view rendering and one in ApplicationConfig for handling emails. I put the email messages in a separate file and called setTemplateEngineMessageSource instead of setMessageSource as per documentation.

Categories