I'm creating a spring-rest-app.
This is my dispatcher config (I also have a root config that has DataSource bean)
#Configuration
#ComponentScan(basePackages= {"config", "cache", "dao", "entity", "exception", "rest", "service"})
#EnableWebMvc
public class DispatcherConfiguration {
#Bean
public KeyCache keyCache() {
return new KeyCacheImpl();
}
}
This is my webapp initializer
public class TinyURLKeyServiceInitializor implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext appcontext = new AnnotationConfigWebApplicationContext();
appcontext.register(ConfigurationClass.class);
servletContext.addListener(new ContextLoaderListener(appcontext));
AnnotationConfigWebApplicationContext dispatchercontext = new AnnotationConfigWebApplicationContext();
dispatchercontext.register(DispatcherConfiguration.class);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(dispatchercontext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
This is the controller
#Controller
#RequestMapping("/api/keyservice")
public class KeyServiceController {
#Autowired
private KeyService keyService;
#GetMapping(value="/key", produces="application/json")
#ResponseBody
public String getKey() {
return keyService.getKey();
}
}
When I startup the Web app and Send - GET http://localhost:7080/api/keyservice/key
I get the following in DEBUG LOG
[INFO] Completed initialization in 1006 ms
May 17, 2020 11:56:10 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-7080"]
[DEBUG] GET "/api/keyservice/key", parameters={}
[WARNING] No mapping for GET /api/keyservice/key
[DEBUG] Completed 404 NOT_FOUND
I've put #EnableMvc for registring MappingHandlers. But still they are not able to detect the mapping between endpoint and controller method.
I put a debug point in DispatcherServlet.getHandler and it returns null everytime. Has anyone ever faced similar problem?
Found the reason I was hitting -
http://localhost:7080/api/keyservice/key
My pom.xml had -
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>7080</port>
<path>/api/keyservice</path>
</configuration>
</plugin>
My Controller
#Controller
#RequestMapping("/api/keyservice")
public class KeyServiceController {
#Autowired
private KeyService keyService;
#GetMapping(value="/key", produces="application/json")
#ResponseBody
public String getKey() {
Since context path = /api/keyservice (due to setting in pom.xml), spring was trying to find a mapping for /key. Cleary there is no mapping for /key in my controller.
Removed the controller RequestMapping. And it worked.
Related
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.
I'm trying to start Spring application from main without Spring Boot, I managed to get working router functions, but can't make to work classic #Controllers.
I debugged and watched source of boot and built in configs, added RequestMappingHandlerMapping, and it registers my Controller, but anyway WebHandler can not see it.
I can see that spring discovered my #Controller as a bean so it is not a problem.
Can someone help me? Thanx.
When I run the application I get:
Apr 13, 2018 7:27:30 PM
org.springframework.context.support.AbstractApplicationContext
prepareRefresh INFO: Refreshing
org.springframework.context.annotation.AnnotationConfigApplicationContext#20e2cbe0:
startup date [Fri Apr 13 19:27:30 EEST 2018]; root of context
hierarchy Apr 13, 2018 7:27:31 PM
org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping$MappingRegistry
register INFO: Mapped "{[/test],methods=[GET]}" onto public
java.lang.String test.wboot.Controller.test() Apr 13, 2018 7:27:31 PM
org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping$MappingRegistry
register INFO: Mapped "{[/test],methods=[GET]}" onto public
java.lang.String test.wboot.Controller.test() [DEBUG] (main) Using
Console logging [DEBUG] (main) Default Epoll support : false [DEBUG]
(main) Default KQueue support : false [DEBUG] (main) Connecting new
channel:
AbstractBootstrap$PendingRegistrationPromise#4a3329b9(incomplete)
Press button to exit
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.start();
}
}
.
#Configuration
#ComponentScan("test.wboot")
public class AppConfig {
public static final int port = 9099;
#Bean
public NettyContext servak(ApplicationContext applicationContext) throws IOException {
// this handler works just perfect
// HttpHandler httpHandler = RouterFunctions.toHttpHandler(routingFunction());
// And this one can not see Controller
HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(applicationContext).build();
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
NettyContext localhost = HttpServer.create("localhost", port).newHandler(adapter).block();
System.out.println("Press button to exit");
System.in.read();
return localhost;
}
public RouterFunction<ServerResponse> routingFunction() {
return nest(path("/test"), route(GET(""), new HandlerFunction<ServerResponse>() {
public Mono<ServerResponse> handle(ServerRequest request) {
return ServerResponse.ok().syncBody("Hello yopta");
}
}));
}
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(ApplicationContext applicationContext) {
RequestMappingHandlerMapping requestMappingHandlerMapping = new RequestMappingHandlerMapping();
requestMappingHandlerMapping.setApplicationContext(applicationContext);
requestMappingHandlerMapping.afterPropertiesSet();
return requestMappingHandlerMapping;
}
#Bean
public WebHandler webHandler(ApplicationContext applicationContext) {
DispatcherHandler webHandler = new DispatcherHandler();
webHandler.setApplicationContext(applicationContext);
return webHandler;
}
}
.
#RestController
#RequestMapping("/test")
public class Controller {
#GetMapping
public String test() {
return "work";
}
}
Maven dependencies:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>io.projectreactor.ipc</groupId>
<artifactId>reactor-netty</artifactId>
<version>0.7.6.RELEASE</version>
</dependency>
</dependencies>
Why are you redifining everything?
Spring Framework provides an #EnableWebFlux annotation that does this and more, so you should just add that annotation on a #Configuration class.
You then just have to follow the instructions here to start your server using the WebHandler automatically created by the Framework; so for Reactor Netty, this would look like:
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create(host, port).newHandler(adapter).block();
I'm building REST services via Spring MVC in the application where the GUI is managed by Wicket. Basically, all I need is the DispatcherServlet and a controller with #RequestMapping/#RequestBody.
Because services serve JSON, I need to set MappingJackson2HttpMessageConverter. I can do this via AnnotationMethodHandlerAdapter and that works fine:
#Configuration
#ComponentScan("cz.swsamuraj.wicketspring")
public class SpringRestConfiguration {
#Bean
public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter() {
HttpMessageConverter<?>[] converters = { new MappingJackson2HttpMessageConverter()};
AnnotationMethodHandlerAdapter adapter = new AnnotationMethodHandlerAdapter();
adapter.setMessageConverters(converters);
return adapter;
}
}
The problem is that AnnotationMethodHandlerAdapter is deprecated and it's recommended to use RequestMappingHandlerAdapter instead.
But if I use this configuration:
#Configuration
#ComponentScan("cz.swsamuraj.wicketspring")
public class SpringRestConfiguration {
#Bean
public RequestMappingHandlerAdapter requestHandler() {
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
return adapter;
}
}
I receive an exception:
javax.servlet.ServletException: No adapter for handler [cz.swsamuraj.wicketspring.ws.api.QuestionApiController#69f8a79f]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler
at org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(DispatcherServlet.java:1198)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
So, my question is: how can I set up a handler adapter in the RequestMappingHandlerAdapter?
I spent couple of days of research, but I didn't find any useful example of how to configure RequestMappingHandlerAdapter. All the advices just says to put #EnableWebMvc on the configuration, but this is not the way because of this Wicket-Spring coexistence.
Just to provide a full context, I've created a small buildable and runnable project on Bitbucket: sw-samuraj/blog-wicket-spring-rest
I was able to solve my problem with different approach - with usage of WebApplicationInitializer, I was able to put the #EnableWebMvc annotation on my configuration class and therefore neither the bean RequestMappingHandlerAdapter, nor AnnotationMethodHandlerAdapter are necessary. JSON now works fine, out-of-the-box.
Configuration
#Configuration
#EnableWebMvc
#ComponentScan("cz.swsamuraj.wicketspring")
public class SpringRestConfiguration {
// some additional beans needed for business logic
}
WebApplicationInitializer
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.register(SpringRestConfiguration.class);
servletContext.addListener(new ContextLoaderListener(dispatcherContext));
ServletRegistration.Dynamic dispatcher =
servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/rest/*");
}
}
Example project
Complete working example is on Bitbucket: blog-wicket-spring-rest
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
}
}
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() { }
}