I can define a Spring ControllerAdvice that is selectively used by a subset of controllers using a custom annotation:
#RestController
#UseAdviceA
#RequestMapping("/myapi")
class ApiController {
...
}
#ControllerAdvice(annotations = UseAdviceA.class)
class AdviceA {
...
}
But is it possible to pass in an attribute via the custom annotation where the advice class can pick up from the annotation? For e.g.:
#RestController
#UseAdviceA("my.value")
#RequestMapping("/myapi")
class ApiController {
...
}
#ControllerAdvice(annotations = UseAdviceA.class)
class AdviceA {
// Some way to get the string "myvalue" from the instance of UseAdviceA
...
}
Any other way to achieve the same outcome, which is to be able to define a custom configuration at the Controller method which can be passed to the ControllerAdvice would be much appreciated too.
Here is a solution.
Given
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface UseAdviceA {
public String myValue();
}
Controller
#RestController
#UseAdviceA(myValue = "ApiController")
#RequestMapping("/myapi")
class ApiController {
...
}
Your Controller Advice should be like
#ControllerAdvice(annotations = {UseAdviceA.class})
class AdviceA {
#ExceptionHandler({SomeException.class})
public ResponseEntity<String> handleSomeException(SomeException pe, HandlerMethod handlerMethod) {
String value = handlerMethod.getMethod().getDeclaringClass().getAnnotation(UseAdviceA.class).myValue();
//value will be ApiController
return new ResponseEntity<>("SomeString", HttpStatus.BAD_REQUEST);
}
Related
My question is this.
I would like to add custom annotation to spring boot and designate it as declared without declaring a specific logic.
Suppose you have the following code:
#MyCustomAnnotation
#Controller
public class ExController {
#RequestMapping(value = "/index", method = RequestMethod.GET)
public String index(Model model){
return "index";
}
}
I want the above code to perform the following logic.
#MyCustomAnnotation
#Controller
public class ExController {
#RequestMapping(value = "/index", method = RequestMethod.GET)
public String index(Model model){
//Code added because it has an annotation
model.addAttribute("key","value");
//Code added because it has an annotation
return "index";
}
}
I've been thinking about Reflection and other methods, but I can't think of the right way
Can someone grateful give a solution or keyword for this problem?
#RequestParam is a Spring annotation that is used to bind a parameter to the value of a method parameter.
If you want to add logic to your method, you can do so without any annotations.
public ResponseEntity<String> myMethod(#RequestParam(required = false) String param) {
//additional logic
return new ResponseEntity<>(HttpStatus.OK);
}
The above code will add additional logic to the method.
i hope this can be helpful i am new to this .
You can add specific logic to the method by custom annotation in Spring boot by using the #Component annotation.
You can use #Configuration annotation to add your custom logic.
<code>#Configuration
public class MyCustomAnnotationConfig {
#Bean
public MyCustomAnnotationBean myCustomAnnotationBean() {
return new MyCustomAnnotationBean();
}
}
And then you can use #Autowired to inject your bean into your controller.
```
#MyCustomAnnotation
#Controller
public class ExController {
#Autowired
private MyCustomAnnotationBean myCustomAnnotationBean;
#RequestMapping(value = "/index", method = RequestMethod.GET)
public String index(Model model){
myCustomAnnotationBean.doSomething();
return "index";
}
}
I followed this tutorial to create custom validators:
http://codetutr.com/2013/05/29/custom-spring-mvc-validation-annotations/
And according to this one, it's possible to validate request aguments with JSR-303 validation annotation:
https://raymondhlee.wordpress.com/2015/08/29/validating-spring-mvc-request-mapping-method-parameters/
My custom ConstraintValidator is never invoked. Here is my code:
Controller:
#RestController
#RequestMapping(value = "/json")
#Validated
public class JsonResource {
#RequestMapping(method = POST, consumes=APPLICATION_JSON_VALUE"))
public void postJson(#SafeHtml #RequestBody JsonNode jsonQuery){
// post a foo
}
}
SafeHtml annotation:
#Documented
#Constraint(validatedBy = {SafeHtmlJsonValidator.class})
#Target( {ElementType.PARAMETER, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface SafeHtml {
String message() default "{SafeHtml}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Custom ConstraintValidator:
#Component
public class SafeHtmlJsonValidator implements ConstraintValidator<SafeHtml, JsonNode> {
#Override
public void initialize(SafeHtml constraintAnnotation) {}
#Override
public boolean isValid(JsonNode value, ConstraintValidatorContext context) {
// validate my JSON
return true;
}
}
The problem is that SafeHtmlJsonValidator.isValid() is never invoked.
Tested with Spring 4.2.6.RELEASE
If you don't want to convert everything to a DTO you can add MethodValidationPostProcessor
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
that will allow you to add validation annotation to method field directly
like
#RestController
#RequestMapping(value = "/json")
#Validated
public class JsonResource {
#RequestMapping(method = POST, consumes=APPLICATION_JSON_VALUE"))
public void postJson(#SafeHtml #RequestBody JsonNode jsonQuery){
// post a foo
}
}
I am not sure if annotating with custom validation (#SafeHtml) onto a #RequestBody parameter will work in a controller method. It will surely work for #RequestParam and #PathVariable in the controller method, when its a get request.
For post requests, I believe there should be a POJO bean representing the request body, and the custom validation annotations should be placed on the attributes of this request object.
In your example you should ideally have a RequestObj.class with an attribute JsonNode, and this should be annotated with #SafeHtml.
Since you have already annotated #Validated at the class level of the controller, the controller method will then be
#RequestMapping(method = POST, consumes=APPLICATION_JSON_VALUE"))
public void postJson(#RequestBody RequestObj requestBody){
// post a foo
}
and RequestObj.class will have
#SafeHtml
private JsonNode jsonQuery;
//getters,setters...
This should trigger the custom validator. Worth a try. Note that your request json structure will change accordingly.
I need to stop processing of Spring MVC annotations on interface, but bean for this interface should be created.
e.g. I have shared Api interface with MVC REST annotations, Controller implements this Api. In other project I create REST client based on interface (by processing annotations). But when I create client, Spring sees interface as return type and process annotations inside it. So, I need to stop annotations processing when I create REST client, but for controller annotations should work (now they work OK).
#RequestMapping("/resource1")
public interface Api {
#RequestMapping(method = RequestMethod.POST)
Resource1 getResource1();
}
#RestController
public class Controller implements Api {
#Override
public Resource1 getResource1() {
return null;
}
}
#Configuration
public class Config {
#Bean
public Api api() {
return RestClientFactory.createRestClientBasedOnAnnotations(Api.class);
}
}
I solved it by creating new annotation which is used to mark API interface and overriding boolean isHandler(Class<?> beanType) method of org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping. This method originally checks whether class (or any interface that class implements) is annotated with Controller or RequestMapping annotations. I added extra check that looks up for my BackEndApiInterface annotation and if it is found then return false. Here is the code:
#Retention(RetentionPolicy.RUNTIME)
public #interface BackEndApiInterface {
}
#BackEndApiInterface
#RequestMapping("/resource1")
public interface Api {
#RequestMapping(method = RequestMethod.POST)
Resource1 getResource1();
}
#RestController
public class Controller implements Api {
#Override
public Resource1 getResource1() {
return null;
}
}
#Configuration
public class Config {
#Bean
public Api api() {
return RestClientFactory.createRestClientBasedOnAnnotations(Api.class);
}
#Bean
public static RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
#Override
protected boolean isHandler(Class<?> beanType) {
if (AnnotationUtils.findAnnotation(beanType, BackEndApiInterface.class) != null) {
return false;
}
return super.isHandler(beanType);
}
};
}
}
you could move the annotations to the implementation and just keep the interface as pure java.
I am trying to enable/disable controller depending on value in properties file.
My Controller looks like this:
#RestController
#ConditionalOnExpression("${properties.enabled}")
public class Controller{
public String getSomething() {
return "Something";
}
}
My properties file looks like this:
properties.enabled= false
And controller is always enabled (I can access method getSomething). I also tried combinations like this:
#ConditionalOnExpression("${properties.enabled:true}")
#ConditionalOnExpression("${properties.enabled}==true")
#ConditionalOnExpression("${properties.enabled}=='true'")
#ConditionalOnExpression("'${properties.enabled}'=='true'")
Edit:
Also tried different annotation:
#ConditionalOnProperty(prefix = "properties", name="enabled")
I finally found the problem. This Bean wasn't created by Spring but in WebConfiguration class so i had to also add annotation there
public class CommonWebConfiguration extends WebMvcConfigurationSupport {
#Bean
#ConditionalOnProperty(prefix="properties",name = "enabled")
public Controller controller() {
return new Controller ();
}
}
My Controller now looks like this:
#RestController
#ConditionalOnProperty(prefix="properties",name = "enabled")
public class Controller{
public String getSomething() {
return "Something";
}
}
I want to have one controller class, but 4 instances of it, each of instance will have own datasource and controller path, everything else (methods, validations rules, views names) will be the same;
So i need something like this :
class MyController{
private MyService service;
#RequestMapping("somework")
public String handleRequest(){
........
}
....................
}
Configuration class :
#Configuration
#EnableWebMvc
public class AppConfiguration {
#Controller // assuming it exists to get the
#RequestMapping('con1') // desired result
MyController controller1(){
MyController con = new MyController();
con.setService(service1Bean);
return con;
}
#Controller // assuming it exists to get the
#RequestMapping('con2') // desired result
MyController controller2(){
MyController con = new MyController();
con.setService(service2Bean);
return con;
}
...............................
}
No, you can't do this.
First, annotations are a set in stone at compile time. They are constant meta data that you cannot modify. So even though, they are accessible at run time through reflection, you cannot modify them.
Second, the #Controller annotation call only be used to annotate types. You cannot use it on a method. There is no corresponding annotation in Spring MVC that does what you want in your example. (You could always write your own.)
Finally, the Spring MVC stack registers your #Controller beans' methods as handlers mapping them to the various URL patterns you provide. If it tries to register a pattern that has already been registered, it fails because duplicate mappings are not allowed.
Consider refactoring. Create a #Controller class for each path you want but move the logic to a #Service bean which you can customize to use whatever data source you need.
You may achieve what you want by implementing an abstract superclass of
your controller, with constructor parameters for your service.
Then you should write derive your controllers from the abstract superclass,
with a constructor, where you inject your concrete service implementation:
public abstract class MyBaseController {
private MyService service;
public MyBaseController(final MyService service) {
this.service = service;
}
...
#RequestMapping("method1")
public ... method1( ... ) {
...
}
}
#Controller
#RequestMapping("con1")
public MyController1 extends MyBaseController {
#Autowired
public MyController1(#Qualifier("con1") final MyService service) {
super(service);
}
}
#Controller
#RequestMapping("con2")
public MyController2 extends MyBaseController {
#Autowired
public MyController1(#Qualifier("con2") final MyService service) {
super(service);
}
}
#Configuration
public class MyConfiguration {
#Bean(name = "con1")
public MyService serviceCon1() {
return ...;
}
#Bean(name = "con2")
public MyService serviceCon2() {
return ...;
}
}