I have a bean being created by a service with the following class:
#Configuration
public class AccessManager {
#Bean(name="access", destroyMethod="destroy")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#Autowired
public Access create(HttpServletRequest request) {
System.out.println(request.getRemoteAddr());
return new Access();
}
}
Everything works as expected, except that when the application is starting, this method is being called, probably because I have some other singleton beans that use the Access bean. At the start up there is no request bound to the Thread, and it's expected to get a java.lang.IllegalStateException when trying to access any property of the request parameter.
No problem. The question is, is it possible to check if the underlying HttpServletRequest of the proxy request is null before calling a property that raises the exception?
You probably want to take a look at RequestContextHolder#getRequestAttributes(). That will return null if you're not currently in a context where request scope could be used.
#Configuration
public class AccessManager {
#Bean(name="access", destroyMethod="destroy")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#Autowired
public Access create(HttpServletRequest request) {
if (RequestContextHolder.getRequestAttributes() != null) {
System.out.println(request.getRemoteAddr());
}
return new Access();
}
}
I think the issue here is with separation of concerns. Usually your service layer should not have any dependency on the servlet classes. This is very much a controller/UI concern.
Your service class should be provided with the properties which it needs to do its job. In this case a String. This service method should be called from a controller method which is injected with the servlet request.
Something like the following:
#Controller
public class MyController {
#Autowired
private AccessManager accessManager;
#RequestMapping
public void handleRequest(HttpServletRequest request) {
accessManager.create(request.getRemoteAddr());
}
}
and your service would then look like this:
#Service
public class AccessManager {
public Access create(String remoteAddress) {
return new Access();
}
}
To sum up, anything annotated as #Service shouldn't have access to the Request.
Related
I have tried to find documentation on how to manually configure a RestController (i.e in a Configuation class). That means without using the RestController annotation. Considering all the other annotations, like mapping, pathvariables etc. is at all possible?
A controller is essentially a component with a request mapping. See RequestMappingHandlerMapping.
#Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
If you want to instantiate a "rest controller" through configuration, you can probably do so via the following:
#Configuration
public class MyConfiguration {
#Bean
public MyController() {
return new MyController();
}
}
#ResponseBody
public class MyController {
#RequestMapping("/test")
public String someEndpoint() {
return "some payload";
}
}
But I don't think you'll be able to configure the request mappings (path variables, etc) in the configuration though; at least I haven't seen an example nor figured out how.
I have a typed abstract RestController that contains some common logic for processing of all objects of the type. The service for processing is provided through the constructor.
During the bean instantiation of the subclass, both constructors are called with non-null parameters and the superclass non-null assertion successfully passed.
Calling the API endpoint (URI path is a composition of the subclass and superclass paths) calls the correct method, with correctly identified parameters.
However, the endpoint method throws a null pointer exception because the provided service (the one that passed the non-null assertion) was null. Upon inspection all properties of both subclass and superclass of the bean whose method was called report all properties to be null.
Here is a simplified example:
Model:
public class Cookie {
public long id;
}
public class ChocolateCookie extends Cookie {
public long chipCount;
}
Service:
public interface CookieService<T extends Cookie> {
T findCookie(long cookieId);
void eatCookie(T cookie);
}
#Service
public class ChocolateCookieService implements CookieService<ChocolateCookie> {
#Override
public ChocolateCookie findCookie(long cookieId) {
// TODO Load a stored cookie and return it.
return new ChocolateCookie();
}
#Override
public void eatCookie(ChocolateCookie cookie) {
// TODO Eat cookie;
}
}
Rest Controllers:
public abstract class CookieApi<T extends Cookie> {
private final CookieService<T> cookieService;
public CookieApi(CookieService<T> cookieService) {
this.cookieService = cookieService;
Assert.notNull(this.cookieService, "Cookie service must be set.");
}
#PostMapping("/{cookieId}")
public ResponseEntity eatCookie(#PathVariable long cookieId) {
final T cookie = cookieService.findCookie(cookieId); // Cookie service is null
cookieService.eatCookie(cookie);
return ResponseEntity.ok();
}
}
#RestController
#RequestMapping("/chocolateCookies")
public class ChocolateCookieApi extends CookieApi<ChocolateCookie> {
#Autowired
public ChocolateCookieApi(ChocolateCookieService cookieService) {
super(cookieService);
}
#PostMapping
public ResponseEntity<ChocolateCookie> create(#RequestBody ChocolateCookie dto) {
// TODO Process DTO and store the cookie
return ResponseEntity.ok(dto);
}
}
As a note, if instead of providing a service object to the superclass I defined an abstract method for getting the service on demand and implemented it in the subclass, the superclass would function as intended.
The same principle works in any case where #RestController and #RequestMapping are not included in the equation.
My question is two-fold:
Why is the happening?
Is there a way to use the constructor, or at least to not have to implement getter methods for each subclass and each service required by the superclass?
EDIT 1:
I tried recreating the issue, but the provided code was working fine as people suggested. After tampering with the simplified project, I finally managed to reproduce the issue. The actual condition for reproducing the issue is that the endpoint method in the superclass must be inaccessible by the subclass (example: Classes are in different packages and the method has package visibility).
This causes spring to create an enhancerBySpringCGLIB proxy class with zero populated fields.
Modifying the superclass methods to have protected/public visibility resolved the issue.
Nikola,
I'm not sure why your code is not working in your system, I created same classes in a project and it is working fine, I even added another Cookie type, service and api classes.
SpringBoot log (you can see 4 end points initialized):
2019-02-26 14:39:07.612 INFO 86060 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/chocolateCookies],methods=[POST]}" onto public org.springframework.http.ResponseEntity<cookie.ChocolateCookie> cookie.ChocolateCookieApi.create(cookie.ChocolateCookie)
2019-02-26 14:39:07.613 INFO 86060 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/chocolateCookies/{cookieId}],methods=[POST]}" onto public org.springframework.http.ResponseEntity<?> cookie.CookieApi.eatCookie(long)
2019-02-26 14:39:07.615 INFO 86060 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/oatmeal-raisin-cookie],methods=[POST]}" onto public org.springframework.http.ResponseEntity<cookie.OatmealRaisinCookie> cookie.OatmealRaisingCookieApi.create(cookie.OatmealRaisinCookie)
2019-02-26 14:39:07.615 INFO 86060 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/oatmeal-raisin-cookie/{cookieId}],methods=[POST]}" onto public org.springframework.http.ResponseEntity<?> cookie.CookieApi.eatCookie(long)
Testing controllers in postman
As #Domingo mentioned, you may have some configuration problems in your application because from OOP and Spring IoC perspectives your code looks fine and runs with no problems.
NOTE: I'm running these controllers using SpringBoot 2.0.5, Java 8, Eclipse
I posted my project in GitHub for your reference.
https://github.com/karl-codes/cookie-monster
Cheers!
you can define an abstract method in your abstract class and autowire the correct service on each implementation :
public abstract class CookieApi<T extends Cookie> {
protected abstract CookieService<T> getCookieService();
#RequestMapping("/cookieId")
public void eatCookie(#PathVariable long cookieId) {
final T cookie = cookieService.findCookie(cookieId); // Cookie service is null
this.getCookieService().eatCookie(cookie);
}
}
#RestController
#RequestMapping("/chocolateCookies")
public class ChocolateCookieApi extends CookieApi<ChocolateCookie> {
#Autowired
private ChocolateCookie chocolateCookie;
#Override
protected CookieService<T> getCookieService() {
return this.chocolateCookie;
}
#PostMapping
public ResponseEntity<ChocolateCookie> create(#RequestBody ChocolateCookie dto) {
// TODO Process DTO and store the cookie
return ResponseEntity.ok(dto);
}
}
your sample in general looks alright and dependency injection should work with spring.
If you want to access service which reference is in parent abstract class the reference should be not private but protected.
--
Spring initializes all RestControllers as Singleton Beans into application context injecting existing Services Beans, if there is no bean to inject application startup will fail. If calling rest endpoint access the controller that has no service reference in it, it is not the same one that was initialized with service in it(which I don't know how could happen) or something is wrong with your config.
Put that on git hub.
I want to measure time of sql execution which will be run by MyBatis (Spring Boot project) and bind that with other request parameters, so I can get full info about performance issues regarding specific requests. For that case I have used MyBatis Interceptor on following way:
#Intercepts({
#Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
#Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class QueryMetricsMybatisPlugin implements Interceptor {
#Override
public Object intercept(Invocation invocation) throws Throwable {
Stopwatch stopwatch = Stopwatch.createStarted();
Object result = invocation.proceed();
stopwatch.stop();
logExectionTime(stopwatch, (MappedStatement) invocation.getArgs()[0]);
return result;
}
}
Now when it come to binding with request, I want to store those metrics in request as attribute. I have tried this simple solution to get request, but that was not working since request was always null (I have read that this solution won't work in async methods, but with MyBatis Interceptor and its methods I think that's not the case):
#Autowired
private HttpServletRequest request;
So, the question is how properly get request within MyBatis interceptor?
One important note before I answer your question: it is a bad practice to access UI layer in the DAO layer. This creates dependency in the wrong direction. Outer layers of your application can access inner layers but in this case this is other way round. Instead of this you need to create a class that does not belong to any layer and will (or at least may) be used by all layers of the application. It can be named like MetricsHolder. Interceptor can store values to it, and in some other place where you planned to get metrics you can read from it (and use directly or store them into request if it is in UI layer and request is available there).
But now back to you question. Even if you create something like MetricsHolder you still will face the problem that you can't inject it into mybatis interceptor.
You can't just add a field with Autowired annotation to interceptor and expect it to be set. The reason for this is that interceptor is instantiated by mybatis and not by spring. So spring does not have chance to inject dependencies into interceptor.
One way to handle this is to delegate handling of the interception to a spring bean that will be part of the spring context and may access other beans there. The problem here is how to make that bean available in interceptor.
This can be done by storing a reference to such bean in the thread local variable. Here's example how to do that. First create a registry that will store the spring bean.
public class QueryInterceptorRegistry {
private static ThreadLocal<QueryInterceptor> queryInterceptor = new ThreadLocal<>();
public static QueryInterceptor getQueryInterceptor() {
return queryInterceptor.get();
}
public static void setQueryInterceptor(QueryInterceptor queryInterceptor) {
QueryInterceptorRegistry.queryInterceptor.set(queryInterceptor);
}
public static void clear() {
queryInterceptor.remove();
}
}
Query interceptor here is something like:
public interface QueryInterceptor {
Object interceptQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
}
Then you can create an interceptor that will delegate processing to spring bean:
#Intercepts({
#Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }),
#Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}) })
public class QueryInterceptorPlugin implements Interceptor {
#Override
public Object intercept(Invocation invocation) throws Throwable {
QueryInterceptor interceptor = QueryInterceptorRegistry.getQueryInterceptor();
if (interceptor == null) {
return invocation.proceed();
} else {
return interceptor.interceptQuery(invocation);
}
}
#Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
#Override
public void setProperties(Properties properties) {
}
}
You need to create an implementation of the QueryInterceptor that does what you need and make it a spring bean (that's where you can access other spring bean including request which is a no-no as I wrote above):
#Component
public class MyInterceptorDelegate implements QueryInterceptor {
#Autowired
private SomeSpringManagedBean someBean;
#Override
public Object interceptQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
// do whatever you did in the mybatis interceptor here
// but with access to spring beans
}
}
Now the only problem is to set and cleanup the delegate in the registry.
I did this via aspect that was applied to my service layer methods (but you can do it manually or in spring mvc interceptor). My aspect looks like this:
#Aspect
public class SqlSessionCacheCleanerAspect {
#Autowired MyInterceptorDelegate myInterceptorDelegate;
#Around("some pointcut that describes service methods")
public Object applyInterceptorDelegate(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
QueryInterceptorRegistry.setQueryInterceptor(myInterceptorDelegate);
try {
return proceedingJoinPoint.proceed();
} finally {
QueryInterceptorRegistry.clear();
}
}
}
I would like to use Spring Event to "speak" with my beans in my web application.
So, for example, my bean which fires event is like this:
#Controller
#Scope("request")
#KeepAlive
public class Controller extends InitializingBean, ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
public void test() {
applicationEventPublisher.publishEvent(new TestEvent(this));
}
}
And my listener event is like this:
#Component
#Scope("request")
#KeepAlive
public class Module implements ApplicationListener<TestEvent> {
#Override
public void onApplicationEvent(TestEvent event) {
}
}
The most important point is these beans are scope request because they need to be initialized at every time the page is called.
But in startup, I get this message:
Caused by: java.lang.IllegalStateException: No thread-bound request
found: Are you referring to request attributes outside of an actual
web request, or processing a request outside of the originally
receiving thread? If you are actually operating within a web request
and still receive this message, your code is probably running outside
of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current
request.
Like if Spring try to instantiate my Module bean in startup and as the bean is scope request, it can't do this (the context request is not instantiate)
If I delete event management, everything works fine.
So, my question is:
Is it possible to have event listener is scope request ? And how to do this ?
Thanks
Try to inject a scoped proxy in a Singleton ApplicationListener to handle the TestEvent.
#Scope(proxyMode=ScopedProxyMode.TARGET_CLASS, value="request")
public class TestEventHandler {
public void onTestEvent(TestEvent event)
// ...
}
}
public class TestEventApplicationListener implements ApplicationListener<TestEvent> {
#Autowired
private TestEventHandler handler;
#Override
public void onApplicationEvent(TestEvent event) {
handler.onTestEvent(event);
}
}
My ActionResponse code is :
#Component
#Scope(value = "request",proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ActionResponse{
public int a;
//body
}
My Controller:
#Controller
#RequestMapping(value="/ajax/discussion")
public class DiscussionController extends AbstractController {
#Autowired
private ActionResponse actionResponse;
public void setActionResponse(ActionResponse actionResponse) {
this.actionResponse = actionResponse;
}
#RequestMapping("/test")
public #ResponseBody String test(){
String response=this.actionResponse.a+"";
if(this.actionResponse.a==0)
this.actionResponse.a=10;
return response;
}
}
I start the project and then the first time I request /ajax/discussion/test it shows 0
but after that for other requests it shows 10
It has to show 0 in every request because of request scope for ActionResponse
The question is:
Why the bean(ActionResponse) is created once not in every request?!!!
CGLIB works on class level.
CGLIB proxy is still a singleton, so it inherits the fields from the base class. When you change its public properties you change the values of the singleton.
You should encapsulate your data changes in public getters and setters.
Was a little late - Just adding on to Boris Treukhov's answer(have +1'd it):
The reason is that since you have annotated ActionResponse with #Scope(proxyMode=..) Spring ends up creating a CGLIB subclass of this ActionResponse which internally handles the scope appropriately.
Now when you inject ActionResponse into the DiscussionController it is the CGLIB proxy that gets injected, and since you are setting the fields directly with going through the setter, it just modifies the fields of the proxy and not the underlying scoped proxied object. The fix is simply to make state changes via the getters and setters not through fields.