I use Spring Boot and my main controller handles all top urls:
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
#Controller
public class IndexController {
#GetMapping({"/*"})
public ResponseEntity<String> handleIndex() {
// ...
}
}
And for some reasons I want exclude path /exclude_path from handling of my controller.
Something like this:
#GetMapping(include={"/*"}, exclude={"/exclude_path"})
Could you please answer how I may do this?
Firstly, /* is a bad practice. If at all you want to do it, your best option would be to introduce an Aspect for your controller. And filter it out there.
Something like this will do:
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMapping() {}
#Pointcut("within(blah.blah.package.controller.*)")
public void myController() {}
#Around("requestMapping() && myController()")
public Object logAround(ProceedingJoinPoint joinPoint, RequestMapping requestMapping) throws Throwable {
String url = requestMapping.value();
// check the url and based on that do
// joinPoint.proceed();
}
You should remove “/*” as it will except all the requests. Provide only the path you want so you don’t need /excludepath.
Related
This question already has answers here:
Why does self-invocation not work for Spring proxies (e.g. with AOP)?
(2 answers)
Closed 23 days ago.
I have created a simple custom annotation in Spring Boot that logs something and it's working but only for the first annotation, the nested one are not called
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Traceable {
}
Annotation processor (aspect)
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class TraceableAspect {
#Around("#annotation(Traceable)")
public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Inside Aspect");
Object result = joinPoint.proceed();
System.out.println(result);
return result;
}
}
Example of a controller used to test
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
#RequestMapping("/test")
public class ControllerTest {
#GetMapping("/get")
#Traceable
public String get(){
test1();
test2();
return "Hi";
}
#Traceable
public void test1(){
String str = "1";
System.out.println(str);
}
#Traceable
public Object test2(){
String str = "2";
System.out.println(str);
test1();
return null;
}
}
The console output here is:
Inside Aspect
1
2
1
Hi
but I think it's worng, it has to be like this:
Inside Aspect
Inside Aspect
1
Inside Aspect
2
Inside Aspect
1
Hi
It seems that only the first #Traceable is processed, all the other ones are ignored. How to handle this? Thanks
To understand why you're getting the results you see, you have to understand how Spring implements handling of most behavioral annotations: using proxies. That means that only method calls that come through the proxy get the annotation behavior. That's the typical scenario when one object calls a reference it has to another object; the reference is actually to the proxy, which intercepts the call and wraps the behavior (in this case, logging) around it. However, when calling methods within the same instance, there is no proxy in play (Spring doesn't/can't replace this with a reference to the proxy), and thus no annotation behavior.
There are a few ways to work around that, some ideas are discussed in this SO Q&A. There are also some answers here with options to work around the proxying limitation.
In my Spring (4.3.2) project I'm using Swagger (2.7.0) to automatically generate docs and swagger-ui for my project. This worked great so far.
But now I determined that I need to be able to declare Path Variables at the Controller level (not method level). And I need to teach swagger to discover these path variables and add them to docs and swagger-ui.
I've created custom annotation
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface HasCommonPathVariable {
/**
* The URI template variable to bind to.
*/
String name();
Class<?> type();
String defaultValue() default "";
}
And I'm using it like this:
#RestController
#Secured(SecurityConstants.ROLE_USER)
#RequestMapping(path = "/rest/api/v1/env/{envId}/asset-type")
#HasCommonPathVariable(name = "envId", type = Long.class)
public class AssetTypeRestController extends CustomRestControllerBase<Long, AssetTypeRow, AssetTypeService> {
// ... contorller code
}
I do not have controller methods that mentions parameters with Spring's PathVariable annotation, and the point is I'm not allowed to do so (it's due to the fact that I'm building micro-framework).
So question is: how to teach swagger to discover path variables described using custom annotation HasCommonPathVariable applied at the controller level?
Ok, I've figured it out. Here is the solution. This bean needs to be registered in the context. Swagger will discover this bean and use it as one of the plugins to enrich operations
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.springframework.core.annotation.Order;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.swagger.common.SwaggerPluginSupport;
#Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
public class CommonPathVariableOperationBuilderPlugin implements OperationBuilderPlugin {
protected Logger log = Logger.getLogger(getClass());
private TypeResolver typeResolver;
public CommonPathVariableOperationBuilderPlugin(TypeResolver typeResolver) {
this.typeResolver = typeResolver;
}
#Override
public boolean supports(DocumentationType delimiter) {
return true;
}
#Override
public void apply(OperationContext opCtx) {
List<Parameter> ret = new ArrayList<Parameter>();
Optional<HasCommonPathVariable> annSingle = opCtx.findControllerAnnotation(HasCommonPathVariable.class);
if (annSingle.isPresent()) {
ret.add(addParameter(annSingle.get()));
}
Optional<HasCommonPathVariables> annPlural = opCtx.findControllerAnnotation(HasCommonPathVariables.class);
if (annPlural.isPresent()) {
for (HasCommonPathVariable ann : annPlural.get().value()) {
ret.add(addParameter(ann));
}
}
opCtx.operationBuilder().parameters(ret);
}
private Parameter addParameter(HasCommonPathVariable ann) {
ParameterBuilder pb = new ParameterBuilder();
pb.parameterType("path").name(ann.name()).type(typeResolver.resolve(ann.type()));
pb.modelRef(new ModelRef("string"));
pb.required(true);
if (!"".equals(ann.defaultValue())) {
pb.defaultValue(ann.defaultValue());
}
return pb.build();
}
}
I'm building a spring boot application. My problem is that when I run the project, my login page is not shown. This is my code:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class RestLogin {
#RequestMapping("/")
public String login() {
return "login";
}
}
I get only a white page and "login" is written in it. When I try to change the #RestController with #Controller I get this GET http://localhost:8080/ 404 (). My login.html page is located under the webapp>tpl>login.html
How can I display my login page?
Edit
This is my application class
#SpringBootApplication
public class ExampleApplication extends SpringBootServletInitializer {
private static Logger logger = LoggerFactory.getLogger(ExampleApplication.class);
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(ExampleApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
I dont know your configuration but:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests()
.antMatchers("/**").permitAll();
http.authorizeRequests().antMatchers("/**").permitAll();
}
}
In the Application.properties file add:
spring.mvc.view.suffix: .html
Change #RestController to #Controller for RestLogin class. Also put your html file inside the static folder inside resources folder.
You need an application class with a main method. See this tutorial.
Here's a snippet:
package hello;
import java.util.Arrays;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
System.out.println("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
};
}
}
This is the normal behavior.
New version of Spring web comes with #RestController annotation which nothing but #Controller + #ResponseBody. So when you have a return statement in a method you must use #RestController or annotate your method with #ResponseBody.
Here the problem is that Spring don't know a lot about the http method type, can you please try to use #GetMapping("/") to combinbe path and method at the same time.
According to your posted code and your description, you're getting an expected behavior.
When you annotate your controller with #RestController, that means that your methods on that controller will try to return their result as JSON.
According to your code:
#RestController
public class RestLogin {
#RequestMapping("/")
public String login() {
return "login";
}
}
You're returning the String "login", that's why you're getting empty white page with the word login as JSON
If you will change the #RestController to #Controller then it no longer will return your string as JSON,
but Spring will try to figure out from the that "login" string a view, and for that you'll need to add a view resolver bean to your project.
I have a java bean being used to send JSON messages to a spring #RestController and I have bean validation setup and running just fine using #Valid. But I want to move to Protobuf/Thrift and move away from REST. It is an internal API and a lot of big companies have done away with REST internally. What this really means is that I no longer have control of the message objects - they are generated externally. I can't put annotations on them anymore.
So now my validation has to be programmatic. How do I do this? I have coded up a Validator and it works just great. But it doesn't use the nice #Valid annotation. I have to do the following:
#Service
public StuffEndpoint implements StuffThriftDef.Iface {
#Autowired
private MyValidator myValidator;
public void things(MyMessage msg) throws BindException {
BindingResult errors = new BeanPropertyBindingResult(msg, msg.getClass().getName());
errors = myValidator.validate(msg);
if (errors.hasErrors()) {
throw new BindException(errors);
} else {
doRealWork();
}
}
}
This stinks. I have to do this in every single method. Now, I can put a lot of that into one method that throws BindException and that makes it one line of code to add to every method. But that's still not great.
What I want is to see it look like this:
#Service
#Validated
public StuffEndpoint implements StuffThriftDef.Iface {
public void things(#Valid MyMessage msg) {
doRealWork();
}
}
And still get the same result. Remember, my bean has no annotations. And yes, I know I can use the #InitBinder annotation on a method. But that only works for web requests.
I don't mind injecting the correct Validator into this class, but I would prefer if my ValidatorFactory could pull the correct one based on the supports() method.
Is this possible? Is there a way to configure bean validation to actually use Spring validation instead? Do I have to hijack a Aspect somewhere? Hack into the LocalValidatorFactory or the MethodValidationPostProcessor?
Thanks.
Its pretty complicated thing to combine Spring validation and JSR-303 constrains. And there is no 'ready to use' way. The main inconvenience is that Spring validation uses BindingResult, and JSR-303 uses ConstraintValidatorContext as result of validation.
You can try to make your own validation engine, using Spring AOP. Let's consider, what we need to do for it. First of all, declare AOP dependencies (if you didn't yet):
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>
I'm using Spring of version 4.2.4.RELEASE, but of cause you can use your own. AspectJ needed for use aspect annotation. Next step, we have to create simple validator registry:
public class CustomValidatorRegistry {
private List<Validator> validatorList = new ArrayList<>();
public void addValidator(Validator validator){
validatorList.add(validator);
}
public List<Validator> getValidatorsForObject(Object o) {
List<Validator> result = new ArrayList<>();
for(Validator validator : validatorList){
if(validator.supports(o.getClass())){
result.add(validator);
}
}
return result;
}
}
As you see it is very simple class, which allow us to find validator for object. Now lets create annotation, that will be mark methods, that need to be validated:
package com.mydomain.validation;
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomValidation {
}
Because of standard BindingException class is not RuntimeException, we can't use it in overriden methods. This means we need define our own exception:
public class CustomValidatorException extends RuntimeException {
private BindingResult bindingResult;
public CustomValidatorException(BindingResult bindingResult){
this.bindingResult = bindingResult;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
Now we are ready to create an aspect that will do most of the work. Aspect will execute before methods, which marked with CustomValidation annotation:
#Aspect
#Component
public class CustomValidatingAspect {
#Autowired
private CustomValidatorRegistry registry; //aspect will use our validator registry
#Before(value = "execution(public * *(..)) && annotation(com.mydomain.validation.CustomValidation)")
public void doBefore(JoinPoint point){
Annotation[][] paramAnnotations =
((MethodSignature)point.getSignature()).getMethod().getParameterAnnotations();
for(int i=0; i<paramAnnotations.length; i++){
for(Annotation annotation : paramAnnotations[i]){
//checking for standard org.springframework.validation.annotation.Validated
if(annotation.annotationType() == Validated.class){
Object arg = point.getArgs()[i];
if(arg==null) continue;
validate(arg);
}
}
}
}
private void validate(Object arg) {
List<Validator> validatorList = registry.getValidatorsForObject(arg);
for(Validator validator : validatorList){
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if(errors.hasErrors()){
throw new CustomValidatorException(errors);
}
}
}
}
execution(public * *(..)) && #annotation(com.springapp.mvc.validators.CustomValidation) means, that this aspect will applied to any public methods of beans, which marked with #CustomValidation annotation. Also note, that to mark validated parameters we are using standard org.springframework.validation.annotation.Validated annotation. But of cause we could make our custom. I think other code of aspect is very simple and does not need any comments. Further code of example validator:
public class PersonValidator implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return aClass==Person.class;
}
#Override
public void validate(Object o, Errors errors) {
Person person = (Person)o;
if(person.getAge()<=0){
errors.rejectValue("age", "Age is too small");
}
}
}
Now we have make tune the configuration and all ready to use:
#Configuration
#ComponentScan(basePackages = "com.mydomain")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig{
.....
#Bean
public CustomValidatorRegistry validatorRegistry(){
CustomValidatorRegistry registry = new CustomValidatorRegistry();
registry.addValidator(new PersonValidator());
return registry;
}
}
Note, proxyTargetClass is true because we will use cglib class proxy.
Example of target method in service class:
#Service
public class PersonService{
#CustomValidation
public void savePerson(#Validated Person person){
....
}
}
Because of #CustomValidation annotation aspect will be applied, and because of #Validated annotation person will be validated. And example of usage of service in controller(or any other class):
#Controller
public class PersonConroller{
#Autowired
private PersonService service;
public String savePerson(#ModelAttribute Person person, ModelMap model){
try{
service.savePerson(person);
}catch(CustomValidatorException e){
model.addAttribute("errors", e.getBindingResult());
return "viewname";
}
return "viewname";
}
}
Keep in mind, that if you will invoke #CustomValidation from methods of PersonService class, validation will not work. Because it will invoke methods of original class, but not proxy. This means, that you can invoke this methods only from outside of class (from other classes), if you want validation to be working (eg #Transactional works same way).
Sorry for long post. My answer is not about 'simple declarative way', and possible you will do not need it. But I was curious resolve this problem.
I marked #Ken's answer as correct because it is. But I have taken it a little further and wanted to post what I have made. I hope anybody coming to this page will find it interesting. I might try to get it in front of the Spring folks to see if it might be something included in future releases.
The idea is to have a new annotation to replace #Valid. So I called it #SpringValid. Using this annotation would kick off the system put together above. Here are all the pieces:
SpringValid.java
package org.springframework.validation.annotation;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Target({METHOD, FIELD, CONSTRUCTOR, PARAMETER})
#Retention(RUNTIME)
public #interface SpringValid {
}
SpringValidationAspect.java
package org.springframework.validation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
#Aspect
#Component
public class SpringValidationAspect {
private SpringValidatorRegistry springValidatorRegistry;
#Autowired
public SpringValidationAspect(final SpringValidatorRegistry springValidatorRegistry) {
this.springValidatorRegistry = springValidatorRegistry;
}
public SpringValidatorRegistry getSpringValidatorRegistry() {
return springValidatorRegistry;
}
#Before("#target(org.springframework.validation.annotation.Validated) "
+ "&& execution(public * *(#org.springframework.validation.annotation.SpringValid (*), ..)) "
+ "&& args(validationTarget)")
public void beforeMethodThatNeedsValidation(Object validationTarget) {
validate(validationTarget);
}
private void validate(Object arg) {
List<Validator> validatorList = springValidatorRegistry.getValidatorsForObject(arg);
for (Validator validator : validatorList) {
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if (errors.hasErrors()) {
throw new SpringValidationException(errors);
}
}
}
}
Spring's examples show classes annotated with #Validated so I wanted to keep that. The above aspect only targets classes with #Validated at the class-level. And, just like when you use #Valid, it looks for the #SpringValid annotation stuck to a method parameter.
SpringValidationException.java
package org.springframework.validation;
import org.springframework.validation.BindingResult;
public class SpringValidationException extends RuntimeException {
private static final long serialVersionUID = 1L;
private BindingResult bindingResult;
public SpringValidationException(final BindingResult bindingResult) {
this.bindingResult = bindingResult;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
SpringValidatorRegistry.java
package org.springframework.validation;
import org.springframework.validation.Validator;
import java.util.ArrayList;
import java.util.List;
public class SpringValidatorRegistry {
private List<Validator> validatorList = new ArrayList<>();
public void addValidator(Validator validator) {
validatorList.add(validator);
}
public List<Validator> getValidatorsForObject(Object o) {
List<Validator> result = new ArrayList<>();
for (Validator validator : validatorList) {
if (validator.supports(o.getClass())) {
result.add(validator);
}
}
return result;
}
}
Just like the first answer, a place to register all classes that implement Spring's org.springframework.validation.Validator interface.
SpringValidator.java
package org.springframework.validation.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Component
public #interface SpringValidator {
}
This is just extra sauce to make it easier to register/find Validators. You could register all your Validators by hand, or you could find them via reflection. So this part is not required, I just thought it made things easier.
MyConfig.java
package com.example.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.SpringValidationAspect;
import org.springframework.validation.SpringValidatorRegistry;
import org.springframework.validation.annotation.SpringValidator;
import java.util.Map;
import javax.validation.Validator;
#Configuration
public class MyConfig {
#Autowired
private ApplicationContext applicationContext;
#Bean
public SpringValidatorRegistry validatorRegistry() {
SpringValidatorRegistry registry = new SpringValidatorRegistry();
Map<String, Object> validators =
applicationContext.getBeansWithAnnotation(SpringValidator.class);
validators.values()
.forEach(v -> registry.addValidator((org.springframework.validation.Validator) v));
return registry;
}
#Bean
public SpringValidationAspect springValidationAspect() {
return new SpringValidationAspect(validatorRegistry());
}
}
See, scan your classpath and look for #SpringValidator classes and register them. Then register the Aspect and away you go.
Here is an example of such a Validator:
MyMessageValidator.java
package com.example.validators;
import com.example.messages.MyMessage;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.SpringValidator;
#SpringValidator
public class MyMessageValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyMessage.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "firstField", "{javax.validation.constraints.NotNull}",
"firstField cannot be null");
MyMessage obj = (MyMessage) target;
if (obj.getSecondField != null && obj.getSecondField > 100) {
errors.rejectField(errors, "secondField", "{javax.validation.constraints.Max}", "secondField is too big");
}
}
}
And here is the service class that uses the #SpringValid annotation:
MyService.java
package com.example.services;
import com.example.messages.MyMessage;
import org.springframework.validation.annotation.SpringValid;
import org.springframework.validation.annotation.Validated;
import javax.inject.Inject;
#Validated
public class MyService {
public String doIt(#SpringValid final MyMessage msg) {
return "we did it!";
}
}
Hope this makes sense for someone at some point. I personally think it is quite useful. A lot of companies are starting to move their internal APIs away from REST and to something like Protobuf or Thrift. You can still use Bean Validation but you have to use XML, and it isn't all that nice. So I hope this will be helpful to people who want to still do programmatic validation.
Hope it helps someone. I've got it working by adding the following configuration:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
#Configuration
public class ValidatorConfiguration {
#Bean
public MethodValidationPostProcessor getMethodValidationPostProcessor(){
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setValidator(this.validator());
return processor;
}
#Bean
public LocalValidatorFactoryBean validator(){
return new LocalValidatorFactoryBean();
}
}
The service is then annotated the same way (#Validated on the class and #Valid on the parameter) and can be injected into another bean where the method can be called directly and validation happens.
I use WebApplicationInitializer to initialize Spring Dispatcher, this is how it was set.
public class MyApplicationInitializer implements WebApplicationInitializer{
#Override
public void onStartup(final ServletContext servletContext) throws ServletException {
final XmlWebApplicationContext appconfig = new XmlWebApplicationContext();
appconfig.setConfigLocation("/WEB-INF/app-servlet.xml");
final ServletRegistration.Dynamic dispatcher =
servletContext.addServlet("my_app", new DispatcherServlet(appconfig));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/app/*.do");
System.out.println("Here I am ");
}
and I intention is to use controller class as namespace as
#Controller
#RequestMapping("app")
public class TestingController{
#RequestMapping("/{act}.do")
public ModelAndView getIndex(){ return new ModelAndView("/index.jsp"); }
}
it seems reasonable, but when I try to run this artifact from tomcat I got this error message.
Caused by: java.lang.IllegalArgumentException: Invalid <url-pattern> /app/*.do in servlet mapping
Question is why i can't define dispatching URI like this and if I want to achieve class URI + method URI for a dispatcher how should I set it?
It looks like your endpoints are formulated for struts. Spring does not support this. Probably because it is a non-standard REST technique.
The more RESTful way of doing this would be to design the URI like this:
/app/do/{action}
Or:
/app/do?action={action}
However, you can use regex PathVariables like this:
/app/{action:[A-Za-z\\d]+}.do
Then, within the method block evaluate the request path and redirect to different controllers conditionally based on pattern matching.
Here is the example I was able to get working:
package com.app.controller;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
#Controller
#RequestMapping(value = "/test")
public class TestController
{
#RequestMapping(value = "/{action:[A-Za-z\\d]+}.do")
public #ResponseBody
ModelAndView getDo(#PathVariable String action)
{
return new ModelAndView("redirect:/test/action/" + action);
}
#RequestMapping(value = "/action/a")
public #ResponseBody
ModelAndView getAction()
{
return new ModelAndView("/index.jsp");
}
}
The getAction method could be in another controller if you wanted it to, but by using regex and redirect I think you can accomplish what you are trying to do.