I'm trying to develop a small Spring MVC application, where i'd like User object to initialize from the beginning of each session.
I have the User class
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyUser implements User {
// private fields
// getters and setters
public void fillByName(String username) {
userDao.select(username);
}
}
And i want to initialize MyUser object once Spring Security recognize the user, in Interceptor Class (Btw, is it a good practice?)
public class AppInterceptor extends HandlerInterceptorAdapter {
@Autowired
MyUser user;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!(auth instanceof AnonymousAuthenticationToken)) {
user.fillByName(auth.getName());
}
return true;
}
}
So, when Controller handles the request, there is already initialized session scoped User class. But when i try to serialize MyUser object with Jackson, it just doesnt't work:
@RequestMapping("/")
public String launchApp(ModelMap model) {
ObjectMapper mapper = new ObjectMapper();
try {
System.out.println(user.getUsername()); // Works good!
model.addAttribute("user", mapper.writeValueAsString(user)); // Doesn't work
} catch (JsonProcessingException e) {
// @todo log an error
}
return "app/base";
}
As you can see, MyUser object getters work good from the Controller class, but Jackson - doesn't.
When i remove @Scope annotation from User object, Jackson serialization start working.
Obviously, scoped proxy bean and singleton Controller class are the problem
But how i can fix it?
--
UPDATE
Looks like i'm first who came across this :) Maybe it is bad architecture? Should i create a new instance of MyUser class in the Controller? What is the common practice?
Whenever the beanB is requested from the container a new instance will be created. To solve these types of problem, Java spring framework provides the concept called proxy beans. For dependencies with less scope than a parent, the framework will create the proxies rather than creating actual objects.
Singleton: Only one instance will be created for a single bean definition per Spring IoC container and the same object will be shared for each request made for that bean. Prototype: A new instance will be created for a single bean definition every time a request is made for that bean.
According to Spring Docs Bean is singleton only one shared Instance will be managed, and all request beans with an ID or ID matching that bean definition.
Marker annotation identical in functionality with <aop:scoped-proxy/> tag. Provides a smart proxy backed by a scoped bean, which can be injected into object instances (usually singletons) allowing the same reference to be held while delegating method invocations to the backing, scoped beans.
The reason for this is that your proxyMode
is set to TARGET_CLASS
, which instructs Spring to create a class-based proxy for your class (essentially a new class that implements the interface of the original class, but might not be compatible with the way ObjectMapper
converts objects into string).
You can solve this by telling ObjectMapper
which class signatures to use when writing the object by using writerFor
as such:
mapper.writerFor(User.class).writeValueAsString(user)
Or if you already have a custom writer, use forClass
, like this:
writer.forClass(User.class).writeValueAsString(user)
One way that I can think of is to add yet another wrapper:
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyScopedUser implements User {
private MyUser myUser;
// private fields
// getters and setters
public void fillByName(String username) {
userDao.select(username);
}
public MyUser getMyUser() {
return this.myUser;
}
}
So now your MyScopedUser
is a scoped proxy, but the core user is a normal class. You can get the user out and marshal later on:
mapper.writeValueAsString(scopeUser.getMyUser())
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With