Spring MVC configuration and mapping - spring

Hate to ask a simple config question but I'm new to the spring mvc framework and for some reason struggling a little bit. I am working on this just to learn it as I have used MVC in ruby and wanted to try it in java.
I have a sample app that talks to a DB and returns a full table from my controller out to a JSP it all works my table is displaying from the DB Correctly. I still think I have my configs wrong though as my app only works if my web.xml is setup like so
<servlet-mapping>
<servlet-name>foo</servlet-name>
<url-pattern>/RunList.jsp</url-pattern>
</servlet-mapping>
I don't think I should have to use the full name of the JSP in my pattern. if I just use / I get
<servlet-mapping>
<servlet-name>foo</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Sep 01, 2015 10:53:02 AM org.springframework.web.servlet.PageNotFound noHandlerFound
WARNING: No mapping found for HTTP request with URI [/dyn-qa-qeb/] in DispatcherServlet with name 'foo'
Here is my controller
#RequestMapping(value="/RunList")
public ModelAndView listRun(ModelAndView model) throws IOException{
//#ModelAttribute
System.out.println("**** Controller ******");
List<QAModel> listRun = runDao.list();
model.addObject("RunList", listRun);
model.setViewName("RunList");
return model;
}
I also have an MVC Configuration file that I setup based on a tutorial but I am not sure if that just overrides the web.xml
#Configuration
#ComponentScan(basePackages="com.foo")
#EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter{
#Bean
public ViewResolver getViewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/jsp/");
resolver.setSuffix(".jsp");
return resolver;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}

This was a config issue long ago solved just cleaning up my questions

Related

What exactly is the ResourceConfig class in Jersey 2?

I have seen a lot of Jersey tutorials that starts with something like
#ApplicationPath("services")
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
packages("com.abc.jersey.services");
}
}
without explaining what exactly the ResourceConfig class is. So where can I find its documentation, usage, etc.? Googling for "jersey resourceconfig" does not yield any official doc.
Some of my questions about this class and its usage are:
What things can I do inside the subclass of ResourceConfig?
Do I need to register the subclass of ResourceConfig somewhere so that it can be found or is it automatically detected by Jersey?
If the subclass is automatically detected what happens if I have multiple subclasses of ResourceConfig?
Is the purpose of ResourceConfig the same as the web.xml file? If so what happens if I have both in my project? Does one of them take precedence over the other?
Standard JAX-RS uses an Application as its configuration class. ResourceConfig extends Application.
There are three main ways (in a servlet container) to configure Jersey (JAX-RS):
With only web.xml
With both web.xml and an Application/ResourceConfig class
With only an Application/ResourceConfig class annotated with #ApplicationPath.
With only web.xml
It is possible to configure the application in a standard JAX-RS way, but the following is specific to Jersey
<web-app>
<servlet>
<servlet-name>jersey-servlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.mypackage.to.scan</param-value>
</init-param>
</servlet>
...
<servlet-mapping>
<servlet-name>jersey-servlet</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
...
</web-app>
Since Jersey runs in a servlet container, it is only right that the Jersey application runs as a servlet. The Jersey Servlet that handles incoming requests is the ServletContainer. So here we declare it as the <servlet-class>. We also configure an <init-param> telling Jersey which package(s) to scan for our #Path and #Provider classes so it can register them.
Under the hood, Jersey will actually create a ResourceConfig instance, as that's what it uses to configure the application. Then it will register all the classes that it discovers through the package scan.
With both web.xml and Application/ResourceConfig
If we want to programmatically configure our application with an Application or ResourceConfig subclass, we can do so with one change to the above web.xml. Instead of setting an init-param to scan for packages, we use an init-param to declare our Application/ResourceConfig subclass.
<servlet>
<servlet-name>jersey-servlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.example.JerseyApplication</param-value>
</init-param>
<servlet-mapping>
<servlet-name>jersey-servlet</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</servlet>
package com.example;
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
packages("com.abc.jersey.services");
}
}
Here, we configure the init-param javax.ws.rs.Application with the fully qualified name of our ResourceConfig subclass. And instead of using the init-param that tells Jersey which package(s) to scan, we just use the convenience method packages() of the ResourceConfig.
We could also use the methods register() and property() to register resources and providers, and to configure Jersey properties. With the property() method, anything that can be configured as an init-param, can also be configured using the property() method. For instance instead of calling packages(), we could do
public JerseyApplication() {
property("jersey.config.server.provider.packages",
"com.mypackage.to.scan");
}
With only Application/ResourceConfig
Without a web.xml, Jersey needs a way for us to provide the servlet-mapping. We do this with the #ApplicationPath annotation.
// 'services', '/services', or '/services/*'
// is all the same. Jersey will change it to be '/services/*'
#ApplicationPath("services")
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
packages("com.abc.jersey.services");
}
}
Here with the #ApplicationPath, it's just like if we configured the servlet mapping in the web.xml
<servlet-mapping>
<servlet-name>JerseyApplication</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
When using only Java code for configuration, there needs to be some way for Jersey to discover our configuration class. This is done with the use of a ServletContanerInitializer. This is something that was introduced in the Servlet 3.0 Specification, so we cannot use "Java only" configuration in earlier servlet containers.
Basically what happens is that the implementor of the initializer can tell the servlet container what classes to look for, and the servlet container will pass those classes to the initializer onStartup() method. In Jersey's implementation of the initializer, Jersey configures it to look for Application classes and classes annotated with #ApplicationPath. See this post for further explanation. So when the servlet container starts the application, Jersey's initializer will get passed our Application/ResourceConfig class.
What things can I do inside the subclass of ResourceConfig
Just look at the javadoc. Its mostly just registration of classes. Not much else you need to do with it. The main methods you will be using are the register(), packages(), and property() methods. The register() method lets you manually register classes and instances of resources and providers manually. The packages() method, discussed earlier, lists the package(s) you want Jersey to scan for #Path and #Provider classes and register them for you. And the property() method allows you to set some configurable properties 1.
The ResourceConfig is just a convenience class. Remember, it extends Application, so we could even use the standard Application class
#ApplicationPath("/services")
public class JerseyApplication extends Application {
private final Set<Class<?>> classes;
private final Set<Object> singletons;
public JerseyApplication() {
// configure in constructor as Jersey
// may call the getXxx methods multiple times
this.classes = new HashSet<>();
this.classes.add(MyResource.class);
this.singletons = new HashSet<>();
this.singletons.add(new MyProvider());
}
#Override
public Set<Class<?>> getClasses() {
return this.classes;
}
#Override
public Set<Object> getSingletons() {
return this.singletons;
}
#Override
public Map<String, Object> getProperties() {
final Map<String, Object> properties = new HashMap<>();
properties.put("jersey.config.server.provider.packages",
"com.mypackage.to.scan");
return properties;
}
}
With a ResourceConfig, we would just do
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
register(MyResource.class);
register(new MyProvider());
packages("com.mypackages.to.scan");
}
}
Aside from being more convenient, there are also a few thing under the hood that help Jersey configure the application.
An SE Environment
All the examples above assume you are running in an installed server environment, e.g. Tomcat. But you can also run the app in an SE environment, where you run an embedded server and start the app from a main method. You will sometimes see these examples when searching around for info, so I want to show what that looks like, so that if you ever do come across this, you are not surprised and know how it differs from your setup.
So sometimes you will see an example like
ResourceConfig config = new ResourceConfig();
config.packages("com.my.package");
config.register(SomeFeature.class);
config.property(SOME_PROP, someValue);
What is most likely happening here is that the example is using an embedded server, like Grizzly. The rest of the code to start the server might be something like
public static void main(String[] args) {
ResourceConfig config = new ResourceConfig();
config.packages("com.my.package");
config.register(SomeFeature.class);
config.property(SOME_PROP, someValue);
String baseUri = "http://localhost:8080/api/";
HttpServer server = GrizzlyHttpServerFactory
.createHttpServer(URI.create(baseUri), config);
server.start();
}
So in this example, there is a standalone server being started and the ResourceConfig is used to configure Jersey. The different here and from previous examples is that in this example, we are not extending the ResourceConfig, but instead just instantiating it. It wouldn't be any different if we were to do
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
packages("com.my.package");
register(SomeFeature.class);
property(SOME_PROP, someValue);
}
}
HttpServer server = GrizzlyHttpServerFactory
.createHttpServer(URI.create(baseUri), new JerseyConfig());
Say you were going through some tutorial and it showed a configuration for a standalone app where they instantiate the ResourceConfig. But you are running your app in an installed servlet container and have been using the earlier configuration where you are extending the ResourceConfig. Well now you know what the difference is and what changes you need to make. I've seen people do some really weird stuff because they didn't understand this difference. For example I saw someone instantiating a ResourceConfig inside a resource class. So this is why I added this extra little piece; so you don't make the same mistake.
Footnotes
1. There are a number of different configurable properties. The link to the ServerProperties are just some general properties. There are also different properties related to specific features. The documentation should mention these properties in the section of the docs related to that feature. For a complete list of all configurable properties, you can look at all the Jersey constants and look for the ones where the string value starts with jersey.config. If you are using a web.xml, then you would use the string value as the init-param param-name. If you are using Java config (ResourceConfig), then you would call property(ServerProperties.SOME_CONF, value)

How to allow GET method for endpoint programmatically?

I am loading a .war file and add it as web app to the embedded Tomcat server.
#Bean
public EmbeddedServletContainerFactory servletContainerFactory() {
LOGGER.info("Adding web app");
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
String appHome = System.getProperty(Environment.APP_HOME);
String targetFileName = "web-0.0.1-SNAPSHOT.war";
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(targetFileName);
LOGGER.info(System.getProperty("user.name"));
LOGGER.debug("Loading WAR from " + appHome);
File target = new File(Paths.get(appHome, targetFileName).toString());
try {
LOGGER.info(String.format("Copy %s to %s", targetFileName, target.getAbsoluteFile().toPath()));
java.nio.file.Files.copy(resourceAsStream, target.getAbsoluteFile().toPath(), StandardCopyOption.REPLACE_EXISTING);
Context context = tomcat.addWebapp("/", target.getAbsolutePath());
context.setParentClassLoader(getClass().getClassLoader());
} catch (ServletException ex) {
throw new IllegalStateException("Failed to add webapp.", ex);
} catch (Exception e) {
throw new IllegalStateException("Unknown error while trying to load webapp.", e);
}
return super.getTomcatEmbeddedServletContainer(tomcat);
}
};
}
This is working so far but if I access http://localhost:8080/web I am getting
2017-03-04 11:18:59.588 WARN 29234 --- [nio-8080-exec-2] o.s.web.servlet.PageNotFound : Request method 'GET' not supported
and the response
Allow: POST
Content-Length: 0
Date: Sat, 04 Mar 2017 10:26:16 GMT
I am sure all I have to do is to allow the GET method on /web and hopefully the static web content provided from the loaded war file will be accessible via web browser.
How/where can I configure the endpoint such that it allows GET requests?
I tried to introduce a WebController as described in this tutorial.
#Controller
public class WebController {
private final static Logger LOGGER = Logger.getLogger(WebController.class);
#RequestMapping(value = "/web", method = RequestMethod.GET)
public String index() {
LOGGER.info("INDEX !");
return "index";
}
}
In the log output I can see that this is getting mapped correctly:
RequestMappingHandlerMapping : Mapped "{[/web],methods=[GET]}" onto public java.lang.String org.ema.server.spring.controller.dl4j.WebController.index()
but it does not change the fact that I cannot visit the website.
I've also configured a InternalResourceViewResolver:
#Configuration
#EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter {
private final static Logger LOGGER = Logger.getLogger(MvcConfiguration.class);
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
LOGGER.info("configureViewResolvers()");
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setSuffix(".html");
registry.viewResolver(resolver);
}
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
web.xml
Since I configure everything in pure Java, this file does not define a lot:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Easy Model Access Server</display-name>
<listener>
<listener-class>org.ema.server.ServerEntryPoint</listener-class>
</listener>
<context-param>
<param-name>log4j-config-location</param-name>
<param-value>WEB-INF/classes/log4j.properties</param-value>
</context-param>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/web/*.html</url-pattern>
</servlet-mapping>
</web-app>
Reproduce
If you want to reproduce this you can simply checkout the entire code from github. All you need to do this:
mkdir ~/.ema
git clone https://github.com/silentsnooc/easy-model-access
cd easy-model-access/ema-server
mvn clean install
java -jar server/target/server-*.jar
This will clone, build and run the server.
The directory ~/.ema directory is required at the moment. It is where the WAR is being copied as the server starts.
My guess is that your web.xml maps any path to the Spring DispatcherServlet, something like:
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Because of <url-pattern>/</url-pattern> any request must be handled by a Spring controller, for this reason your static files are not served by Tomcat. Also a pattern like /*.html would have same effect.
If you have only a few pages you might add one or more mapping to the predefined default servlet for them, before the mapping of Spring (and also before Spring Security if you use it):
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>index.html</url-pattern>
</servlet-mapping>
You may also use <url-pattern>*.html</url-pattern> or, if your resources are under the web path and there are only static resources there: <url-pattern>/web/*</url-pattern>
Maybe all this is done instead in Java code in the org.ema.server.ServerEntryPoint that you have as a listener in web.xml
I think the mapping I wrote up in web.xml is done in your case in method getServletMappings of class org.ema.server.spring.config.AppInitializer, I changed it to use a more strict pattern /rest-api/* instead than /, not sure pattern is correct and everything else works, but now http://127.0.0.1:8080/index.html works
#Override
protected String[] getServletMappings() {
return new String[] { "/rest-api/*" };
}
as I see the url: http://localhost:8080/web is wrong.
You can try: http://localhost:8080/[name-of-war-file]/web

Dispatcher Servlet in Spring Boot

In my Spring Boot application with packaging type as war, i am configuring Spring MVC. As i understand we dont have to configure Dispatcher Servlet Manually. However, i old style of web.xml i used to configure Dispatcher Servlet and then i used to pass contextClass and contextConfigLocation as follows
<servlet>
<description>
</description>
<display-name>DispatcherServlet</display-name>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<description>contextClass</description>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<description>contextConfigLocation</description>
<param-name>contextConfigLocation</param-name>
<param-value>com.xxx.yyy.jdorderspringmvcweb.config.SpringMvcConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
I belive this was to indicate that SpringMvcConfig (my custom class with spring mvc configuration) is the configuration class for Spring MVC..
However, In spring boot if Dispatcher Servlet is configured Automatically, how can i pass my custom class to dispatcher Servlet ?
In my Spring Boot application, my SpringMvcConfig class extends from WebMvcConfigurerAdapter and is annotated with #Configuration class
Help Needed...
Right in the configuration class which is annotated by #Configuration you could define your dispatcherServlet and pass init-parameter to it.
#Bean
public ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(dispatcherServlet());
registrationBean.addInitParameter("contextClass","org.springframework.web.context.support.AnnotationConfigWebApplicationContext");
registrationBean.addInitParameter("contextConfigLocation","com.xxx.yyy.jdorderspringmvcweb.config.SpringMvcConfig");
return registrationBean;
}
Another way would be to create a paramter map and then set parameter for registration bean. This stream shows how to do it.
I think you have to create a config class as follow:
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MySpringMvcDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
// TODO Auto-generated method stub
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { DemoAppConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}

Not being able to find my mapping anymore (spring - controller)

I am not being able to find my mapping anymore (I made it work. But then, I made changes, but probably due to cache, I didn't realize when it stopped working :-( ). It gives 404 error now when I click on submit on the form. Any help would be appreciated. Thank you in advance!
web.xml:
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
JSP:
<form:form method="POST" action="/app/app/studentSearchById" commandName="student">
Controller:
#Controller
// #RequestMapping("/studentSearch")
public class StudentSearchController {
// #Autowired
//StudentService userService = new StudentService();
// #RequestMapping(value="/StudentSearchById", method = RequestMethod.GET)
// public ModelAndView searchById(#PathVariable Long studentId) {
#RequestMapping(value = "/studentSearchById", method = RequestMethod.POST)
public ModelAndView searchById(#ModelAttribute("student") Student student, Map<String, Object> map,
HttpServletRequest request) {
StudentService userService = new StudentService();
Student studentFound = userService.findStudent(student.getStudentId());
ModelAndView modelAndView = new ModelAndView();
// modelAndView.addObject("students", studentFound);
return modelAndView;
}
Thank you a lot in advance!
When you are using stereotype annotation (#Controller , #Service, #Repository) then its better to use context: component-scan tag to say that Spring has to scan packages searching the annotation and register beans within the application context.
I spent hours on this... just after posting, I figured out...
I had changed the line on my spring-servlet.xml. Putting the line as before, it worked:
<context:component-scan base-package="my.controller.package" />

How to configure Jersey with Spring using only annotations

I have a Servlet 3.0 web app that uses both Spring and Jersey. I currently have it set up using the SpringServlet configured as a filter in web.xml, and the resource classes annotated with both #Path and #Component. Here's the web.xml snippet:
<filter>
<filter-name>jersey-serlvet</filter-name>
<filter-class>
com.sun.jersey.spi.spring.container.servlet.SpringServlet
</filter-class>
<init-param>
<param-name>
com.sun.jersey.config.property.packages
</param-name>
<param-value>com.foo;com.bar</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.feature.FilterForwardOn404</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jersey-serlvet</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
This setup works, but I really want to get this set up with annotations only - no web.xml config. My first attempt at this was to remove the above SpringServlet configuration and create a class that extends Application. Here's a snippet of that:
#ApplicationPath("/*")
public class MyApplication extends PackagesResourceConfig {
public MyApplication() {
super("com.foo;com.bar");
HashMap<String, Object> settings = new HashMap<String, Object>(1);
settings.put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
this.setPropertiesAndFeatures(settings);
}
}
This works in that the JAX-RS resources are registered and I can hit them at their URLs, but they throw NullPointerExceptions when they try and use their autowired properties... this makes sense because I'm guessing the resources are now being loaded by Jersey and are not Spring managed beans, therefore no autowiring.
Despite a fair bit of searching around I cannot find any way of loading the Jersey resources as Spring beans with annotations only. Is there such a way? I don't really want to have to write a bunch of code for the resources to manually fetch the Spring context and invoke the DI if I can help it.
If annotations-only isn't going to work, then I can live with the filter config in web.xml if I can specify an Application class to load instead of a list of packages to scan. If I can get rid of the package list in there and just specify an Application class instance then I'll be content.
Obviously it would be great if someone had a definitive answer for me but I'd also be grateful for any pointers or hints of where else I could look or things to try.
Thanks,
Matt
Below is part of my app, which uses Servlet 3.0, Spring, Jersey 1.8 and it has no web.xml:
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
final AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("com.myapp.config");
final FilterRegistration.Dynamic characterEncodingFilter = servletContext.addFilter("characterEncodingFilter", new CharacterEncodingFilter());
characterEncodingFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
characterEncodingFilter.setInitParameter("encoding", "UTF-8");
characterEncodingFilter.setInitParameter("forceEncoding", "true");
final FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy());
springSecurityFilterChain.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
servletContext.addListener(new ContextLoaderListener(context));
servletContext.setInitParameter("spring.profiles.default", "production");
final SpringServlet servlet = new SpringServlet();
final ServletRegistration.Dynamic appServlet = servletContext.addServlet("appServlet", servlet);
appServlet.setInitParameter("com.sun.jersey.config.property.packages", "com.myapp.api");
appServlet.setInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters", "com.myapp.api.SizeLimitFilter");
appServlet.setLoadOnStartup(1);
final Set<String> mappingConflicts = appServlet.addMapping("/api/*");
if (!mappingConflicts.isEmpty()) {
throw new IllegalStateException("'appServlet' cannot be mapped to '/' under Tomcat versions <= 7.0.14");
}
}
}
I haven't been able to get my ideal result but I have been able to make some progress, so I'll post here in case it helps anyone else. I was able to use the Spring Servlet to specify my application class, thereby removing the package list from the web.xml.
The web.xml changes required are in the init params (the filter mapping is not shown but is still required):
<filter>
<filter-name>jersey-serlvet</filter-name>
<filter-class>
com.sun.jersey.spi.spring.container.servlet.SpringServlet
</filter-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name> <!-- Specify application class here -->
<param-value>com.foo.MyApplication</param-value>
</init-param>
</filter>
And then in the application class I had to change the way I called the super constructor slightly:
public MyApplication() {
super("com.foo", "com.bar"); // Pass in packages as separate params
HashMap<String, Object> settings = new HashMap<String, Object>(1);
settings.put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
this.setPropertiesAndFeatures(settings);
}
Still not exactly what I was after but at least this pulls a little more config into Java code and out of the web.xml, which is important for me as I'm trying to hide this detail.
Two options spring to mind (no pun intended).
Maybe you could extend SpringServlet with your own class and add appropriate servlet 3.0 annotations to it.
Going along with your approach of switching from the SpringServlet to an Application class, you could solve the no-autowiring problem by enabling Spring build-time or load-time bytecode weaving. That enables Spring to inject objects instantiated by anywhere instead of only objects created by Spring. See "Using AspectJ to dependency inject domain objects with Spring".
First of all, in a servlet 3.0 container you don't really need a web.xml.
But with Jersey 2.0 you can set a flag to scan the whole web app for annotated resources:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>jersey</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.servlet.provider.webapp</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Spring will be enabled automatically if you include this jar:
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>2.3.1</version>
</dependency>
I used Jersey with my previously made project using SpringMVC. I based my code on the Spring's official documentation.
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
// Don't create the Listener that Jersey uses to create.
// There can only be one linstener
servletContext.setInitParameter("contextConfigLocation", "<NONE>");
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// Add app config packages
context.setConfigLocation("config.package");
// Add listener to the context
servletContext.addListener(new ContextLoaderListener(context));
// Replacing:
// <servlet-name>ServletName</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>webservices.packages</param-value>
// </init-param>
// <load-on-startup>1</load-on-startup>
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
ServletRegistration.Dynamic appServlet = servletContext.addServlet("ServletName", new DispatcherServlet(dispatcherContext));
appServlet.setInitParameter("com.sun.jersey.config.property.packages", "org.sunnycake.aton.controller");
appServlet.setLoadOnStartup(1);
appServlet.addMapping("/RootApp");
}
}
The configuration classes in config.package are:
// Specifies that there will be bean methods annotated with #Bean tag
// and will be managed by Spring
#Configuration
// Equivalent to context:component-scan base-package="..." in the xml, states
// where to find the beans controlled by Spring
#ComponentScan(basePackages = "config.package")
public class AppConfig {
/**
* Where will the project views be.
*
* #return ViewResolver como el XML
*/
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
return viewResolver;
}
}
Hibernate configuration
// Specifies that there will be bean methods annotated with #Bean tag
// and will be managed by Spring
#Configuration
// Equivalent to Spring's tx in the xml
#EnableTransactionManagement
// Equivalent to context:component-scan base-package="..." in the xml, states
// where to find the beans controlled by Spring
#ComponentScan({"config.package"})
// Here it can be stated some Spring properties with a properties file
#PropertySource(value = {"classpath:aplicacion.properties"})
public class HibernateConfig {
/**
* Inyected by Spring based on the .properties file in the
* \#PropertySource tag.
*/
#Autowired
private Environment environment;
/**
* Here it's created a Session Factory, equivalent to the Spring's config file one.
*
* #return Spring Session factory
*/
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
// Uses the datasource
sessionFactory.setDataSource(dataSource());
// Indicates where are the POJOs (DTO)
sessionFactory.setPackagesToScan(new String[]{"dto.package"});
// Se asignan las propiedades de Hibernate
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
/**
* Propiedades de la base de datos (Según environment)
*
* #return Nuevo DataSource (Configuración de la base de datos)
*/
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
return dataSource;
}
/**
* Hibernate properties
*
* #return Properties set with the configuration
*/
private Properties hibernateProperties() {
Properties properties = new Properties();
// Dialect (Mysql, postgresql, ...)
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
// Show SQL query
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
return properties;
}
/**
* Inyected by sessionFactory
*/
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
}
This is a full example. First of all - don't use any web.xml. Use only code below.
Rest resource:
#Path("hello")
public class HelloResource {
#GET
#Produces(MediaType.TEXT_PLAIN)
public Response hello() {
String output = "Hello World!";
return Response.status(200).entity(output).build();
}
}
Rest application (note "core" in package name)
#ApplicationPath("rest")
public class RestApplication extends javax.ws.rs.core.Application {
public RestApplication() {
}
#Override public Set<Class<?>> getClasses() {
return Set.of(
HelloResource.class
);
}
}
Spring web configuration.
#Configuration
#EnableWebMvc
#ComponentScan(basePackageClasses = {
})
public class WebConfig implements WebMvcConfigurer {
private static final Logger logger = LoggerFactory.getLogger(WebConfig.class);
#Autowired
private ApplicationContext applicationContext;
public WebConfig() {
}
}
Spring initializer
//This #Order is required!!!
#Order(Ordered.HIGHEST_PRECEDENCE)
public class MyWebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
var ctx = new AnnotationConfigWebApplicationContext();
//spring WebMvcConfigurer
ctx.register(WebConfig.class);
ctx.setServletContext(servletContext);
//Spring servlet
var servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.setLoadOnStartup(1);
servlet.addMapping("/");
// Register Jersey 2.0 servlet
ServletRegistration.Dynamic jerseyServlet = servletContext.addServlet("jerseyServlet",
"org.glassfish.jersey.servlet.ServletContainer");
//note "javax.ws.rs.Application" doesn't have "core"
jerseyServlet.setInitParameter("javax.ws.rs.Application", RestApplication.class.getName());
jerseyServlet.addMapping("/rest/*");
jerseyServlet.setLoadOnStartup(1);
}
}
And it must work on, for example, http://127.0.0.1:8080/rest/hello

Resources