My application is working well, but I´m not sure why I needed to configure the way I did.
The issue is that transactions must be enabled in both contexts, root and dispatcher.
Why is this necessary?
The app is configured like this:
public class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses () {
return new Class<?>[] {RootConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses () {
return new Class<?>[] {WebMvcConfig.class};
}
}
Then
#Configuration
#Import(value = {
DataSourceConfig.class,
JpaConfig.class,
SecurityConfig.class
})
#ComponentScan(
basePackages = {"com.**.service", "com.**.mail", "com.core.config"},
excludeFilters = {
#Filter(Configuration.class),
#Filter(Controller.class),
#Filter(RestController.class)
})
#EnableAsync
#EnableScheduling
#EnableAspectJAutoProxy
#EnableTransactionManagement
public class RootConfig {
// Beans Config...
}
Finally
#Configuration
#ComponentScan(basePackages = {"com.web"})
#EnableWebMvc
#EnableTransactionManagement
public class WebMvcConfig extends WebMvcConfigurerAdapter {
// Beans Config ...
}
Note:
Transactions are just managed in the Service Layer.
That #EnableTransactionManagement is present in both config
classes.
If I remove the one in WebMvcConfig, transactions will not be injected in the service layer when accesed from a Controller class. Even though the service layer is scanned by the root context.
If I remove the one in RootConfig, transactions will not be injected in the service layer when accesed for example by Spring Security.
Is there any way to enable transactions globally?
Related
I have a camel rest in my project and a servlet is configured for it. At the moment I'm trying to add a regular RestController without camel. Can two types of rest be in the same project?
For example
#Override
public void configure() throws Exception {
rest("/dictionary/get-dictionary")
.get()
.param().name("dictionary").required(true).type(RestParamType.query).endParam()
.param().name("name").required(true).type(RestParamType.query).endParam()
.....
.endRest();
CamelServlet implemented for camel
#RestController
#RequestMapping("/createOrder")
#RequiredArgsConstructor
public class OrderController {
private final OrderService OrderService;
#PostMapping
public void createForm(#RequestBody App app) {
orderService.createFullOrder(app);
}
}
When I request createOrder, I always get 404. How can I make both types of controllers work?
Thank in advice
Solution
#Configuration
public class AppConfig {
#Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
#SuppressWarnings("rawtypes")
#Bean
public ServletRegistrationBean axisServletRegistrationBean() {
#SuppressWarnings("unchecked")
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/createOrder");
registration.addUrlMappings("/createOrder");
return registration;
}
}
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.package.package")
public class WebConfig {
}
My use case is running custom code before a controller method by annotating methods.
HandlerInterceptor seems the way to go but it seems impossible to inject dependencies into it because it needs to be registered before the context is being created.
All examples I've found so far use empty constructors (see spring boot adding http request interceptors) or autowire properties in the configuration which fails because I declare dependent beans in the same configuration (Requested bean is currently in creation: Is there an unresolvable circular reference?).
Is there a better way that does not involve AOP?
Assume that your interceptor has constructor dependencies like that:
public class CustomInterceptor extends HandlerInterceptor {
private final DependentBean bean;
public CustomInterceptor(DependentBean bean) {
this.bean = bean;
}
}
Then you can register your handler like that:
#Configuration
public WebConfig extends WebMvcConfigurerAdapater {
#Bean
public DependentBean dependentBean() {
return new DependentBean();
}
#Bean
public CustomInterceptor customInterceptor() {
return new CustomInterceptor(dependentBean());
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor());
}
}
#Configuration will ensure each Bean method call return the same bean instance
Building on the answer above from Mạnh, if using component scan for dependency injection of the dependency, then that can be Autowired in the WebConfig
#Configuration
public WebConfig extends WebMvcConfigurerAdapater {
#Autowired
DependentBean dependentBean;
#Bean
public CustomInterceptor customInterceptor() {
return new CustomInterceptor(dependentBean);
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor());
}
}
Also building on previous answers, and if you use Lombok, you can further simplify.
Have your interceptor implementation been a #Component
Add a private final DependentBean field to it.
Also add a #RequiredArgsConstructor annotation to it, to have Lombok generating a constructor with a single DependentBean parameter.
In your WebConfig, use the same technic to have a private final CustomInterceptor field been injected by Spring IOC.
This way the CustomInterceptor instance will be available & initialized the right way when addInterceptors will be called
Here are the corresponding code samples :
The CustomInterceptor :
#Component
#RequiredArgsConstructor
public class CustomInterceptor implements HandlerInterceptor {
private final DependentBean dependentBean;
#Override
public boolean preHandle( final HttpServletRequest request,
final HttpServletResponse response,
final Object handler ) throws Exception {
// your Interceptor Implementation goes here ...
}
}
The WebConfig :
#Configuration
#RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final CustomInterceptor customInterceptor;
#Override
public void addInterceptors( final InterceptorRegistry registry ) {
registry.addInterceptor( customInterceptor );
}
}
I wanted to use a configured version of Jackson ObjectMapper in my project (ignoring null values and snake_case, also using some custom modules).
In my large project I wasn't able to get Spring MVC to actually use this mapper.
The build.gradle:
buildscript {
ext {
springBootVersion = '1.5.6.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter')
compile("org.springframework.boot:spring-boot-starter-jetty:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.8'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.8'
testCompile('org.springframework.boot:spring-boot-starter-test')
}
My application.yml:
spring:
application:
name: Jackson test
jackson:
property-naming-strategy: SNAKE_CASE
default-property-inclusion: non_empty
debug: true
A container class:
public class MyLocationEntity {
public String nameAndSnake;
}
A config class:
#Configuration
#EnableWebMvc
public class AppConfig {
}
And a controller:
#RestController
#RequestMapping("/test")
public class TestController {
#Autowired
private ObjectMapper objectMapper;
#RequestMapping(value = "/test", produces = "application/json")
public MyLocationEntity test() throws JsonProcessingException {
MyLocationEntity location = new MyLocationEntity();
location.nameAndSnake = "hello world";
String expexted = objectMapper.writeValueAsString(location);
return location;
}
}
If I now look at the value of expected in the debugger it is {"name_and_snake":"hello world"}.
But if I let the controller run through the actual response is {"nameAndSnake":"hello world"}.
When I remove #EnableWebMvc it works. How can I use the configured mapper with MVC and not remove the rest of the autoconfiguration for Web MVC?
It's not evident just from the Javadocs, but #EnableWebMvc disables the Spring Boot default web MVC auto configuration provided by WebMvcAutoConfiguration, including the use of the Jackson ObjectMapper bean configured by the application.yml properties. Per the Spring Boot Reference Documentation:
9.4.7. Switch off the Default MVC Configuration
The easiest way to take complete control over MVC configuration is to provide your own #Configuration with the #EnableWebMvc annotation. Doing so leaves all MVC configuration in your hands.
Since #EnableWebMvc has the (likely surprising) behavior of disabling auto configuration, using this annotation may have unintended and undesirable side-effects. A different approach may be more appropriate.
That being said, it is possible that the behavior of #EnableWebMvc is still desired. To use application.yml properties in concert with the #EnableWebMvc annotation, the MVC configuration must be manually configured to mimic the relevant disabled Spring Boot auto-configuration. There are a few different possible approaches to this.
The first approach is to duplicate the Spring Boot configuration code from WebMvcAutoConfiguration.EnableWebMvcConfiguration.configureMessageConverters(). This will replace the message converters — including the MappingJackson2HttpMessageConverter containing the unconfigured ObjectMapper — with the ones that would have used with the default Spring Boot configuration:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
private ObjectProvider<HttpMessageConverters> messageConvertersProvider;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.messageConvertersProvider
.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
}
}
Alternatively, rather than using the default Spring Boot list of message converters, it is possible to swap in just the ObjectMapper or MappingJackson2HttpMessageConverter bean provided by Spring Boot (which have the application.yml properties applied):
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
private ObjectMapper objectMapper;
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream()
.filter(c -> c instanceof MappingJackson2HttpMessageConverter)
.map(c -> (MappingJackson2HttpMessageConverter) c)
.forEach(c -> c.setObjectMapper(objectMapper));
}
}
or
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (int i = 0; i < converters.size(); i++) {
if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {
converters.set(i, mappingJackson2HttpMessageConverter);
}
}
}
}
I have a simple spring boot app and I'm trying to get it up and running. The config consists of an app context ( applicationContext.xml) XML with a bunch of beans in it. I have a Spring application class:
#SpringBootApplication
#Configuration
#ImportResource("classpath:applicationContext.xml")
public class WebCheckApplication {
private static final Logger logger = Logger.getLogger(WebCheckApplication.class);
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(WebCheckApplication.class, args);
if (logger.isDebugEnabled()) {
logger.debug("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
logger.debug(beanName);
}
}
}
}
And I have a #WebListener class that grabs a few beans from the WebContext from within the ServletContext:
#WebListener
public class SystemPropertiesContextInitializer extends SysPropsAlertsFetcher implements ServletContextListener {
private static final Logger logger = Logger.getLogger(SystemPropertiesContextInitializer.class);
#Override
public void contextDestroyed(ServletContextEvent sce) {
//remove the SystemProperties and alert types map object from context
sce.getServletContext().removeAttribute(BaseAuthenticatedController.SYSPROPS_KEY);
sce.getServletContext().removeAttribute(BaseAuthenticatedController.ALERT_TYPES_MAP_KEY);
}
#Override
public void contextInitialized(ServletContextEvent sce) {
SysPropsDataAccess = (SystemPropertiesDataAccess) WebApplicationContextUtils.getRequiredWebApplicationContext(sce.getServletContext()).getBean("SystemPropertiesDataAccess");
AlertsDataAccess = (AlertDataAccess) WebApplicationContextUtils.getRequiredWebApplicationContext(sce.getServletContext()).getBean("AlertsDataAccess");
fetchObjects(sce.getServletContext());
}
}
When I attempt to start the app, I get the following error:
SEVERE: Exception sending context initialized event to listener instance of class web.SystemPropertiesContextInitializer
java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
at org.springframework.web.context.support.WebApplicationContextUtils.getRequiredWebApplicationContext(WebApplicationContextUtils.java:83)
at .web.SystemPropertiesContextInitializer.contextInitialized(SystemPropertiesContextInitializer.java:31)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4994)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5492)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
and it occurs at this line:
SysPropsDataAccess = (SystemPropertiesDataAccess) WebApplicationContextUtils.getRequiredWebApplicationContext(sce.getServletContext()).getBean("SystemPropertiesDataAccess");
It looks like Spring isn't creating a WebApplicationContext.
Greater than or equal 1.3.0.RC1 use #ServletComponentScan
#ServletComponentScan // <-- This scans for EJB #WebFilter, #WebListener and #WebServlet
#SpringBootApplication
#ImportResource("classpath:applicationContext.xml")
public class WebCheckApplication {
Less than or equal 1.2.x use #Component to scan for listener
#Component // <-- This allows the component to be found by #ComponentScan inside of #SpringBootApplication
#WebListener
public class MojoSystemPropertiesContextInitializer extends MojoSysPropsAlertsFetcher implements ServletContextListener {
War Deploy extend SpringBootServletInitializer
public class WebCheckApplication extends SpringBootServletInitializer {
In 1.3.0.RC1 #ServletComponentScan was added so simply annotating your main application config should allow these to be picked up. Otherwise adding #Component to your ServletContextListener should work
This link is a discussion on how they currently handle #WebFilter how they decided to handle #WebFilter and they also discuss SpringBootServletInitializer and how this would pick process each item twice if both were to be used. Also links to the commits that implement the new feature.
https://github.com/spring-projects/spring-boot/issues/2290
If you intend to deploy your application as a war file you may also have your main configuration extend SpringBootServletInitializer
http://docs.spring.io/spring-boot/docs/current/reference/html/howto-traditional-deployment.html
All that was needed was to make the Application class extend SpringBootServletInitializer
I thought I had this figured out but the setting does not seem to change the index. setOneIndexedParameters(true)
#Configuration
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
resolver.setOneIndexedParameters(true);
argumentResolvers.add(resolver);
super.addArgumentResolvers(argumentResolvers);
}
.... other config stuff
Expected result is that instead of the base URL for spring data rest being http://localhost:8080/api/text?page=0&size=20 it would change to http://localhost:8080/api/text?page=1&size=20 as the initial page.
Did I do this correctly or is this a bug?
The answer was here Spring Data Rest - Configure pagination
I moved the configuration to extending RepositoryRestMvcConfiguration
#Configuration
class CustomRestMvcConfiguration extends RepositoryRestMvcConfiguration {
#Override
#Bean
public HateoasPageableHandlerMethodArgumentResolver pageableResolver() {
HateoasPageableHandlerMethodArgumentResolver resolver = super.pageableResolver();
resolver.setOneIndexedParameters(true);
return resolver;
}
}