Spring 3, AbstractAnnotationConfigDispatcherServletInitializer, multiple servlets - spring

With Servlet 2.5 it was possible to use multiple servlets configured in the web.xml file by simple duplicating and editing the following xml tags.
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Is it somehow possible to create multiple servlets using Spring's AbstractAnnotationConfigDispatcherServletInitializer with Servlet 3?
I thought that returning 2 classes in getServletConfigClasses() method and 2 paths in getServletMappings() method would be enough, but that doesn't work as I expected it to.
So, is there a (simple) way to configure multiple servlets using Spring 3 and Servlet 3?
Thank you for your answers!

You can do something like:
public class MyWebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) {
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(appContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
ServletRegistration.Dynamic anotherServlet =
container.addServlet("anotherServlet", "com.xxx.AnotherServlet");
anotherServlet.setLoadOnStartup(2);
anotherServlet.addMapping("/another/*");
ServletRegistration.Dynamic yetAnotherServlet =
container.addServlet("yetAnotherServlet", "com.xxx.YetAnotherServlet");
yetAnotherServlet.setLoadOnStartup(3);
yetAnotherServlet.addMapping("/yetanother/*");
}
}
Ofcourse, You could use any of the addServlet() methods as per your convenience.

Related

Migrate Spring Web Application (web.xml) to Spring Boot Executable Jar

Okay I've done a lot of googling and I can't seem to find a clear answer. Let's keep it as simple as possible. I have a web.xml file
<listener>
<listener-class>A</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:springcontexts/*.xml</param-value>
</context-param>
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:wsspringcontexts/*.xml</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring_mvc_contexts/*.xml</param-value>
</init-param>
</servlet>
I think I know how to migrate this to Spring Boot ...
#SpringBootApplication(exclude = DispatcherServletAutoConfiguration.class)
#ImportResource("classpath*:springcontexts/*.xml")
public class Application
{
public static void main(String[] args)
{
SpringApplication.run(Application.class, args);
}
}
Somewhere in a sub-package...
#Configuration
#EnableWebMvc
public class SpringMVCConfiguration
{
#Bean
public ServletRegistrationBean mvc()
{
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.setConfigLocation("classpath*:spring_mvc_contexts/*.xml");
// the dispatcher servlet should automatically add the root context
// as a parent to the dispatcher servlet's applicationContext
DispatcherServlet dispatcherServlet = new DispatcherServlet(applicationContext);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/spring/*");
servletRegistrationBean.setName("DispatcherServlet");
return servletRegistrationBean;
}
}
...and we do the above again for the other Servlet
My first problem is how to add the listener "A" to Spring Boot and ensure it runs before the root application is refreshed? Some beans that get configured require some static fields to be setup (legacy code), and this setup is done in listener "A". This works fine when deployed as a standard war using the above web.xml
In addition does the above Spring Boot setup look correct?
Why not put your legacy initialisation in a postConstruct method on a bean ?
Failing that you can add a listener that implements
ApplicationListener<ContextRefreshedEvent>
and overrides
public void onApplicationEvent(final ContextRefreshedEvent event)
Does your Spring Boot setup look OK ? Difficult to tell, though I'd let Spring Boot autoconfigure things like the dispatcher servlet for you and get rid of any XML config if at all possible.

Spring context loader

I have a question about spring context. My application's using spring and spring scheduler.
In web.xml, i declared:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
My question is:
If I declared org.springframework.web.context.ContextLoaderListener in web.xml, the scheduler will run twice, all beans are duplicate, and App start-up time about 160 seconds.
If I remove org.springframework.web.context.ContextLoaderListener,
spring throws exception: No WebApplicationContext found: no ContextLoaderListener registered. And App start-up time reduce to 80 seconds.
How can i solve it? Thanks all!
Thanks #M.Deinum, but i don't understand your idea.
Here is my web.xml:
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.htc.epos.api.bootstrap</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>webapp</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
com.htc.epos.api.bootstrap.WebAppConfig
com.htc.epos.api.bootstrap.AppConfig
</param-value>
</init-param>
</servlet>
Think #M.Deinum is right; split your beans via what is remoting and what is normal. I do this in my web.xml:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/spring/root-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/spring/remoting-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
root-context.xml contains all my normal beans (services, helpers, calculator, jms listeners, scheduled tasks, etc).
remoting-servlet.xml only specifies those services that need to be exposed via the HttpInvokerServiceExporter. There are no imports or links to beans defined in the root, other than things like ref="historyWebService" for the exporter.
From what I understand, you end up with 2 application context: 1 root and 1 remoting. The remoting one inherits all the beans from the root so you don't declare or instantiate beans twice (i think)!!! I certain don't appear to have duplicate beans produced (i.e. 2 task, 2 jms listeners, etc).
I have 2 file config:
1. AppConfig:
#Configuration
#EnableScheduling
#EnableTransactionManagement
#EnableJpaRepositories("com.test.api.repository")
#PropertySource("classpath:application.properties")
public class AppConfig {
...............
}
2. WebInitializer
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[0];
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebAppConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.test.api"})
public static class WebAppConfig extends WebMvcConfigurerAdapter {
...................
}
}
In WebAppConfig, if I change #ComponentScan(basePackages = {"com.test.api"}) to web package #ComponentScan(basePackages = {"com.test.api.web"}), so spring bean's not duplicate and scheduler not run twice. But sometime it throw exception:
error: org.hibernate.LazyInitializationException: could not initialize proxy - no Session

Setting up JavaServer Faces in a spring project

I want add support to JavaServer Faces to a spring project mine, but the tutorials I found on the web teach that for setting this to a project, add this line to the file web.xml:
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
but my spring project don't use XML files for configuration, only java classes. Anyone can tell me how to configure JavaServer Faces in this scenario?
Equivalent class based configuration would be:
public class MyInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
ServletRegistration.Dynamic facesServlet = servletContext.addServlet("Faces Servlet", new FacesServlet());
facesServlet.setLoadOnStartup(1);
facesServlet.addMapping("/faces/*");
}
}
More details here

Spring 2.5.5 and jersey with autowired

I am trying to integrate jersey to an existing Spring application (Spring 2.5.5).
Jersey is working fine, but however when I AutoWire an existing spring bean, the object is null.
Below is my web.xml
<servlet>
<servlet-name>fs3web</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>jersey-servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.fl.fs3.api;org.codehaus.jackson.jaxrs</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>fs3web</servlet-name>
<url-pattern>/fs3/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>jersey-servlet</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
And, here my application context xml (obviously this is not complete, since this is a huge application, there is much more bean definitions):
TestPojo is my bean I would like to autowire to my jersey resource.
<context:annotation-config />
<aop:aspectj-autoproxy/>
<context:component-scan base-package="com.fl.fs3.api,com.fl.fs3.integration.*.web"/>
Both my jersey resource class and POJO class is in package com.fl.fs3.api
#Component
#Path("/v1/site")
public class SitesApiControllerV1 {
#Autowired TestPojo testPojo;
#GET
#Path("/{folderName}")
#Produces(MediaType.APPLICATION_JSON)
public Response getSite(#PathParam("folderName") String folderName) {
System.out.println("pojo obj:" + testPojo);
return Response.ok("info for " + folderName).build();
}
}
#Component
public class TestPojo {
}
When I start my tomcat, I do not see the expected line in logs:
INFO: Registering Spring bean, hello, of type ..... as a root resource class
When I invoke my service /v1/site/xyz, testPojo object is null.
However, before integrating this to my existing project, I did a sample jersey+spring application, and it worked perfectly. I was able to see 'Registering Spring bean' line in logs.
Any help is appreciated.
Try this, it may be more simplified:
Load spring through web.xml like shown below as normal spring confifuration:
<servlet>
<servlet-name>project-spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:project-spring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>project-spring</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
Now load your jersey Resources through Application as shown below:
#ApplicationPath("/rest")
public class ResourceLoader extends Application
{
/* (non-Javadoc)
* #see javax.ws.rs.core.Application#getClasses()
*/
#Override
public Set<Class<?>> getClasses()
{
Set<Class<?>> classes = new HashSet<Class<?>>();
loadResourceClasses(classes);
return classes;
}
private void loadResourceClasses(Set<Class<?>> classes)
{
classes.add(StudentResource.class);
}
}
Then in your resource:
#Path("student")
class StudentResource
{
private StudentService studentService;
StudentResource(#Context ServletContext servletContext)
{
ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
this.transactionService = applicationContext.getBean(StudentService .class);
}
}
There you go.
Spring has been configured with all dependency injections with Jersey!

Configuring Grizzly with a web.xml

I can start grizzly and deploy Jersey webservices on it with the following lines.
protected HttpServer create() throws Throwable {
ResourceConfig rc = new PackagesResourceConfig("com.resource", "com.provider");
HttpServer server = GrizzlyServerFactory.createHttpServer(uri, rc);
return server;
}
But is there a way to load a web.xml instead of a ResourceConfig?
<web-app>
<servlet>
<servlet-name>Jersey</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.resource, com.provider</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Jersey</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
It seems that there is currently no direct way to configure grizzly with a web.xml. However I have used a partial solution that may be a beginning.
web.xml
First to understand the solution, we must understand what is the meaning of using a web.xml. It is basically use for configure your web application (see this answer for a more detail). In this case we are configuring init-params for the servlet.
The (partial) solution
Instead of using the web.xml and instead of using ResouceConfig.class, we can use Grizzly as our servlet and initializing the parameters.
For example
<web-app>
<servlet>
<servlet-name>Jersey</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.resource, com.provider</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Jersey</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
would give something like :
protected HttpServer create() throws Throwable {
HashMap<String, String> initParams = new HashMap<>();
//ServerProperties.PROVIDER_PACKAGES is equal to "jersey.config.server.provider.packages"
initParams.put(ServerProperties.PROVIDER_PACKAGES, "com.resource,com.provider");
//Make sure to end the URI with a forward slash
HttpServer server = GrizzlyWebContainerFactory.create("http://localhost:8080/", initParams);
return server;
}
With this, we can therefore put all the init-params that we want to.
However this solution cannot replace a whole web.xml.

Resources