Spring Boot with multiple DispatcherServlet, each having their own #Controllers - spring

Basically I want to split my application into 2 parts. Each part has it's own security stuff and own #Controllers. The #Services should be accessible from both parts.
So I thought, I should get 2 DispatcherServlet. One listening to /admin/* and the second listening to everything else ( / ). Each of those will have its own AnnotationConfigWebApplicationContext so I can have separate component scan for the #Controllers.
And because Spring Boot provides one DispatcherServlet listening on / out of the box, I thought, I can just add a second one:
#Configuration
public class MyConfig {
#Bean(name="myDS")
public DispatcherServlet myDS(ApplicationContext applicationContext) {
AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
webContext.setParent(applicationContext);
webContext.register(MyConfig2.class);
// webContext.refresh();
return new DispatcherServlet(webContext);
}
#Bean
public ServletRegistrationBean mySRB(#Qualifier("myDS") DispatcherServlet dispatcherServlet) {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet);
servletRegistrationBean.addUrlMappings("/admin/*");
servletRegistrationBean.setName("adminServlet");
return servletRegistrationBean;
}
}
The MyConfig2 class, only has #Configuration and #ComponentScan. Within the same package is a #Controller.
When starting the application, I can see, that the second servlet mapping is getting registered, but the #Controller is not. Additionally I can now access all #Controllers from / and /admin.
Any idea how I can get this working?

I got it working somehow!
Here's my Package Layout:
test.foo.
FooConfig.java
FooController.java
test.bar.
BarConfig.java
BarController.java
test.app.
Application.java
MyService.java
src/main/resources/application.properties
Application.java:
#SpringBootApplication(exclude=DispatcherServletAutoConfiguration.class)
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
#Bean
public ServletRegistrationBean foo() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(FooConfig.class);
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/foo/*");
servletRegistrationBean.setName("foo");
return servletRegistrationBean;
}
#Bean
public ServletRegistrationBean bar() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(BarConfig.class);
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/bar/*");
servletRegistrationBean.setName("bar");
return servletRegistrationBean;
}
}
The exclude does prevent Spring Boot from creating its own DispatcherServlet with / mapping. You can remove that line, if you want that mapping or define your own.
You can add servletRegistrationBean.setLoadOnStartup(1) if you want to have your Servlets initialized on application start. Else it will wait for the first request for that servlet.
It's important to set servletRegistrationBean.setName(...), else the servlets will override each other.
FooConfig.java & BarConfig.java:
#Configuration #ComponentScan #EnableWebMvc
public class FooConfig { }
#EnableWebMvc will enable the component scan. Without it, it won't find the #Controller class.
The Controller and Service code is not important. You just have to know, that if you have #RequestMapping("/foo") inside FooController, the request must be GET /foo/foo because the Servlet's URL mapping is /foo/*. It's not possible to call the URL GET /foo because the Servlet URL mapping needs a / at the end of its path (in other words: GET /foo will look for a Servlet with / mapping!), though #RequestMapping("") must be called via GET /foo/. And of course it was not possible to use /foo or /foo* as Servlet mapping (or I just did not find the correct settings for that)
Scope: The Controllers can't see each other, though it's not possible to #Autowired them in each other. Also the Service can't #Autowired any of the Controllers. But the Controllers can #Autowired the Service.
Though it's a classical parent child context hierarchy.
The only "bad" thing is, that we need #EnableMvcConfig and don't get the auto configured sugar from Spring boot within the context. The parent context is getting auto configured. I put some database stuff within the application.properties and did a query inside MyService which got called by FooController and it worked flawlessly! :)
I hope this may help some people!

Related

Spring boot MessageDispatcherServlet overriding DispatcherServlet. how to skip overriding and register both DispatcherServlet?

in a single application, I m trying to use soap as well as rest web service. and each servlet is given different URI, below is the code of config class.
The question is: only soap service URL is working fine but for the rest on getting 405 error.
#EnableWs
#Configuration
public class WebServiceConfig {
#Bean
public ServletRegistrationBean RsRegistrationBean(ApplicationContext applicationContext) {
DispatcherServlet servlet = new DispatcherServlet();
servlet.setApplicationContext(applicationContext);
return new ServletRegistrationBean(servlet,"/rest/*");
}
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(
ApplicationContext context) {
MessageDispatcherServlet messageDispatcherServlet = new MessageDispatcherServlet();
messageDispatcherServlet.setApplicationContext(context);
messageDispatcherServlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(messageDispatcherServlet,"/");
}
}
Remove your WebServiceConfig class, as both are auto-configured by Spring Boot already (as of Spring Boot 1.4). Add the following to your application.properties
spring.mvc.servlet.path=/rest
spring.webservices.path=/
Now you leverage the Spring Boot proivded infrastructure instead of fighting with it.

Avoid automatic servlet mapping for a Servlet created as a #Bean in Spring Boot

I have a servlet to which I need to supply dependencies via autowiring (it's a class from an external library, I cannot change its code). I try to register it as a bean and later register it using programmatic registration (ServletContextInitializer). Here is what I have:
#Configuration
public class MyConfiguration {
#Bean
public MyServlet myServlet() {
return new MyServlet();
}
}
Also, SpringMVC-related autoconfiguration creates a usual DispatcherServlet and maps it at /.
When I try to start the application, I get the following:
Caused by: java.lang.IllegalStateException: Multiple servlets map to path /: dispatcherServlet[mapped:JAVAX_API:null],myServlet[mapped:JAVAX_API:null]
So it looks like Spring Boot (or Spring itself?) automatically maps the servlet at the default /. I would like to avoid the mapping at all as I just need to create the servlet instance; I will register it myself later.
Can this be done?
you should use a ServletRegistrationBean then you can provide an extra mapping
#Bean
public MyServlet myServlet() {
return new MyServlet();
}
#Bean
public ServletRegistrationBean myServletRegistration(MyServlet myServlet) {
    ServletRegistrationBean registration = new ServletRegistrationBean(myServlet,
"/myservlet/*");
registration.setLoadOnStartup(1);
    return registration;
}

not able to initialize the init methof of servlet from spring boot

i have a custom servlet initialized in my spring boot app.
#Bean
public ServletRegistrationBean<CustomServlet> servletRegistrationBean()
{
log.info("going to initialise the servlet");
return new ServletRegistrationBean<>(new CustomServlet(), “/path1/*");
}
This is inside a configuration classes annotated with #Configuration, whatever i do spring does not call the init method inside the CustomServlet, although the above log is printed
This code was copied from another sample app, where exact same thing is done, but there, the servlet methods init is initialised, Any thoughts ?
spring boot starter parent version:
2.0.4.RELEASE
Try this;
#Bean
public ServletRegistrationBean exampleServletBean() {
ServletRegistrationBean bean = new ServletRegistrationBean(new CustomServlet(), "/exampleServlet/*");
bean.setLoadOnStartup(1);
return bean;
}
and check this out, it may help : https://www.baeldung.com/register-servlet

How to migrate from AnnotationMethodHandlerAdapter to RequestMappingHandlerAdapter

I'm building REST services via Spring MVC in the application where the GUI is managed by Wicket. Basically, all I need is the DispatcherServlet and a controller with #RequestMapping/#RequestBody.
Because services serve JSON, I need to set MappingJackson2HttpMessageConverter. I can do this via AnnotationMethodHandlerAdapter and that works fine:
#Configuration
#ComponentScan("cz.swsamuraj.wicketspring")
public class SpringRestConfiguration {
#Bean
public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter() {
HttpMessageConverter<?>[] converters = { new MappingJackson2HttpMessageConverter()};
AnnotationMethodHandlerAdapter adapter = new AnnotationMethodHandlerAdapter();
adapter.setMessageConverters(converters);
return adapter;
}
}
The problem is that AnnotationMethodHandlerAdapter is deprecated and it's recommended to use RequestMappingHandlerAdapter instead.
But if I use this configuration:
#Configuration
#ComponentScan("cz.swsamuraj.wicketspring")
public class SpringRestConfiguration {
#Bean
public RequestMappingHandlerAdapter requestHandler() {
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
return adapter;
}
}
I receive an exception:
javax.servlet.ServletException: No adapter for handler [cz.swsamuraj.wicketspring.ws.api.QuestionApiController#69f8a79f]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler
at org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(DispatcherServlet.java:1198)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
So, my question is: how can I set up a handler adapter in the RequestMappingHandlerAdapter?
I spent couple of days of research, but I didn't find any useful example of how to configure RequestMappingHandlerAdapter. All the advices just says to put #EnableWebMvc on the configuration, but this is not the way because of this Wicket-Spring coexistence.
Just to provide a full context, I've created a small buildable and runnable project on Bitbucket: sw-samuraj/blog-wicket-spring-rest
I was able to solve my problem with different approach - with usage of WebApplicationInitializer, I was able to put the #EnableWebMvc annotation on my configuration class and therefore neither the bean RequestMappingHandlerAdapter, nor AnnotationMethodHandlerAdapter are necessary. JSON now works fine, out-of-the-box.
Configuration
#Configuration
#EnableWebMvc
#ComponentScan("cz.swsamuraj.wicketspring")
public class SpringRestConfiguration {
// some additional beans needed for business logic
}
WebApplicationInitializer
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.register(SpringRestConfiguration.class);
servletContext.addListener(new ContextLoaderListener(dispatcherContext));
ServletRegistration.Dynamic dispatcher =
servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/rest/*");
}
}
Example project
Complete working example is on Bitbucket: blog-wicket-spring-rest

Spring Boot - access component inside a servlet

I have a spring boot application that serves my jersey based api. I have a requirement to have the services layer serve blob data to a client as a stream. I wrote a servlet to do that and configured it as follows.
#Bean
public ServletRegistrationBean servletRegistrationBean(){
return new ServletRegistrationBean(new BlobReaderServlet(),"/blobReader/*");
}
However, in the servlet code I can't seem to inject any components (they are all null). I need to inject a component that actually loads the blob data from the database.
#WebServlet(name = "BlobReaderServlet",
urlPatterns = {"/blobreader"})
#Component
public class BlobReaderServlet extends HttpServlet {
Logger logger = Logger.getLogger(this.getClass().getName());
#Inject
DocumentLoaderComponent blobLoader;
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
I strongly suspect the servlet isn't a spring managed component after all and dependency injection isn't working. How can I get access to a component from the context?
UPDATE
It was much simpler than I thought.
#Override
public void init() throws ServletException {
ApplicationContext ac = (ApplicationContext) getServletConfig().getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
this.documentBlob = (DocumentBlob) ac.getBean("documentBlobBean");
}
You are correct that your servlet isn't a Spring managed bean. That is because you are instantiating the instance directly (i.e., calling new BlobReaderServlet() in your bean method). Another solution is to change your configuration class as follows:
#Bean
public ServletRegistrationBean servletRegistrationBean(){
return new ServletRegistrationBean(blobReaderServlet(),"/blobReader/*");
}
#Bean
public BlobReaderServlet blobReaderServlet(){
return new BlobReaderServlet();
}
This will allow Spring to manage the instance and perform autowiring on it.

Resources