I'm building a RESTful API and have a Spring REST Controller (#RestController) and an annotation-based configuration. I'd like to have my project's welcome-file be a .html or .jsp file with the API documentation.
In other web projects I would place a welcome-file-list in my web.xml, but in this particular project I can't seem to get it to work (preferrably using Java and annotations).
This is my WebApplicationInitializer
public class WebAppInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(ApplicationConfig.class);
context.setServletContext(servletContext);
ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcher",
new DispatcherServlet(context));
dynamic.addMapping("/");
dynamic.setLoadOnStartup(1);
}
}
This is my WebMvcConfigurerAdapter
#Configuration
#ComponentScan("controller")
#EnableWebMvc
public class ApplicationConfig extends WebMvcConfigurerAdapter {
#Bean
public Application application() {
return new Application("Memory");
}
}
And this is a small part of my REST Controller
#RestController
#RequestMapping("/categories")
public class CategoryRestController {
#Autowired
Application application;
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<Integer, Category>> getCategories(){
if(application.getCategories().isEmpty()) {
return new ResponseEntity<Map<Integer, Category>>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<Map<Integer, Category>>(application.getCategories(), HttpStatus.OK);
}
}
So far I've tried:
Adding just a web.xml with a <welcome-file-list> with a <welcome-file>. (no luck there)
Moving the #RequestMapping("/categories") in the Controller from the class level to all of the methods, and adding a new method with #RequestMapping("/"), which returns either a String or a ModelAndView with the view name. (the former just returned a blank page with the String, for the latter no mapping could be found)
As suggested here: a combination of both, where my web.xml <welcome-file> is "/index", combined with #RequestMapping(value="/index") returning a new ModelAndView("index"), and a ViewResolver in my configuration class. (returns a Warning: No mapping found in DispatcherServlet with name 'dispatcher', even though "/index" is successfully mapped. Manually adding "/index" to the URL successfully resolves it to index.jsp)
When specifying a controller to handle your index page you should use a #Controller not a #RestController. Although the #RestController is a #Controller it doesn't resolve to a view but returns the result as is to the client. When using a #Controller when returning a String it will resolve to the name of a view.
#Controller
public class IndexController {
#RequestMapping("/")
public String index() {
return "index";
}
}
However there is an easier way to configure this and you don't need a controller for it. Configure a view controller. In your configuration class simply override/implement the addViewControllers method.
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
That way you don't even need to create a class for it.
Related
I'm trying to map resourceHandlers to resourceLocations in a Spring MVC application, but somehow I can't make the mapping between /* and my index.html work.
#Configuration
#EnableWebMvc
#EnableTransactionManagement(proxyTargetClass = true)
#EnableScheduling
#ComponentScan(basePackages = {"mySpringApp.web.controller"})
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/*", "/", "").addResourceLocations("classpath:/static/index.html");
registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/static/assets/");
}
...
The requests I make returns the following:
http://localhost:8080/ - 404
http://localhost:8080/index.html - 200(returns the index-file that I wan't on /* )
http://localhost:8080/assets/main.js - 200(returns one of the files
located in my assets-folder)
Any idea why the index-mapping fails?
When http://localhost:8080/ is requested, spring is looking for a controller with a mapping looking like #RequestMapping("") or #RequestMapping("/"), which you do not have.
registry.addResourceHandler("/*", "/", "").addResourceLocations("classpath:/static/index.html");
not serving index file because:
you are simply not specifying what resource you want spring to serve.
possible solutions
Write a controller
#RequestMapping(value = "/")
public String index() {
return "redirect:index.html";
}
Refactor addResourceHandlers()
registry.addResourceHandler("*.html").addResourceLocations("/static/");
Note that addResourceHandler is adding a "pattern" while addResourceLocation is telling spring where exactly/physically to find the requested resource.
I am using spring again after a longer period and happy to see that xml configurations are no longer required in every case.
I want to build a RESTful App, but I still have to deliver the frontend app. I figured the simplest way without using any additional template engines like thymeleaf would be serving a static jsp.
I'm using the project template from start.spring.io with just spring-mvc as dependency, thus i'm using spring boot as well.
I wrote a controller in order to deliver the jsp, but it seems that the mapping for the views has to be configured first.
#Controller
public class StaticPagesController {
#RequestMapping(value = "/")
public String index(){
return "index";
}
}
So i created a configuration class:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "de.tuberlin.sense.emp")
public class WebConfiguration {
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".html");
return viewResolver;
}
}
index.html is located in main/webapp/WEB-INF/views/
When i send a request to /, I get a WARN in the logs wich states No mapping found for HTTP request with URI [/WEB-INF/views/index.html] in DispatcherServlet with name 'dispatcherServlet'
What am I missing? Can I do this without any xml configuration?
Here is my main application class code:
UPDATE:
#SpringBootApplication
public class ExperimentManagementPlatformApplication {
public static void main(String[] args) {
SpringApplication.run(ExperimentManagementPlatformApplication.class, args);
}
}
Try adding a view controller:
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
Reference
It seems to me that your controller is not being detected, I would suggest to check your file structure, is the StaticPagesController controller in the de.tuberlin.sense.emp package? (as stated in:)
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "de.tuberlin.sense.emp")
public class WebConfiguration
I was able to serve the static page without needing any controller at all by just adding the html file to the static directory. Furthermore i found out that the entire webapp directory is not included when the app is deployed as jar which explains why the files in there could not be found.
The Spring documentation
Your config class should extend WebMvcConfigurerAdapter class
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "de.tuberlin.sense.emp")
public class WebConfiguration extends WebMvcConfigurerAdapter{
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".html");
return viewResolver;
}
}
I want to create a spring app that doesn't use any XMLs at all (no web.xml no context.xml or anything). So far it seems to work quite fine, except that my view resolver has some problems and I cannot figure it out on my own.
here's my WebApplicationInitializer
public class AppConfig implements WebApplicationInitializer {
private AnnotationConfigWebApplicationContext getContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("fi.cogniti.service.config");
return context;
}
#Override
public void onStartup(javax.servlet.ServletContext servletContext) throws ServletException {
WebApplicationContext context = getContext();
servletContext.addListener(new ContextLoaderListener(context));
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(
context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/*");
// Enabling spring security
// servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"))
// .addMappingForUrlPatterns(null, false, "/*");
}
}
and my spring configuration
#Configuration
#EnableWebMvc
#ComponentScan("fi.cogniti.service")
public class SpringConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
}
#Bean
public ViewResolver getViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setViewClass(JstlView.class);
resolver.setPrefix("/pages/");
resolver.setSuffix(".jsp");
return resolver;
}
}
and finally my controller
#Controller
#RequestMapping("/")
public class HomeController {
#RequestMapping
public String entry() {
return "index";
}
}
index.jsp is located in src/main/webapp/pages/index.jsp.
So, if in my controller I use the annotation #ResponseBody, then the controller gives me the response "index", hence I know that my configuration works at least to some extent, however, if I remove the annotation in hopes that it would return the content of index.jsp, I only get a 404 error.
Any suggestions?
Change:
dispatcher.addMapping("/*");
To something that doesn't match everything (you will get this error otherwise). For example:
dispatcher.addMapping("*.html");
In this way, http://localhost:8080/.html should show the jsp (change the #RequestMapping("/") in the controller to something more 'human')
You also should change this line including WEB-INF:
resolver.setPrefix("/WEB-INF/pages/");
I'm not sure if WebApplicationInitializer is getting executed (check this, where they are suggesting to use ServletContextInitializer but it's still creating issues).
If this is the case, you couldn't use the .addMapping("*.html"). If this is the case, you can add the following lines to SpringConfig in order to achieve the same result:
#Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
#Bean
public ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet(), "*.html");
registration
.setName(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
return registration;
}
Currently all your jsp files are accessible by everyone, it is recommended to put them in WEB-INF instead at the top level.
Then modify your configuration for the view resolver to the following.
#Bean
public ViewResolver getViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/pages/");
resolver.setSuffix(".jsp");
return resolver;
}
You don't need to set the viewClass property as that is determined for you.
Next add the following to have the DispatcherServlet pass on requests that it cannot handle itself. This is needed due to the fact that you mapped the servlet to /.
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
With these 2 changes you have secured your jsps from being accessible by everyone and made default resources being handled again.
I would strongly suggest using Spring Boot as that would really simplify your life.
In your controller the method should look like:
#RequestMapping(method = RequestMethod.GET)
public ModelAndView entry() {
return new ModelAndView("index");
}
When request is processed by spring controller, the service is not wired:
#Controller
#RequestMapping(value = "/login")
public class LoginController {
#Inject
private AccountService accountService;
#RequestMapping(method = RequestMethod.POST)
public String handleLogin(HttpServletRequest request, HttpSession session){
try {
...
//Next line throws NullPointerException, this.accountService is null
Account account = this.accountService.login(username, password);
} catch (RuntimeException e) {
request.setAttribute("exception", e);
return "login";
}
}
}
AccountService and its only implementation are defined in module service as:
package com.savdev.springmvcexample.service;
...
public interface AccountService {
...
package com.savdev.springmvcexample.service;
#Service("accountService")
#Repository
#Transactional
public class AccountServiceImpl implements AccountService {
The web configuration is loaded by files, located in web module:
package com.savdev.springmvcexample.web.config;
public class SpringMvcExampleWebApplicationInitializer implements WebApplicationInitializer
{
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerDispatcherServlet(servletContext);
}
private void registerDispatcherServlet(final ServletContext servletContext) {
WebApplicationContext dispatcherContext = createContext(WebMvcContextConfiguration.class);
...
}
private WebApplicationContext createContext(final Class<?>... annotatedClasses) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(annotatedClasses);
return context;
}
The WebMvcContextConfiguration file that scans packages to discovery beans:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { "com.savdev.springmvcexample.web", "com.savdev.springmvcexample.service" })
public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter {
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setOrder(1);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
This class is loaded, cause view resolving is used according to InternalResourceViewResolver logic. As a result the "com.savdev.springmvcexample.web" is scanned cause controller that processes request is found.
The "com.savdev.springmvcexample.service" is scanned, but it is in another module, I don't know can it be an issue or not, but I don't get any errors.
UPDATED:
#JBNizet, module - means module in maven multimodule project. I've removed #Repository and now I'm getting an error in test:
NoSuchBeanDefinitionException: No qualifying bean of type
[javax.sql.DataSource] found for dependency.
That means, that means the spring profile is not activated. DataSource is loaded only for profiles.
In web infrastructure I manage profiles with:
public class SpringMvcExampleProfilesInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext> {
#Override
public void initialize(ConfigurableWebApplicationContext ctx) {
ConfigurableEnvironment environment = ctx.getEnvironment();
List<String> profiles = new ArrayList<String>(getProfiles());
if( profiles == null || profiles.isEmpty() )
{
throw new IllegalArgumentException("Profiles have not been configured");
}
environment.setActiveProfiles(profiles.toArray( new String[0]));
}
//TODO add logic
private Collection<String> getProfiles() {
return Lists.newArrayList("file_based", "test_data");
}
}
If I'm not wrong SpringMvcExampleProfilesInitializer is used before Spring ApplicationContext is loaded. And it is made automatically. Nothing additional has to be configured for this. But it's not working. Please fix me, if I'm wrong.
Please note, the initializer has the following signature:
SpringMvcExampleProfilesInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext>
At the moment when DispatcherServlet is configured I can setup it using:
setContextInitializers(ApplicationContextInitializer<ConfigurableApplicationContext>... contextInitializers)
How can I setup setContextInitializers but pass something that implements ApplicationContextInitializer<ConfigurableWebApplicationContext>, but not ApplicationContextInitializer<ConfigurableApplicationContext>
#Inject requires the presence of the JSR 330 jar on the classpath. It could be that it's visible at compile time but not at runtime.
Try this:
#Autowired(required=true)
private AccountService accountService;
If this does not work, it means that the bean AccountServiceImpl is not being scanned correctly.
If it does work, it means that there is a problem with #Inject support (maybe a missing jar at runtime).
If the scanning is not working, try to do the scanning via xml:
<context:component-scan base-package="com.savdev.springmvcexample.service" />
Can you post back:
If #Autowired(required=true) works
if and how the JSR-330 is added in the poms (output of mvn dependency:tree)
did the scanning via xml worked
I am trying to use a request scoped been inside my servlet 3.0 application.
I am not using the web.xml but an implementation of WebApplicationInitializer. The onStartup method looks like this:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext applicationContext =
new AnnotationConfigWebApplicationContext();
applicationContext.setServletContext(servletContext);
applicationContext.scan("package containing proxy scoped bean and other stuff");
applicationContext.refresh();
servletContext.addListener(new ContextLoaderListener(applicationContext));
servletContext.addListener(new RequestContextListener());
}
and the request scoped bean looks like this:
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CallerInformation {
private String clientIp;
public String getClientIp() {
return clientIp;
}
public void setClientIp(String clientIp) {
this.clientIp = clientIp;
}
}
Now the injected "CallerInformation" is not a CGLIB-proxy but behaves like prototype scoped, it is a different instance in every class and it does not hold any information through the request...
Any ideas what I am doing wrong?
EDIT:
I have tried the same scenario with servlet 2.5 and web.xml config and it worked like hell ;)
the behavior is right.
the instance only lives the one request.
see this site for further information.
try to inject the spring-component with the #Autowired annotation