How to register the Spring MVC dispatcher servlet with an embedded tomcat without spring-boot? - spring

My question is similar to this one Embedded Tomcat Integrated With Spring. I want to run a Spring MVC Dispatcher Servlet on an embedded Tomcat. But I always end up with an exception saying that the WebApplicationObjectSupport instance does not run within a ServletContext.
My example has just the two classes:
class Application {
public static void main(String[] args) throws LifecycleException, ServletException {
try (AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext()) {
context.registerShutdownHook();
context.register(WebConfig.class);
context.refresh();
Tomcat tomcat = new Tomcat();
tomcat.setPort(9090);
File base = new File("");
System.out.println(base.getAbsolutePath());
Context rootCtx = tomcat.addWebapp("", base.getAbsolutePath());
DispatcherServlet dispatcher = new DispatcherServlet(context);
Tomcat.addServlet(rootCtx, "SpringMVC", dispatcher);
rootCtx.addServletMapping("/*", "SpringMVC");
tomcat.start();
tomcat.getServer().await();
}
}
}
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/assets/");
}
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("redirect:index.html");
}
}
How do I have to define the servlet context differently then by calling the tomcat.addWebApp(..) method? Does anybody have an example how the Spring MVC dispatcher can be used with an embedded tomcat but without boot?

You can create a ServletContextInitializer and sneak it in via #Configuration:
#Configuration
class WebAppInitConfig implements ServletContextInitializer {
#Override
void onStartup(ServletContext context) {
AnnotationConfigWebApplicationContext webAppContext = new AnnotationConfigWebApplicationContext()
webAppContext.register(RootConfig)
webAppContext.registerShutdownHook()
ServletRegistration.Dynamic dispatcher = context.addServlet("dispatcher", new DispatcherServlet(webAppContext))
dispatcher.loadOnStartup = 1
dispatcher.addMapping("/*")
// Whatever else you need
}
}

Related

How to set equivalent of web.xml JNDI <env-entry> in a Spring Boot project?

Referring to this SO answer, I'd like to setup the equivalent of this web.xml configuration in a JSF / JoinFaces / SpringBoot application (that doesn't have web.xml).
<env-entry>
<env-entry-name>jsf/ClientSideSecretKey</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>[AES key in Base64 format]</env-entry-value>
</env-entry>
Any pointers?
If you are using spring boot and embedded tomcat server, you can add <env-entry> programmatically with the following configuration.
#SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
#Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
#Override
protected TomcatWebServer getTomcatWebServer(org.apache.catalina.startup.Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatWebServer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
// adding <resource-ref>
ContextResource resource = new ContextResource();
resource.setName("jdbc/myJndiResource");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", "org.postgresql.Driver");
resource.setProperty("url", "jdbc:postgresql://hostname:port/dbname");
resource.setProperty("username", "username");
resource.setProperty("password", "password");
context.getNamingResources()
.addResource(resource);
// adding <env-entry>
ContextEnvironment ce = new ContextEnvironment();
ce.setName("jsf/ClientSideSecretKey");
ce.setType(String.class.getName());
ce.setValue("[AES key in Base64 format]");
context.getNamingResources().addEnvironment(ce);
}
};
}
public static void main(String[] args) throws NamingException {
SpringApplication.run(DemoApplication.class, args);
}
}
Once defined the jndi naming resources they can be accessed in your application using JndiTemplate of InitialContext.
JndiTemplate jndiTemplate = new JndiTemplate();
String str = (String) jndiTemplate.lookup("java:comp/env/jsf/ClientSideSecretKey");
Hope this helps you in resolving your problem.
Essentially <env-entry> declares a web application context attribute.
You can initialize your servlet context and provide the equivalent servlet context attributes in your Spring Boot application.
For that purpose, you can register a bean that implements the ServletContextInitializer interface (or WebApplicationInitializer if your app has to be deployed in a traditional servlet container). For example:
public class JsfServletContextInitializer implements ServletContextInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setAttribute("jsf/ClientSideSecretKey", "[AES key in Base64 format]");
}
}
Do not forget to register it as a bean in your configuration.

How to register DispatcherServlets and override onStartup when extending SpringBootServletInitializer

I have a spring boot application that is being deployed to an external instance of Tomcat. The main class extends SpringBootServletInitializer and overrides the methods configure and onStartup. In the method configure, a custom environment is initialized to provide an encryptor for decrypting user passwords for activemq because jasypt is not initialized until after activemq. This I got from the jasypt-spring-boot documentation.
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
try {
StandardEncryptableServletEnvironment env = StandardEncryptableServletEnvironment.builder().encryptor(encryptor).build();
return application.environment(env).sources(AxleServer.class);
} catch (FileNotFoundException e) {
logger.error("Could not load encryptable environment", e);
return application.sources(AxleServer.class);
}
}
In the method onStartup I am configuring DispatcherServlets:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(RemotingConfig.class);
servletContext.addListener(new ContextLoaderListener(context));
ServletRegistration.Dynamic restDispatcher = servletContext.addServlet("rest-dispatcher", new DispatcherServlet(context));
restDispatcher.setLoadOnStartup(1);
restDispatcher.addMapping("/rest/*");
ServletRegistration.Dynamic remotingDispatcher = servletContext.addServlet("remoting-dispatcher", new DispatcherServlet(context));
remotingDispatcher.setLoadOnStartup(2);
remotingDispatcher.addMapping("/remoting/*");
}
The problem is that the configure method is never called because it is called from the super implementation of onStartup. I tried calling the super implementation super.onStartup(servletContext); but then I get an error that a root context already exists when deploying the app.
What is the correct way to register the DispatcherServlets and to override the onStartup method? Is it required to call the super implementation of onStartup?
I was able to solve this issue by first calling the super.onStartup and then getting the root context from ServletContext attributes
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
Object obj = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if(obj instanceof AnnotationConfigServletWebServerApplicationContext) {
AnnotationConfigServletWebServerApplicationContext context = (AnnotationConfigServletWebServerApplicationContext) obj;
context.register(RemotingConfig.class);
ServletRegistration.Dynamic restDispatcher = servletContext.addServlet("rest-dispatcher", new DispatcherServlet(context));
restDispatcher.setLoadOnStartup(1);
restDispatcher.addMapping("/rest/*");
ServletRegistration.Dynamic remotingDispatcher = servletContext.addServlet("remoting-dispatcher", new DispatcherServlet(context));
remotingDispatcher.setLoadOnStartup(2);
remotingDispatcher.addMapping("/remoting/*");
}
}

Springboot>WebServlet - Pass spring container

I have springBoot standalone application. I used #SpringBootApplication, #ServletComponentScan annotations in my standalone application. All my components, beans getting initialized in spring container and prints in the application startup.
Inside my servlet, i invoke handler and beans were coming as null. How do i pass spring container through my servlet ?
#SpringBootApplication
#ServletComponentScan
public class AStandaloneApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(AStandaloneApplication.class, args);
}
}
#WebServlet("/ba")
public class BAServlet extends SpeechletServlet {
#Autowired
private BASpeechletHandler bASpeechletHandler;
#Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
this.setSpeechlet(bASpeechletHandler);
}
}
public class BASpeechletHandler implements Speechlet {
#Autowired
private BSEngine bSEngine;
#Autowired
private IBotResponseObjToAlexaSpeechletResponseObj botResponseObjToAlexaSpeechletResponseObj;
}
The bASpeechletHandler is null in servlet, if i instatiate object in my servlet for bASpeechletHandler and move on then components, services and repository inside bASpeechletHandler also null.
Thanks.
1.Add the packages to component scan - similar to this
#ServletComponentScan(basePackages="org.my.pkg")
2.Add one of the #Component annotations into your BASpeechletHandler class.
This will make that class eligible for auto-discovery of beans.
May be i little complication in asking. I found the solution. In Web applicationContext i pinged the spring context and got the bean.
private ApplicationContext appContext;
private BASpeechletHandler bASpeechletHandler;
public void init(ServletConfig config) throws ServletException {
super.init();
appContext = (ApplicationContext) config.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
bASpeechletHandler = (bASpeechletHandler) appContext.getBean("bASpeechletHandler");
}
Thanks.

SpringMVC, using two contexts and two dispatchers for Rest Service and normal contents

In my SpringMVC project configuration, I have a RootContextConfiguration and other two context configuration files for rest services and normal requests namely; RestServletContextConfiguration and WebServletContextConfiguration.
And, I'm bootstrapping the application as in the following code.
public class Bootstrap implements WebApplicationInitializer
{
#Override
public void onStartup(ServletContext container) throws ServletException
{
container.getServletRegistration("default").addMapping("/resource/*");
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(RootContextConfiguration.class);
container.addListener(new ContextLoaderListener(rootContext));
AnnotationConfigWebApplicationContext restContext = new AnnotationConfigWebApplicationContext();
restContext.register(RestServletContextConfiguration.class);
DispatcherServlet restServlet = new DispatcherServlet(restContext);
restServlet.setDispatchOptionsRequest(true);
ServletRegistration.Dynamic springRestDispatcher = container.addServlet("springRestDispatcher", restServlet);
springRestDispatcher.setLoadOnStartup(1);
springRestDispatcher.addMapping("/api/*");
AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
webContext.register(WebServletContextConfiguration.class);
DispatcherServlet webServlet = new DispatcherServlet(webContext);
ServletRegistration.Dynamic springWebDispatcher = container.addServlet("springWebDispatcher", webServlet );
springWebDispatcher.setLoadOnStartup(2);
springWebDispatcher.setMultipartConfig(new MultipartConfigElement(null, 20_971_520L, 41_943_040L, 512_000));
springWebDispatcher.addMapping("/*");
}
}
I need /api/cars to resolve to
#RestController
#RequestMapping("/cars")
class CarRestController{}
And /cars to resolve to
#Controller
#RequestMapping("/cars")
class CarController{}
However, deployment fails because of ambiguous mapping. If I change the mapping of CarRestController to #RequestMapping('/api/cars') then I can access that controller with the path /api/api/cars (Note the double api prefix). But what I want is to be able to access the CarRestController with /api/cars.
What should I do to achieve my goal?. Highly appreciate your help.
Why you dont put mapping #RequestMapping on your method ?
For example
#RestController
public class YourClass {
#RequestMapping("/car")
public String yourMethod() { }
}

How to embed Jetty into Spring and make it use the same AppContext it was embedded into?

I have a Spring ApplicationContext where I declare Jetty server bean and start it. Inside Jetty I have a DispatcherServlet and a couple of controllers. How to make that DispatcherServlet and its controllers use beans from the same ApplicationContext where Jetty is declared?
In fact, in that outer context I have a couple of daemon-like beans and their dependencies. Controllers inside Jetty use the same dependencies, so I'd like to avoid duplicating them inside and outside Jetty.
I did this a while ago.
Spring's documentation suggests that you use a ContextLoaderListener to load the application context for servlets. Instead of this Spring class, use your own listener. The key thing here is that your custom listener can be defined in the Spring config, and can be aware of the application context it's defined in; so instead of loading a new application context, it just returns that context.
The listener would look something like this:
public class CustomContextLoaderListener extends ContextLoaderListener implements BeanFactoryAware {
#Override
protected ContextLoader createContextLoader() {
return new DelegatingContextLoader(beanFactory);
}
protected BeanFactory beanFactory;
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}
and the DelegatingContextLoader does this:
public class DelegatingContextLoader extends ContextLoader {
protected BeanFactory beanFactory;
public DelegatingContextLoader(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
#Override
protected WebApplicationContext createWebApplicationContext(ServletContext servletContext, ApplicationContext parent) throws BeansException {
return new GenericWebApplicationContext((DefaultListableBeanFactory) beanFactory);
}
}
It's a bit messy, and can probably be improved, but this did work for me.

Resources