Add some extra configuration to beans defined in Spring starters - java

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 );
}
}

Related

#Transactional Services

I passed one day and a half looking for answer, but this thing is going to put me crazy!
My teammates and I are working on an project, based on springboot. I work specificaly on the administration part, which is a web administration.
There are mainly three layers on my project: Controllers which use Services which use Repositories.
I want my project work with #Transactional for the Service layer (we made some successful efforts until now to use only annotations for configuration).
But, it seems that it doesn't work: One of my service throws a RuntimeException and no rollback is done. I allready read all the proposition in the others sibling subjects. The only thing, related to my problem, that i'm not sure to do neatly is the contexts configuration. Eventhow, i'm not sure that it's really my problem.
I show you the actual configuration:
#SpringBootApplication
#EnableScheduling
#EnableTransactionManagement
public class Application extends SpringBootServletInitializer {
#Value("${ajp.port}")
private int ajpPort;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {};
tomcat.addAdditionalTomcatConnectors(createConnector(ajpPort));
return tomcat;
}
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return container -> {
ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/static/401.html");
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/static/404.html");
container.addErrorPages(error401Page, error404Page);
};
}
#Bean
public EmailValidator emailValidator() {
return EmailValidator.getInstance();
}
private static Connector createConnector(int ajpPort) {
Connector connector = new Connector("AJP/1.3");
connector.setPort(ajpPort);
return connector;
}
}
The web config:
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
#Autowired
private RequestProcessingTimeInterceptor requestProcessingTimeInterceptor;
#Autowired
private CertificateInterceptor certificateInterceptor;
#Autowired
private ProfilesAuthorizationInterceptor profilesAuthorizationInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestProcessingTimeInterceptor);
registry.addInterceptor(certificateInterceptor);
registry.addInterceptor(profilesAuthorizationInterceptor);
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setExposeContextBeansAsAttributes(true);
resolver.setPrefix("/WEB-INF/");
resolver.setSuffix(".jsp");
return resolver;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/admin/css/**").addResourceLocations("/WEB-INF/admin/css/").setCachePeriod(CACHE_PERIOD);
registry.addResourceHandler("/admin/img/**").addResourceLocations("/WEB-INF/admin/img/").setCachePeriod(CACHE_PERIOD);
registry.addResourceHandler("/admin/js/**").addResourceLocations("/WEB-INF/admin/js/").setCachePeriod(CACHE_PERIOD);
registry.addResourceHandler("/admin/plugins/**").addResourceLocations("/WEB-INF/admin/plugins/").setCachePeriod(CACHE_PERIOD);
}
}
A Controler-like:
#RestController
#RequestMapping("/pathA")
public class ControlerA {
#Autowired
public ServiceA serviceA;
#RequestMapping(value = "{id}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public A getA(#PathVariable long id) {
return serviceA.getA(id);
}
}
A Service-like (interface + implémentation):
public interface ServiceA {
A getA(long id);
}
#Service
#Transactional
public class ServiceAImpl implements ServiceA {
#Autowired
public RepositoryA repositoryA;
public A getA(long id) {
(...)
A a = repositoryA.findOne(id);
a.updatesomething(something);
repositoryA.update(a);
doOtherThing(a); //throw RuntimeException
(...)
return a;
}
}
And the Repository:
#Repository
public interface RepositoryA extends JpaRepository<A, Long> {
(...)
}
Here is the configuration of the MySQL database:
# Configuration de la base de donnée
spring.datasource.url=jdbc:mysql://localhost/name_innodb
spring.datasource.username=username
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.testOnBorrow=true
spring.datasource.validationQuery=SELECT 1
I know that repository transaction works by default (I saw it, when a SQLException happen). But in the service layer, nothing happen (cf. the throwing exception line) ; when the exception is thrown, the update is done and not rollback. Then it mean that my #Transactional is ignored.
Edit :
I manage to get a transaction like I want, adding #Transactional on the method getA(...) of the Controller. It works, but it's not the place to manage Transaction.
Then my question is: How can I make it work?
Ok, after some days of brainstorming, I found!
The only reasonnable answer is to take care about your Configuration class. My problem was only a crossover configuration problem which leaded to a DispatcherServlet configuration who caused the mess.
Related Subject: For web MVC Spring app should #Transactional go on controller or service?
Edit:
I add some details because it'll be hard to find some information in order to separate context. And I'm still calibrating the configuration because there's no complete and exhaustive information about all the spring's annotations.
You could create parent and child context like this:
#SpringBootApplication
#ComponentScan({"com.mycompany.service", "com.mycompany.interceptors","com.mycompany.manager"})
#PropertySource("file:config/application.properties")
public class ParentConfig{
public static void main(String[] args) {
new SpringApplicationBuilder()
.parent(ParentConfig.class)
.child(ChildConfig1.class, ChildConfig2.class, ChildConfig3.class, ..., ChildConfigN.class)
.run(args);
}
(...)
}
I'm still wondering why I must add the #PropertySource in order children are aware of property values, why "classpath:path" have not work in #PropertySource, why I have to add a static PropertySourcesPlaceholderConfigurer for using #Value in my children (before I do that, i.e without this hierarchical contexts, every context was aware of the properties)
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
and I'm still playing with annotations in order every configuration work.
Edit:
I finally have found something in order to work correctly with spring configuration: Different configurations must respect packaging hierarchy.
I stop working with parent and child configuration and let spring work. I ordonate my different config class like this:
MainConfig
|
|__________my.package.mvc.MVCConfig
|
|__________my.package.schedulers.SchedulerConfig
|
|
and so on..
And in my MainConfig I add:
#ComponentScan({"my.package.mvc", "my.package.services", "my.package.interceptors","my.package.managers", "my.package.schedulers"})
And everything is good now! Mostly, MVCConfig can not create conflict with services, because of the different hierarchy.

Spring-MVC + Spring-websocket + #Cacheable don't work

I have got a project on Spring-MVC and Spring-websockets and I try to plug cache on my service layer. These are my configuration:
#Configuration
#ComponentScan(basePackages = {
"com.example"
})
#PropertySource("classpath:/configuration.properties")
#EnableWebMvc
#EnableAspectJAutoProxy(proxyTargetClass = true)
#EnableCaching
public class WebAppConfig extends WebMvcConfigurerAdapter {
#Bean
public EhCacheManagerFactoryBean ehcache() {
EhCacheManagerFactoryBean ehCache = new EhCacheManagerFactoryBean();
ehCache.setConfigLocation(new ClassPathResource("ehcache.xml"));
ehCache.setShared(true);
return ehCache;
}
#Bean
public CacheManager cacheManager() {
return new EhCacheCacheManager(ehcache().getObject());
}
//...different settings by mvc
}
and my websocket configuration:
#Configuration
#EnableAsync
#EnableWebSocket
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/queue/", "/topic/");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/locations").withSockJS();
}
#Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
registration.taskExecutor().corePoolSize(4).maxPoolSize(10);
}
}
I want to use #Cacheable annotation on my service layer:
#Service
public class StoreServiceImpl implements StoreService {
private static final Log logger = LogFactory.getLog(StoreServiceImpl.class);
#Autowired
private StoreRepository storeRepository;
#Override
#Cacheable("stores")
public Store findById(String storeId) {
return storeRepository.findById(storeId);
}
//... others methods
}
but if I have included annotation #EnableWebSocketMessageBroker then the cache doesn't work, because aop interceptors do not use it, so
if I haven't included #EnableWebSocketMessageBroker then cache and AOP interceptors work well.
The documentation on the websocket I found this information:
In some cases a controller may need to be decorated with an AOP proxy
at runtime. One example is if you choose to have #Transactional
annotations directly on the controller. When this is the case, for
controllers specifically, we recommend using class-based proxying.
This is typically the default choice with controllers. However if a
controller must implement an interface that is not a Spring Context
callback (e.g. InitializingBean, *Aware, etc), you may need to
explicitly configure class-based proxying. For example with
<tx:annotation-driven />, change to <tx:annotation-driven proxy-target-class="true" />
I tried use #EnableCaching(proxyTargetClass = true), but it didn't help.
Has anyone encountered this problem?
I decided this problem:
I changed mode in #EnableAsync(mode = AdviceMode.ASPECTJ) and it works.
I think it depends of the order initialization BeanPostProcessors

Java config for spring interceptor where interceptor is using autowired spring beans

I want to add spring mvc interceptor as part of Java config. I already have a xml based config for this but I am trying to move to a Java config. For interceptors, I know that it can be done like this from the spring documentation-
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleInterceptor());
}
}
But my interceptor is using a spring bean autowired into it like follows-
public class LocaleInterceptor extends HandlerInterceptorAdaptor {
#Autowired
ISomeService someService;
...
}
The SomeService class looks like follows-
#Service
public class SomeService implements ISomeService {
...
}
I am using annotations like #Service for scanning the beans and have not specified them in the configuration class as #Bean
As my understanding, since java config uses new for creating the object, spring will not automatically inject the dependencies into it.
How can I add the interceptors like this as part of the java config?
Just do the following:
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
LocaleInterceptor localInterceptor() {
return new LocalInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeInterceptor());
}
}
Of course LocaleInterceptor needs to be configured as a Spring bean somewhere (XML, Java Config or using annotations) in order for the relevant field of WebConfig to get injected.
The documentation for general customization of Spring's MVC configuration can be found here, and specifically for Interceptors see this section
When you handle the object creation for yourself like in:
registry.addInterceptor(new LocaleInterceptor());
there is no way the Spring container can manage that object for you and therefore make the necessary injection into your LocaleInterceptor.
Another way that could be more convenient for your situation, is to declare the managed #Bean in the #Configuration and use the method directly, like so:
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
public LocaleInterceptor localeInterceptor() {
return new LocaleInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor( localeInterceptor() );
}
}
Try to inject your service as a constructor parameter. It is simple.
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Autowired
ISomeService someService;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleInterceptor(someService));
}
}
Then reconfigure your interceptor,
public class LocaleInterceptor extends HandlerInterceptorAdaptor {
private final ISomeService someService;
public LocaleInterceptor(ISomeService someService) {
this.someService = someService;
}
}
Cheers !

Is it possible to extend WebMvcConfigurationSupport and use WebMvcAutoConfiguration?

I need to extend the WebMvcConfigurationSupport class too modify two things:
#Configuration
public class WebConfig extends WebMvcConfigurationSupport {
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping handlerMapping = super.requestMappingHandlerMapping();
handlerMapping.setRemoveSemicolonContent(false);
handlerMapping.setOrder(1);
return handlerMapping;
}
}
I like the defaults that are registered from the WebMvcAutoConfiguration class but due to the conditional annotation on the class, when I extend the WebMvcConfigurationSupport class it prevents the auto configuration from happening.
#Configuration
#ConditionalOnWebApplication
#ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
#ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
#Order(Ordered.HIGHEST_PRECEDENCE + 10)
#AutoConfigureAfter(DispatcherServletAutoConfiguration.class)
public class WebMvcAutoConfiguration {...}
Is there to have the WebMvcAutoConfiguration class load without having to essentially copy/paste most of the code in that class?
Or is it possible to call RequestMappingHandlerMapping setOrder() and setRemoveSemicolonContent() from somewhere else so I can just use the #EnableWebMvc annotation and have the autoconfiguration class run without any issues?
Thanks in advance!
Extend from DelegatingWebMvcConfiguration instead of WebMvcConfigurationSupport, it will not prevent the autoconfig to take place:
#Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping handlerMapping = super.requestMappingHandlerMapping();
handlerMapping.setRemoveSemicolonContent(false);
handlerMapping.setOrder(1);
return handlerMapping;
}
}
I managed to customize the RequestMappingHandlerMapping while keeping WebMvcAutoConfiguration using a BeanPostProcessor:
#Configuration
public class RequestMappingConfiguration {
#Bean
public RequestMappingHandlerMappingPostProcessor requestMappingHandlerMappingPostProcessor() {
return new RequestMappingHandlerMappingPostProcessor();
}
public static class RequestMappingHandlerMappingPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof RequestMappingHandlerMapping) {
((RequestMappingHandlerMapping) bean).setUseSuffixPatternMatch(false);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
}
I would be happy if Spring Boot provides a better way to handle that... maybe something could be done around PathMatchConfigurer ?
Your analysis is correct (#EnableWebMvc or directly extending WebMvcConfigurationSupport will switch off the WebMvcAutoConfiguration). I'm not sure what the alternative is, since a) we need a "get-out" clause for the autoconfig, and b) I don't think Spring likes to have two WebMvcConfigurationSupports in the same context. Happy to discuss on github if you want to try and find a way to change it (there might be some middle ground).
I think the best way to do this in Spring Boot now is to add a WebMvcRegistrations component to your context - this solution didn't exist at the time of your question (it's been available since Spring Boot 1.4.0).

Autowired property is null - Spring Boot Configuration

I am stuck with null values in an autowired property. I am hoping I could get some help.
We are using for the project spring-boot version 0.5.0.M6.
The four configuration files with beans are in one package and are sorted by "area":
Data source configuration
Global method security configuration (as we use Spring-ACL)
MVC configuration
Spring Security configuration
The main method that bootstraps everything is in the following file:
#EnableAspectJAutoProxy
#EnableSpringConfigured
#EnableAutoConfiguration(exclude = {
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
SecurityAutoConfiguration.class,
ThymeleafAutoConfiguration.class,
ErrorMvcAutoConfiguration.class,
MessageSourceAutoConfiguration.class,
WebSocketAutoConfiguration.class
})
#Configuration
#ComponentScan
public class IntegrationsImcApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = SpringApplication.run(
IntegrationsImcApplication.c lass, args);
}
}
The first file that holds the data source configuration beans is as follows (I have omitted some method body parts to make it more readable):
#EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
#Configuration
public class RootDataSourceConfig
extends TomcatDataSourceConfiguration
implements TransactionManagementConfigurer {
#Override
public DataSource dataSource() {
return jpaDataSource();
}
public PlatformTransactionManager annotationDrivenTransactionManager() {
return jpaTransactionManager();
}
#Bean
public HibernateExceptionTranslator hibernateExceptionTranslator() {
return new HibernateExceptionTranslator();
}
#Bean(name="jpaDataSource")
public DataSource jpaDataSource() {......}
#Bean(name = {"transactionManager","txMgr"})
public JpaTransactionManager jpaTransactionManager() {......}
#Bean(name = "entityManagerFactory")
public EntityManagerFactory jpaEmf() {......}
}
And here is the next configuration file, that depends on the data source from above. It has about 20 beans related to ACL configuration, but it fails on the firsts bean that uses data source:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Configuration
public class RootGlobalMethodSecurityConfig
extends GlobalMethodSecurityConfiguration
implements Ordered {
#Autowired
public DataSource dataSource;
#Override
public int getOrder() {
return IntegrationsImcApplication.ROOT_METHOD_SECURITY_CO NFIG_ORDER;
}
#Bean
public MutableAclService aclService()
throws CacheException, IOException {
MutableJdbcAclService aclService = new MutableJdbcAclService(
dataSource, aclLookupStrategy(), aclCache());
aclService.setClassIdentityQuery("SELECT ##IDENTITY");
aclService.setSidIdentityQuery("SELECT ##IDENTITY");
return aclService;
}
...................................
}
Basically invoking aclService() throws an error as dataSource is null. We have tried ordering the configuration files by implementing the Ordered interface. We also tried using #AutoConfigureAfter(RootDataSourceConfig.class) but this did not help either. Instead of doing #Autowired on the DataSource we also tried injecting the RootDataSourceConfig class itself, but it was still null. We tried using #DependsOn and #Ordered on those beans but again no success. It seems like nothing can be injected into this configuration.
The console output at the startup is listing the beans in the order we want them, with data source being the first. We are pretty much blocked by this.
Is there anything weird or unique we are doing here that is not working? If this is as designed, then how could we inject data source differently?
Repo: github
Eager initialization of a bean that depends on a DataSource is definitely the problem. The root cause is nothing to do with Spring Boot or autoconfiguration, but rather plain old-fashioned chicken and egg - method security is applied via an aspect which is wrapped around your business beans by a BeanPostProcessor. A bean can only be post processed by something that is initialized very early. In this case it is too early to have the DataSource injected (actually the #Configuration class that needs the DataSource is instantiated too early to be wrapped properly in the #Configuration processing machinery, so it cannot be autowired). My proposal (which only gets you to the same point with the missing AuthenticationManager) is to declare the GlobalMethodSecurityConfiguration as a nested class instead of the one that the DataSource is needed in:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Configuration
protected static class ActualMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Autowired
#Qualifier("aclDaoAuthenticationProvider")
private AuthenticationProvider aclDaoAuthenticationProvider;
#Autowired
#Qualifier("aclAnonymousAuthenticationProvider")
private AnonymousAuthenticationProvider aclAnonymousAuthenticationProvider;
#Autowired
#Qualifier("aclExpressionHandler")
private MethodSecurityExpressionHandler aclExpressionHandler;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(aclDaoAuthenticationProvider);
auth.authenticationProvider(aclAnonymousAuthenticationProvider);
}
#Override
public MethodSecurityExpressionHandler createExpressionHandler() {
return aclExpressionHandler;
}
}
i.e. stick that inside the RootMethodSecurityConfiguration and remove the #EnableGlobalMethodSecurity annotation from that class.
I might have resolved the problem.
GlobalMethodSecurityConfiguration.class has the following setter that tries to autowire permission evaluators:
#Autowired(required = false)
public void setPermissionEvaluator(List<PermissionEvaluator> permissionEvaluators) {
....
}
And in my case the aclPermissionEvaluator() bean needs aclService() bean, which in turn depends on another autowired property: dataSource. Which seems not to be autowired yet.
To fix this I implemented BeanFactoryAware and get dataSource from beanFactory instead:
public class RootMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration implements BeanFactoryAware {
private DataSource dataSource;
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.dataSource = beanFactory.getBean("dataSource", DataSource.class);
}
....
}
After this, other exception showed up, whereSecurityAutoConfiguration.class is complaining about missing AuthenticationManager, so I just excluded it from #EnableAutoConfiguration. I am not sure if its ideal, but I have custom security configuration, so this way everything works ok.

Categories