How can I register a servlet with Spring Boot? - spring

This code don't work
I have a web.xml that needs to be translated into a spring boot
<servlet>
<servlet-name>page-resolver</servlet-name>
<servlet-class>com.bk.ForwardServlet</servlet-class>
<init-param>
<param-name>page</param-name>
<param-value>/index.jsp</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>page-resolver</servlet-name>
<url-pattern>/index.html</url-pattern>
<url-pattern>/login</url-pattern>
<url-pattern>/qmgr/*</url-pattern>
</servlet-mapping>
and this class
#WebServlet
public class ForwardServlet extends HttpServlet {
private String page;
#Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
page = config.getInitParameter("page");
}
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher(page).forward(req, resp);
}
}
I wrote code that registers this bean but it doesn't work and I don't understand why
#Configuration
public class ConfigureWeb implements ServletContextInitializer {
private void registerServlet(ServletContext servletContext) {
ServletRegistrationBean serviceServlet =
new ServletRegistrationBean(new ForwardServlet(), "/index.html","/login","/qmgr/*");
serviceServlet.addUrlMappings("/index.jsp");
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerServlet(servletContext);
}
}

First of all check and verify that all your resources are being kept in the correct directory.For spring-boot the jsp pages are to be kept in the directory src->main->webapp->WEB-INF->view->*.jsp .
You should have the following dependencies added in pom to compile the jsp properly when using embedded tomcat :
<!-- JSTL -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- To compile JSP files -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
Now we need to tell spring where the jsp files reside, we can have two approaches for this :
Add entries in application.properties
spring.mvc.view.prefix=/WEB-INF/view/
spring.mvc.view.suffix=.jsp
Here we can specify the folder where the jsp files are to be expected, as well as define the view.
or
Configure InternalResourceViewResolver to serve JSP pages
#Configuration
#EnableWebMvc
#ComponentScan
public class MvcConfiguration extends WebMvcConfigurerAdapter
{
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/view/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
registry.viewResolver(resolver);
}
}
Now for registering servlets :
ServletRegistrationBean is used to register Servlets in Servlet 3.0 + container. We need to create a bean of ServletRegistrationBean in our JavaConfig. Find some methods of ServletRegistrationBean used to configure a Servlet.
setServlet(): Sets the servlet to be registered.
addUrlMappings(): Add URL mappings for the Servlet.
setLoadOnStartup: Sets priority to load Servlet on startup.
Suppose we have two Servlets as HelloCountryServlet and HelloStateServlet then we will register them with Spring Boot using ServletRegistrationBean as following :
#Configuration
public class WebConfig {
#Bean
public ServletRegistrationBean<HttpServlet> countryServlet() {
ServletRegistrationBean<HttpServlet> servRegBean = new ServletRegistrationBean<>();
servRegBean.setServlet(new HelloCountryServlet());
servRegBean.addUrlMappings("/country/*");
servRegBean.setLoadOnStartup(1);
return servRegBean;
}
#Bean
public ServletRegistrationBean<HttpServlet> stateServlet() {
ServletRegistrationBean<HttpServlet> servRegBean = new ServletRegistrationBean<>();
servRegBean.setServlet(new HelloStateServlet());
servRegBean.addUrlMappings("/state/*");
servRegBean.setLoadOnStartup(1);
return servRegBean;
}
}
For every Servlet we create a ServletRegistrationBean bean.
And the servlet class will look like :
public class HelloCountryServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException{
doGet(request,response);
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h3>Hello India!</h3>");
}
}
This is one approach.
Another method to register servlets in spring-boot is using annotations : #ServletComponentScan and #WebServlet. #ServletComponentScan in Spring Boot will scan Servlets annotated with #WebServlet, Filters annotated with #WebFilter and Listeners annotated with #WebListener.The annotation #ServletComponentScan is used on JavaConfig at class level. #ServletComponentScan scans Servlets, Filters and Listeners only using an embedded web server. Find the Servlets annotated with #WebServlet.
So the earlier HelloCountryServlet servlet can be provided as :
#WebServlet(urlPatterns = "/country/*", loadOnStartup = 1)
public class HelloCountryServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException{
doGet(request,response);
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h3>Hello India!</h3>");
}
}
and provide the ServletComponentScan annotation like :
#ServletComponentScan
#SpringBootApplication
public class SpringBootAppStarter {
public static void main(String[] args) {
SpringApplication.run(SpringBootAppStarter.class, args);
}
}

If you are not required to register a servlet, you could add the forwarding to the ViewControllerRegistry
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index.html").setViewName("forward:/index.jsp");
registry.addViewController("/login").setViewName("forward:/index.jsp");
registry.addViewController("/qmgr/**").setViewName("forward:/index.jsp");
}
}
You could combine the three addViewControllers into one using AntPathMatcher patterns
Map a view controller to the given URL path (or pattern) in order to
render a response with a pre-configured status code and view.
Patterns like "/admin/**" or "/articles/{articlename:\w+}" are
allowed. See AntPathMatcher for more details on the syntax.
Personally I think separating them is more readable

Related

Spring Boot + JSF static page configurations

am getting started with Spring Boot + JSF.
Spring documentation says:
Spring will automatically load and serve resources inside
/static (or /public or /resources or /META-INF/resources.
This is not working for me.
it is discouraged to use the "webapp" (src/main/webapp) folder.
But for my static content, it seem to be the only folder thats working with my JSF static page.
see Spring Boot static content.
I have tried these configuration:
#Configuration
public class JSFServletConfigs implements ServletContextInitializer// ,ServletContextAware
{
#Override // ServletContext initiallization parameters
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setInitParameter("facelets.DEVELOPMENT", "false");
servletContext.setInitParameter("javax.faces.PROJECT_STAGE", "Development");
servletContext.setInitParameter("javax.faces.DEFAULT_SUFFIX", ".xhtml");
servletContext.setInitParameter("javax.faces.PARTIAL_STATE_SAVING_METHOD", "true");
servletContext.setInitParameter("javax.faces.FACELETS_REFRESH_PERIOD", "-1");
}
#Bean // Registering the FacesServlet.
public ServletRegistrationBean servletRegistrationBean() {
FacesServlet servlet = new FacesServlet();
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(servlet) {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
FacesInitializer facesInitializer = new FacesInitializer();
Set<Class<?>> clazz = new HashSet<Class<?>>();
clazz.add(JSFServletConfigs.class);
facesInitializer.onStartup(clazz, servletContext);
}
};
servletRegistrationBean.addUrlMappings("*.xhtml", "*.jsf", "*.html");
return servletRegistrationBean;
}
#Bean
public InternalResourceViewResolver jsfViewResolving(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/<sub-folder of any LOCATION>/");
viewResolver.setSuffix(".xhtml");
return viewResolver;
}
}
Servlet pre-configs:
#Configuration
#EnableWebMvc
public class DefaultServletConfigs extends WebMvcConfigurerAdapter {
#Override // Enabling the default Servlet at path="/"
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
private final String[] LOCATIONS = {"<All my possible locations>"};
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if(!registry.hasMappingForPattern("/**")){
registry.addResourceHandler("/**").addResourceLocations(LOCATIONS);
}
if(!registry.hasMappingForPattern("webjars")){
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
}
My Question are:
What am I missing here?
How do I get the flexibility to place my resources in any of the other folders above
(i.e. "/static", "/public", "/resources", "/META-INF/resources").

#Autowired does not work with #Configurable

I am trying to do an image upload API. I have a ImageUpload task as follows,
#Component
#Configurable(preConstruction = true)
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ImageUploadTask implements Callable<JSONObject> {
#Autowired
private ImageUploadService imageUploadService;
#Override
public JSONObject call() throws Exception {
....
//Upload image via `imageUploadService`
imageUploadService.getService().path('...').post('...'); // Getting null pointer here for imageUploadService which is a WebTarget
}
}
The ImageUploadService looks like the below,
#Component
public class ImageUploadService {
#Inject
#EndPoint(name="imageservice") //Custom annotation, battle tested and works well for all other services
private WebTarget imageservice;
public WebTarget getService() {
return imageservice;
}
}
Here is the spring boot application class,
#ComponentScan
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving=EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
#EnableAutoConfiguration
public class ImageApplication extends SpringBootServletInitializer {
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new RequestContextListener());
}
public static void main(String[] args) throws IOException {
SpringApplication.run(ImageApplication.class);
}
}
Additional information :
Spring version of dependencies are at 4.2.5.RELEASE
pom.xml has dependencies added for spring-aspects and
spring-instrument
I am getting a NullPointerException in ImageUploadTask. My suspicion is that #Autowired doesn't work as expected.
Why wouldn't work and how do I fix this?
Is it mandatory to use #Autowired only when I use #Conigurable, why not use #Inject? (though I tried it and getting same NPE)
By default the autowiring for the #Configurable is off i.e. Autowire.NO beacuse of which the imageUploadService is null
Thus update the code to explicity enable it either as BY_NAME or BY_TYPE as below.
#Component
#Configurable(preConstruction = true, autowire = Autowire.BY_NAME)
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ImageUploadTask implements Callable<JSONObject> { .... }
Rest of the configuration viz. enabling load time weaving seems fine.
Also regarding #Inject annotation have a look here which pretty much explains the difference (or similarity perhaps)

Integrating spring-boot with RESTEasy

I am trying to prototype a Spring Boot application. I'm coming from a Guice JAX-RS application, so I prefer the standard JAX-RS annotations to Spring MVC. I've gotten Jetty up and serving:
#Configuration
#Import({ResteasyBootstrap.class, SpringBeanProcessorServletAware.class, HttpServletDispatcher.class})
public class EmbeddedJetty {
#Bean
#Singleton
public EmbeddedServletContainerFactory servletContainer() {
JettyEmbeddedServletContainerFactory factory = new JettyEmbeddedServletContainerFactory();
factory.setPort(9000);
factory.setSessionTimeout(10, TimeUnit.MINUTES);
return factory;
}
}
However, I just can't figure out how to get RESTEasy hooked up correctly. With the above SpringBeanProcessorServletAware it bails, seemingly the ServletContext is not injected through ServletContextAware before it ends up being used:
java.lang.NullPointerException: null
at org.jboss.resteasy.plugins.spring.SpringBeanProcessorServletAware.getRegistry(SpringBeanProcessorServletAware.java:30)
at org.jboss.resteasy.plugins.spring.SpringBeanProcessor.postProcessBeanFactory(SpringBeanProcessor.java:247)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:284)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:174)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:680)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:522)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766)
I also tried using the SpringContextLoaderListener, but that seems to conflict with the spring-boot AnnotationConfigEmbeddedWebApplicationContext class.
I'm using spring-boot 1.3.3 and spring-framework 4.3.0.rc1
The other answer won't have your resources as spring beans, this autoconfiguration will integrate them properly:
The Configuration class:
#Configuration
#ConditionalOnWebApplication
public class RestEasyAutoConfigurer {
private Environment environment;
#Bean(name = "resteasyDispatcher")
public ServletRegistrationBean resteasyServletRegistration() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HttpServletDispatcher(), getPrefix()
+ "/*");
registrationBean.setInitParameters(ImmutableMap.of("resteasy.servlet.mapping.prefix", "/rs/")); // set prefix here
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
#Bean(destroyMethod = "cleanup")
public static RestEasySpringInitializer restEasySpringInitializer() {
return new RestEasySpringInitializer();
}
#Bean
// use Spring Boot configured Jackson
public CustomResteasyJackson2Provider jackson2Provider(ObjectMapper mapper) {
return new CustomResteasyJackson2Provider(mapper);
}
public static class RestEasySpringInitializer
implements
ServletContextInitializer,
ApplicationContextAware,
BeanFactoryPostProcessor {
private ResteasyDeployment deployment;
private ConfigurableApplicationContext applicationContext;
private ConfigurableListableBeanFactory beanFactory;
public void cleanup() {
deployment.stop();
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
ListenerBootstrap config = new ListenerBootstrap(servletContext);
deployment = config.createDeployment();
deployment.start();
servletContext.setAttribute(ResteasyProviderFactory.class.getName(), deployment.getProviderFactory());
servletContext.setAttribute(Dispatcher.class.getName(), deployment.getDispatcher());
servletContext.setAttribute(Registry.class.getName(), deployment.getRegistry());
SpringBeanProcessor processor = new SpringBeanProcessor(deployment.getDispatcher(),
deployment.getRegistry(), deployment.getProviderFactory());
processor.postProcessBeanFactory(beanFactory);
applicationContext.addApplicationListener(processor);
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
}
}
And the Jackson provider:
#Provider
#Consumes({"application/*+json", "text/json"})
#Produces({"application/*+json", "text/json"})
public class CustomResteasyJackson2Provider extends ResteasyJackson2Provider {
private ObjectMapper mapper;
public CustomResteasyJackson2Provider(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) {
return Optional.ofNullable(_mapperConfig.getConfiguredMapper()).orElse(mapper);
}
}
NOTE: this is a working configuration for Spring Boot 1.3.3 / RESTEasy 3.0.16
You can use RESTEasy Spring Boot starter. Here is how you do it:
Adding POM dependency
Add the Maven dependency below to your Spring Boot application pom file.
<dependency>
<groupId>com.paypal.springboot</groupId>
<artifactId>resteasy-spring-boot-starter</artifactId>
<version>2.1.1-RELEASE</version>
<scope>runtime</scope>
</dependency>
Registering JAX-RS application classes
Just define your JAX-RS application class (a subclass of Application) as a Spring bean, and it will be automatically registered. See the example below. See section JAX-RS application registration methods in How to use RESTEasy Spring Boot Starter for further information.
package com.test;
import org.springframework.stereotype.Component;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
#Component
#ApplicationPath("/sample-app/")
public class JaxrsApplication extends Application {
}
Registering JAX-RS resources and providers
Just define them as Spring beans, and they will be automatically registered. Notice that JAX-RS resources can be singleton or request scoped, while JAX-RS providers must be singletons.
Further information at the project GitHub page.
Here is fully working example.
First, a sample JAX-RS endpoint:
#Path("/api")
public class SampleResource {
#GET
#Path("/sample")
#Produces(MediaType.APPLICATION_JSON)
public String getSample() {
return "Some JSON";
}
}
Next, a JAX-RS configuration class that loads all endpoints.
import javax.ws.rs.core.Application;
public class RestEasyConfig extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(SampleRest.class);
return classes;
}
}
Finally, in your Spring configuration, initialize RESTEast filter and inform the framework about its existence.
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.jboss.resteasy.plugins.server.servlet.FilterDispatcher;
...
#Bean
public FilterRegistrationBean filterRegistrationBean() {
Map<String, String> initParams = new HashMap<>();
initParams.put("javax.ws.rs.Application", RestEasyConfig.class.getCanonicalName());
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new FilterDispatcher());
registrationBean.setInitParameters(initParams);
return registrationBean;
}
Your endpoint should be up and running. If you are missing the FilterDispatcher class on your class path, add the resteasy-jaxrs library to your build descriptor.

Spring Autowiring fails

I have added one Listener in web.xml
<listener>
<listener-class>*.LoadCacheServlet</listener-class>
</listener>
This is servlet class
#Component
public class LoadCacheServlet implements ServletContextListener {
#Autowired
private ConnectorRepository connectorRepository;
#Override
public void contextDestroyed(ServletContextEvent arg0) {
LOGGER.info("ServletContextListener destroyed----------------------------");
}
// Run this before web application is started
#Override
public void contextInitialized(ServletContextEvent arg0) {
LOGGER.info("ServletContextListener startedddd---------------------------------");
connectorRepository.callMe("xys");
}
}
On tomcat startup. contextInitialized method gets exceuted but connectorRepository is not initailized. ConnectorRepository is an interface and in other parts of application its gets autowired by SpringJPA module. How to initialize it in servlet.

Access spring component from servlet

I have a controller (for example. MyManager) where I invoke method (for example myMethod() ) of component class (bean), for example MyComponent. I have I servlet where I want to invoke myMethod() . In servlet I have annotated MyManager by #Autowired annotation , despite this I got NullPointerException. I saw this kind of topic but it is not useful for me. For imagination I write little code :
public class myClass extends HttpServlet {
#Autowired
private MyComponent component;
public void init(ServletConfig config) throws ServletException{
super.init(config);
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
protected void doGet(HttpServletRequest req,HttpServletResponse res) throws ... {
List<MyObject> objects =component.myMethod(); // Here is the problem, component is null
}
}
}
I make Spring configuration file "context.xml" and I got bean (component) object, but now I have problem with injected EntityManager in bean object. Now it is null , can anyone help me to solve this problem ? Also update init() method.
public void init(ServletConfig config) throws ServletException{
ApplicationContext con = new ClassPathXmlApplicationContext("context.xml");
component = (MyComponent) con.getBean("myBean");
}
You cannot autowire dependencies like that because Servlets are not Spring Beans. You need to do something like the following:
#Override
public void init() throws ServletException {
super.init();
ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
component= applicationContext.getBean(MyComponent.class);
}
Also drop the #Autowired annotation from component

Resources