Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC @Scope proxy bean & Jackson 2

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?

like image 324
silent-box Avatar asked Apr 03 '15 20:04

silent-box


People also ask

What is proxy bean in Spring?

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.

What is difference between singleton and prototype bean?

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.

Are beans singletons?

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.

What is AOP scoped proxy in Spring?

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.


2 Answers

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)
like image 174
ggradnig Avatar answered Oct 14 '22 10:10

ggradnig


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())
like image 44
Biju Kunjummen Avatar answered Oct 14 '22 10:10

Biju Kunjummen