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.
Related
In a Spring RabbitMQ project I am looking for a way to programmatically validate an object that has JSR303 annotations (like #NotNull, #Size, etc) while at the same time requires some custom validation logic. I would normally use a ConstraintValidator in combination with a custom Annotation, but the use of custom Annotations is not an option in this case.
I have the following (simplified) class, which is generated by Swagger and therefore cannot be edited:
#ApiModel(description="User")
public class User {
private String name;
#NotNull
#Size(min = 1, max = 6)
public String getName() {
return this.name;
}
...
}
The additional validation logic is encapsulated in a validator:
#Component
public class UserValidator implements org.springframework.validation.Validator {
#Override
public boolean supports(Class<?> aClass) {
return User.class.equals(aClass);
}
#Override
public void validate(Object o, Errors errors) {
User user = (User) o;
...
if(!valid) {
errors.reject("some rejection");
}
}
}
The service in which the validation occurs:
#Service
#RequiredArgsConstructor
public class SomeService {
private final javax.validation.Validator validator; // might as well be org.springframework.validation.Validator if that works better
public void someMethod(User user) {
if (!validator.validate(user).isEmpty()) {
// handle invalid user
}
...
}
}
However, the UserValidator is not being invoked. Is there some way to make Spring aware of the UserValidator? I have read some topics on using an InitBinder, however as this is not a web MVC project but a rabbitMQ project I'm not sure whether this can be used.
It is not clear from your description how this is relevant to Spring AMQP, but if you want to use a validator on the listener method level, you should configure it respectively:
#Configuration
#EnableRabbit
public class Config implements RabbitListenerConfigurer {
...
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setValidator(new MyValidator());
}
}
See docs for more info: https://docs.spring.io/spring-amqp/docs/current/reference/html/#rabbit-validation
I have a flag DISABLE_FLAG and I want to use it to control multiple specific APIs in different controllers.
#RestController
public final class Controller1 {
#RequestMapping(value = "/foo1", method = RequestMethod.POST)
public String foo1()
}
#RestController
public final class Controller2 {
#RequestMapping(value = "/foo2", method = RequestMethod.POST)
public String foo2()
}
I can use an interceptor to handle all the urls. Is there a easy way to do that like annotation?
You could use AOP to do something like that.
Create your own annotation...
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Maybe { }
and corresponding aspect...
#Aspect
public class MaybeAspect {
#Pointcut("#annotation(com.example.Maybe)")
public void callMeMaybe() {}
#Around("callMeMaybe()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// do your logic here..
if(DISABLE_FOO) {
// do nothing ? throw exception?
// return null;
throw new IllegalStateException();
} else {
// process the request normally
return joinPoint.proceed();
}
}
}
I don't think there is direct way to disable a constructed request mapping but We can disable API in many ways with some condition.
Here is the 2 ways disabling by spring profile or JVM properties.
public class SampleController {
#Autowired
Environment env;
#RequestMapping(value = "/foo", method = RequestMethod.POST)
public String foo(HttpServletResponse response) {
// Using profile
if (env.acceptsProfiles("staging")) {
response.setStatus(404);
return "";
}
// Using JVM options
if("true".equals(System.getProperty("DISABLE_FOO"))) {
response.setStatus(404);
return "";
}
return "";
}
}
If you are thinking futuristic solution using cloud config is the best approach. https://spring.io/guides/gs/centralized-configuration/
Using Conditional components
This allows to build bean with conditions, if the condition failed on startup, the entire component will never be built. Group all your optional request mapping to new controller and add conditional annotation
#Conditional(ConditionalController.class)
public class SampleController {
#Autowired
Environment env;
#RequestMapping(value = "/foo", method = RequestMethod.POST)
public String foo(HttpServletResponse response) {
return "";
}
public static class ConditionalController implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().acceptsProfiles("staging"); // Or whatever condition
}
}
}
You can solve this with annotations by utilizing spring profiles. You define two profiles one for enabled flag and another profile for the disabled flag. Your example would look like this:
#Profile("DISABLED_FLAG")
#RestController
public final class Controller1 {
#RequestMapping(value = "/foo1", method = RequestMethod.POST)
public String foo1()
}
#Profile("ENABLED_FLAG")
#RestController
public final class Controller2 {
#RequestMapping(value = "/foo2", method = RequestMethod.POST)
public String foo2()
}
Here is the link to the spring framework documentation for this feature: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Profile.html
I did it as follows :
#Retention(RUNTIME)
#Target(ElementType.METHOD)
public #interface DisableApiControl {
}
This class is my customization statement. After could use AOP :
for AbstractBaseServiceImpl :
public abstract class AbstractBaseServiceImpl {
private static boolean disableCheck = false;
public void setDisableChecker(boolean checkParameter) {
disableCheck = checkParameter;
}
public boolean getDisableChecker() {
return disableCheck;
}
}
NOTE : The above class has been prepared to provide a dynamic structure.
#Aspect
#Component
public class DisableApiControlAspect extends AbstractBaseServiceImpl {
#Autowired
private HttpServletResponse httpServletResponse;
#Pointcut(" #annotation(disableMe)")
protected void disabledMethods(DisableApiControl disableMe) {
// comment line
}
#Around("disabledMethods(disableMe)")
public Object dontRun(ProceedingJoinPoint joinPoint, DisableApiControl disableMe) throws Throwable {
if (getDisableChecker()) {
httpServletResponse.sendError(HttpStatus.NOT_FOUND.value(), "Not found");
return null;
} else {
return joinPoint.proceed();
}
}
}
checker parameter added global at this point. The rest will be easier when the value is given as true / false when needed.
#GetMapping("/map")
#DisableApiControl
public List<?> stateMachineFindMap() {
return new ArrayList<>;
}
I see that with Spring boot is really simple create filters. Just follow post like this one https://www.baeldung.com/spring-boot-add-filter
What I have not able to find, is how to create annotations that subscribe specifics endpoints in the controller to one filter.
Something like in Jax-RS it would looks like
#GET
#Path("jax-rs-single")
#Reactive(ttlRequest = 2000)
#Produces(MediaType.APPLICATION_JSON)
public Single getSingle() {
return Single.just("Hello world single");
}
Where #Reactive it would trigger the ReactiveFilter implementation per request.
I also saw the #WebFlow annotation, but it's not what I want. I want to create a library where the consumers decide which filter use, just adding the annotation in the controller.
Any idea how to do something similar with Spring boot/MVC ?
Regards
I will try to describe here more about Custom annotation and the processor in Spring.
I don't know what you want or what you need, but I will give an generic example.
You have 2 options:
BeanProcessor
HandlerInterceptor
BeanProcessor
You need to build 3 things basically: Annotaton, BeanProcessor and a Callback to execute your logic if annotated. Here is an example of it and how it works:
1 - Create the annotation
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
#Documented
public #interface Reactive {
Integer ttlRequest;
}
2 - Implement a BeanPostProcessor
#Component
public class ReactiveAnnotationProcessor implements BeanPostProcessor {
private ConfigurableListableBeanFactory configurableBeanFactory;
#Autowired
public ReactiveAnnotationProcessor(ConfigurableListableBeanFactory beanFactory) {
this.configurableBeanFactory = beanFactory;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
this.scanReactiveAnnotation(bean, beanName);
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
protected void scanReactiveAnnotation(Object bean, String beanName) {
this.configureMethodInjection(bean);
}
private void configureMethodInjection(Object bean) {
Class<?> managedBeanClass = bean.getClass();
MethodCallback methodCallback =
new ReactiveMethodCallback(configurableBeanFactory, bean);
ReflectionUtils.doWithMethod(managedBeanClass, methodCallback);
}
}
3 - Create the method callback (here is the logic to execute)
public ReactiveMethodCallback implements MethodCallback {
private ConfigurableListableBeanFactory configurableBeanFactory;
private Object bean;
public ReactiveMethodCallback(ConfigurableListableBeanFactory bf, Object bean) {
configurableBeanFactory = bf;
this.bean = bean;
}
#Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
if (!method.isAnnotationPresent(Reactive.class)){
return;
}
//YOUR LOGIC HERE
}
}
Here is a good source about annotation processing, it is about FieldProcessing but you can just change the interfaces to implement what you need if you have doubts: https://www.baeldung.com/spring-annotation-bean-pre-processor
[UPDATED] You can also create a HandlerInterceptor instead:
HandlerInterceptor
public class ReactiveFilterHandlerInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// Test if the controller-method is annotated with #CustomFilter
Reactive filter = handlerMethod.getMethod().getAnnotation(Reactive.class);
if (filter != null) {
// ... do the filtering, or call the Component for filtering
}
}
return true;
}
}
And register your handler:
#Configuration
public class WebMvcConfig extends WebMvcConfigurer {
#Autowired
ReactiveFilterHandlerInterceptor reactiveFilterHandlerInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(reactiveFilterHandlerInterceptor);
}
}
If I understand what you want correctly the main problem is how to apply filter based on custom annotation.
So first of all, yes you can use a regular Spring filter (WebFilter in case of Spring Webflux or Filter in case of Spring MVC), but you'll need to write some custom logic.
To do filtering based on annotation you should:
Use RequestMappingHandlerMapping#getHandlerInternal() method to retrieve a reference to the method that handles the request (the getSingle() in your case)
When you manage to retrieve the HandlerMethod then you can check if that method has your custom annotation applied with hasMethodAnnotation(Class<A> annotationType) method.
When you know that, then you can react accordingly: either chain.doFilter(request, response) without performing any actions, or apply your custom logic, and then trigger the rest of the filter chain.
This is the main controller for the web entrypoint
#Controller
#RequestMapping("/webapp")
public class WebAppController {
#RequestMapping(value = "/home/{authKey}",method = RequestMethod.GET)
String index(#ModelAttribute MyMeta myMeta, Model model){
System.out.println("Token: "+myMeta.getAccessToken());
return "index";
}
#RequestMapping(value = "/config/{authKey}",method = RequestMethod.GET)
String config(#ModelAttribute MyMeta myMeta, Model model){
return "configure";
}
}
Now if you look at the interceptor you can see how I am creating the #ModelAttribute, and see the implementation
#Component
#ControllerAdvice
public class SessionInterceptor implements AsyncHandlerInterceptor {
MyMeta myMeta;
...
#ModelAttribute
public MyMeta getTest() {
return this.myMeta;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
...
// parse the key from the request
...
MetaMagicKey metaMagicKey = metaMagicKeyRepo.findKeyByMagicKey(key);
// do work here query my DB and build stuff
...
// assign the queried data built into object
this.myMeta = metaMagicKey.getId().getMyMeta();
return true;
}
My question is, I do not know the true inter-workings of Springboot so I am worried if too many people execute this I might have some object swapping, or some kind of collision? There really isn't a clean way to do this and all of the research I've done is torn between using HttpServletRequest#setAttribute() and using #ModelAttribute, I like the route I chose above as it's VERY easy to implement in my methods.
Springboot 1.4.2 - Java 8
EDIT:
What I ended up trying is this, based on several pages I've read.
I created a new component:
#Component
#RequestScope
public class HWRequest implements Serializable {
private MyMeta myMeta;
public MyMeta getMyMeta() {
return myMeta;
}
public void setMyMeta(MyMeta myMeta) {
this.myMeta = myMeta;
}
}
And then My Config class
#Configuration
public class AppConfig extends WebMvcConfigurerAdapter {
UserSessionInterceptor userSessionInterceptor;
#Autowired
public AppConfig(UserSessionInterceptor userSessionInterceptor) {
this.userSessionInterceptor = userSessionInterceptor;
}
#Bean
#RequestScope
public HWRequest hwRequest() {
return new HWRequest();
}
#Bean
public UserSessionInterceptor createUserSessionInterceptor() {
return userSessionInterceptor;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(createUserSessionInterceptor()).addPathPatterns("/user/**");
}
}
And here is the interceptor I modified
#Component
#ControllerAdvice
public class SessionInterceptor implements AsyncHandlerInterceptor {
#Resource
HWRequest hwRequest;
...
#ModelAttribute
public HWRequest getTest() {
return this.hwRequest;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
...
// parse the key from the request
...
MetaMagicKey metaMagicKey = metaMagicKeyRepo.findKeyByMagicKey(key);
// do work here query my DB and build stuff
...
// assign the queried data built into object
this.hwRequest.setMyMeta(metaMagicKey.getId().getMyMeta());
return true;
}
And of course the modified controller to fit my needs
#Controller
#RequestMapping("/user")
public class WebAppUserController {
#RequestMapping(value = "/home/{authKey}",method = RequestMethod.GET)
String index(#ModelAttribute HWRequest request, Model model){
return "index";
}
#RequestMapping(value = "/config/{authKey}",method = RequestMethod.GET)
String config(#ModelAttribute HWRequest request, Model model){
return "configure";
}
}
Based on all of the documentation I've read this should work, but maybe I am missing something as the interceptor is STILL a singleton. Maybe I am missing something?
myMeta variable represents state in singleton bean. Of course it is not thread-safe and various users will get collisions. Do not ever store any of your application state in singleton beans.
If you want to store some state per request, use Spring's request scope. That means creating separate bean just for storing state annotated with #RequestScope annotation
Reaction on EDIT:
This bean registration can be deleted as it is already registered into Spring IoC container with #Component annotation:
#Bean
#RequestScope
public HWRequest hwRequest() {
return new HWRequest();
}
Another piece that is not needed in your AppConfig is autowiring UserSessionInterceptor bean and registering it as bean again. Delete that. As that bean is being autowired it obviously already is in IoC container, so no need to register it again.
Another confusing piece is workd session in naming. As you are dealing with #RequestScope instead of #SessionScope I would advise to change naming of your class to request (e.g. RequestInterceptor). Session vs Request are very different beasts.
Otherwise it looks like it can work and should be thread safe.
Assume I have a following code in Java EE / EJB / JAX-RS:
#POST
#Path("some/path")
#MyAnnotation
public MyResponse createActivation(MyRequest request, CustomValue value) {
// ...
}
How do I check for the presence of custom #MyAnnotation annotation and populate CustomValue value method parameter based on some request context parameters in case the annotation is present?
Note: I already have this code in Spring using HandlerInterceptorAdapter and HandlerMethodArgumentResolver. Now I need to do the same without Spring. I have already discovered the ContainerRequestFilter and I use it to check for the annotation, but now I am struggling with injecting the method parameter.
Custom method parameter injection is handled a little differently from normal (i.e. field, constructor) injection. With Jersey, this requires the implementation of a ValueFactoryProvider. For your case it would look something like
public class MyAnnotationParamValueProvider implements ValueFactoryProvider {
#Inject
private ServiceLocator locator;
#Override
public Factory<?> getValueFactory(Parameter parameter) {
if (parameter.getAnnotation(MyAnnotation.class) != null
&& parameter.getRawType() == CustomValue.class) {
final Factory<CustomValue> factory
= new AbstractContainerRequestValueFactory<CustomValue>() {
#Override
public CustomValue provide() {
final ContainerRequest request = getContainerRequest();
final String value = request.getHeaderString("X-Value");
return new CustomValue(value);
}
};
locator.inject(factory);
return factory;
}
return null;
}
#Override
public PriorityType getPriority() {
return Priority.NORMAL;
}
}
Then you need to register it with the ResourceConfig
public class AppConfig extends ResourceConfig {
public AppConfig() {
register(new AbstractBinder() {
#Override
protected void configure() {
bind(MyAnnotationParamValueProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
}
});
}
}
See a complete example in this Gist
See also:
Custom Method Parameter Injection with Jersey. It shows another way to do this, where you don't need to explicitly inject, and also you will be able to inject the value in all three areas (field, constructor, and method param).