Difference between SessionBean and SessionAttribute - spring

What is the difference between SessionBean and SessionAttribute, what is the best way to add an object to a session? For example:
SessionBean:
#Component
#Scope(value = "session")
class A {
...
}
SessionAttribute:
public void doGet(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
A a = new A();
session.setAttribute("A", a);
}

They're very similar and both of those objects can be retrieved through HttpSession object.
The only difference between them is that SessionBean will be injected by Spring and session attribute will be added to session by programmer using HttpSession#setAttribute(String, Object)) method.
I would use SessionBean if you know that this bean would be required in session and you also know all required state or the behaviour of the bean and SessionAttribute when you receive the information in runtime.

Related

How to get request in MyBatis Interceptor

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();
}
}
}

Not understanding Spring SessionAttribute and Autowiring

I'm not a Spring expert and I'm facing a behavior I don't understand...
I have a SessionAttribute "user" in my Controller, that is autowired to my bean User.
When I log in, my User is populated with some values etc.
When I log out, I am expecting that my session attribute "user" would be reset, but it keeps its values.
So where is the problem? Is my log out not working properly? Or is it normal and so, could someone explain me what is happening inside Spring please?
Here is a code Sample to understand my question:
#Controller
#SessionAttributes("user")
public class HomeController
{
#Autowired
private User user;
// Session Attribute
#ModelAttribute("user")
public User setSessionAttribute()
{
LOGGER.debug("Adding user to session...");
return user;
}
...
}
Edit: logout sample code and user declaration
My User is declared like this:
#Component
public class User
{
...
}
To log out I have a link pointing to /myapp/j_spring_security_logout and I have implemented a logout handler:
#Component
public class MyLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler
{
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException
{
//to check if user is in session, but it's not
Enumeration<String> e = request.getSession().getAttributeNames();
//some code needed to log out from my custom security manager
//kill the session (not spring session) and redirect to the specified url
agent.logout("/myapp/login");
super.onLogoutSuccess(request, response, authentication);
}
}
Now that you've posted User
#Component
public class User
{
...
}
you will notice that it is has Singleton scope. The bean autowired here
#Autowired
private User user;
is that singleton instance. It will always be the same regardless of what Session or request you're processing and regardless of you logging out. So up to now, all your users have been sharing the same User instance.
You can change it to have Session scope.
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
#Component
public class User
{
...
}
Now each Session will have its own instance to work with.
I believe SimpleUrlLogoutSuccessHandler is not clearing the content of the session.
SimpleUrlLogoutSuccessHandler only invokes the handle() method in AbstractAuthenticationTargetUrlRequestHandler and it's Javadoc says:
Invokes the configured RedirectStrategy with the URL returned by the determineTargetUrl method.
The redirect will not be performed if the response has already been committed.
Simplest solution would be to remove this attribute from the session by:
request.getSession().removeAttribute("user");

Can't get the session for Junit testing

My Struts2 Action classes use the below code to successfully access the session
ActionContext.getContext().getSession().clear();
However, when I try to use Junit to test my Action classes I get a NullPointer exception.
I have been reviewing some of the comments posted by others on StackOverflow and have been using the below code:
HttpServletRequest request;
HttpSession session;
#Before
public void setUp() throws Exception {
request = Mockito.mock(HttpServletRequest.class);
request.setAttribute("beanList", beanList);
request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getSession()).thenReturn(session);
Map<String, Object> contextMap = new HashMap<String, Object>();
contextMap.put(StrutsStatics.HTTP_REQUEST, request);
ActionContext.setContext(new ActionContext(contextMap));
}
However, it still throws a null pointer error. The system is able to successfully find get the context, but when it tries to get the session it dies on me. I have also tries a few different ways to accomplish the same goal to no avail. Any idea what I am doing wrong?
What about instantiating or mocking your session?
session = mock(HttpSession.class);
before calling
Mockito.when(request.getSession()).thenReturn(session);
Use the dependency injection approach and change your action to implement SessionAware. Then, the Struts2 framework will inject the session into your action, such as in the example below. Finally, you can test by simply injecting a Map into your action.
public class MyAction extends ActionSupport implements SessionAware {
private Map<String, Object> session;
public String execute() {
// do actiony stuff
return SUCCESS;
}
public void setSession(Map<String, Object> session) {
this.session = session;
}
}
FYI, ServletConfigInterceptor handles performing this injection and the same kind of injection is available for accessing other servlet objects, such as the HttpServletRequest or the ServletContext.

Get the Servlet Request object in a POJO class

I need to get the current page URL in a POJO that is being called from an Acegi class (need to add some custom logic for the app I'm working on) and need to retrieve the HttpServletRequest so that I can get the subdomain of the URL (on which the logic is based).
I've tried to add:
#Autowired
private HttpServletRequest request;
...
public void setRequest(HttpServletRequest request) {
this.request = request;
}
public HttpServletRequest getRequest() {
return request;
}
However when I try to use the request object in my code, it is null.
Any idea what I am doing wrong or how I can better go about doing this?
If the bean is request scoped you can autowire the HttpServletRequest like you are doing.
#Component
#Scope("request")
public class Foo {
#Autowired private HttpServletRequest request;
//
}
Otherwise you can get the current request as follows:
ServletRequestAttributes sra = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest req = sra.getRequest();
This uses thread-local under the covers.
If you are using Spring MVC that's all you need. If you are not using Spring MVC then you will need to register a RequestContextListener or RequestContextFilter in your web.xml.

Spring MVC - Response

How can I access the response object from a bean? To get the request object I use the following.
ServletRequestAttributes attr = (ServletRequestAttributes)
RequestContextHolder.currentRequestAttributes();
Is there something similar to the above for response object?
If you are in a web application context (which it looks like you are) you can auto wire in the HttpServletRequest or HttpServletResponse.
The request/response from the current request scope will be injected.
#Component
public class SomeComponentInAWebApplicationContext {
#Autowired
private HttpServletRequest request;
#Autowired
private HttpServletResponse response;
...
}

Resources