Spring Web应用中控制器、服务、存储库的范围是什么?

8

我正在使用Spring和Hibernate创建一个Web应用程序。

假设有多个用户想要注册。我将创建一个注册Bean(原型或请求或会话作用域),并在Controller中自动装配它。

现在,我将此Bean传递给Registration Service(带有“@transactional”注释),该服务也在控制器中自动链接。此服务将接收到的注册Bean对象传递给DAO(此DAO在服务中进行了自动链接)。如果服务和DAO是单例模式,那么对于多个用户,请求不会混杂吗?

这是我的做法:我将服务和DAO的范围设置为“请求”。

这样做是否正确?还是我应该采取其他措施使服务和DAO成为“单例模式”呢?

我选择请求作用域的逻辑如下:

将服务和DAO设置为请求作用域的原因是,如果多个用户同时从控制器调用registerationService.registerUser(bean); 并且范围是单例模式,则会存在不一致性,因为一个对象的方法会被不同的输入调用。

请告诉我哪里出错了。

注册Bean

@Component(value="registerBean")
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "request")
public class RegisterBean {


@NotNull
private String userName;

private String lastName;
@NotNull
private String firstName;

String email_address;
String password;
String confirmPassword;
String gender;

//getters and setters


}

控制器

package com.ClickToShop.controllers;






 @Controller
    @SessionAttributes("user_info")
    public class LoginPageController {




        RegisterBean registerBean;//used

        RegisterationService registerationService;//used



        @Autowired
        @Qualifier("registerationService")
        public void setRegisterationService(RegisterationService registerationService) {
            this.registerationService = registerationService;
        }




        @Autowired
        @Qualifier("registerBean")
        public void setRegisterBean(RegisterBean registerBean) {
            this.registerBean = registerBean;
        }



        @ModelAttribute(value = "registerBean")
        RegisterBean returnModelAttribute() {
            return registerBean;
        }

        @RequestMapping(value = "/login-page.html")
        public String showLoginPage() {
    System.out.println("Showing login page");
    System.out.println(registerBean);
            return "login-page";

        }



        @RequestMapping(value = "/newuser-register", method = RequestMethod.POST)
        public String registernewuser( @ModelAttribute("registerBean") @Valid RegisterBean bean, BindingResult result,final RedirectAttributes redirectAttr)
                throws NoSuchAlgorithmException, UnsupportedEncodingException {
            //some validation code

     registerationService.registerUser(bean);



                    return "redirect:successRegisteration";
                }


        }




    }




Service Layer

        @Service("registerationService")
        @Transactional
        @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS,value="request")

        public class UserServiceImpl implements RegisterationService {


            private User_Details_Pojo userToRegisterPojo;
            private AbstractHibernateDAO UserDAO;


            @Autowired
            public void setUserDAO(AbstractHibernateDAO userDAO) {
                UserDAO = userDAO;
            }



            @Autowired
            @Qualifier("userToRegisterPojo")
            public void setUserToRegisterPojo(User_Details_Pojo userToRegisterPojo) {
                this.userToRegisterPojo = userToRegisterPojo;
            }




        //main implementation code starts here

            @Override

            public void registerUser(Object userBean) {
                RegisterBean bean=(RegisterBean) userBean;
                //bean or model is converted to pojo


            UserDAO.save(userToRegisterPojo);//calling DAO with specified pojo



            }



        }

DAO:

public abstract class AbstractHibernateDAO<T extends Serializable> {

    public Class<T> clazz;//class object reference

    protected SessionFactory mysessionFactory;


    @Autowired
    public void setMysessionFactory(SessionFactory mysessionFactory) {
        this.mysessionFactory = mysessionFactory;
    }

    public T findOneByName(final String name){

        return (T) getCurrentSession().createQuery("from "+clazz.getName()).uniqueResult();
    }


    public void setClazz(final Class<T> clazzToSet) {
        this.clazz = clazzToSet;
    }

    public T findOne(final Long id) {
        return (T) getCurrentSession().get(clazz, id);
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll() {
        return getCurrentSession().createQuery("from " + clazz.getName()).list();
    }

    public void save(final T entity) {
        getCurrentSession().merge(entity);
    }

    public void update(final T entity) {
        getCurrentSession().update(entity);
    }

    public void delete(final T entity) {
        getCurrentSession().delete(entity);
    }

    public void deleteById(final Long entityId) {
        final T entity = findOne(entityId);
        delete(entity);
    }

    protected Session getCurrentSession() {

        return mysessionFactory.getCurrentSession();
    }
}

具体DAO

@Repository
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS,value="request")
public class UserDAO extends AbstractHibernateDAO<User_Details_Pojo>{


}
5个回答

3
将您的服务类用@Service注释,Dao类用@Repository注释。
<context:component-scan base-package="x.y.z.service, x.y.z.dao" /> 

它会自动为您的类创建单例bean。


3

服务和DAO应该是无状态的。这将允许您将它们配置为Spring单例。我希望这就是您所说的“单例”的含义。

所有线程相关问题,包括线程池,都将由基础架构处理:您的Web / Java EE服务器和Spring DI。


假设有多个用户想要注册。我将为每个用户创建一个注册bean(原型,请求,会话作用域)。现在我将此bean传递给Registration Service(使用“@transactional”注释进行注释)。该服务将接收到的注册bean对象传递给DAO。如果服务和DAO是单例的,那么对于多个用户,请求不会混淆吗? - beinghuman
除非您正在尝试在DAO中自己管理Hibernate会话,否则不应发生这种情况。Spring将为每个线程分配一个事务上下文,包括Hibernate会话。Web服务器将为每个用户请求分配一个线程/从池中获取一个线程。因此,两个用户的注册bean将“存在”于不同的内存位置,并通过单独的Hibernate会话进行处理,并在单独的DB连接上的单独事务中写入数据库。 - Olaf
如果你能解释一下,这个问题就会变得清晰明了。多个请求到来时,Web服务器为每个请求分配一个独立的线程。这些线程使用相同的单例控制器,该控制器具有原型注册bean。那么每个线程将如何获得单独的模型bean呢?因为我已经看到,如果单例控制器使用原型bean,则在初始化此单例控制器时,它的依赖原型bean也将被创建。因此,所有请求都将访问单个bean。请给我指点迷津。 - beinghuman
我想知道以下内容是否对您有帮助:https://dev59.com/GnA75IYBdhLWcg3wy8Qi - Olaf
@Niks:我研究了Spring MVC中处理用户会话的替代方法(与我通常做的不同),看起来如果你想使用一个会话范围的注册bean,相应的控制器需要是请求范围而不是单例。 - Olaf
显示剩余3条评论

2
除非你的DAO需要实例字段,否则它们不需要是请求范围。只要使用Session#getCurrentSession()检索Hibernate Session(它是线程绑定的),一个单独的DAO实例来处理所有请求就可以了。同样适用于Service类和Controllers。
至于你的声明:

which I think slow down the response to the user

并不总是正确的,这取决于对象的重量。Servlet容器和Spring的DispatcherServlet已经实例化了很多对象。你不应该看到太大的变化。
创建这些对象的池会过度杀伤。注意:这些不会是线程池,只是对象池。

假设有多个用户想要注册。我将为每个用户创建一个注册bean(原型,请求,会话作用域)。现在我将此bean传递给Registration Service(使用@transactional注释进行注释)。该服务将接收到的注册bean对象传递给DAO。如果服务和DAO是单例的,那么对于多个用户,请求不会混淆吗? - beinghuman
@Nikhil:不需要。单例服务和DAO是默认的,只要您没有在任何实例成员中具体指定任何个别请求的内容,它们就可以正常工作。将请求特定的内容保留在方法参数和本地变量中,Spring会处理其余部分。 - Nathan Hughes
@NathanHughes 请查看我在Olaf答案中的评论。你能帮我理解吗? - beinghuman
@NikhilArora 你传递的Registration bean是请求作用域的,也就是说它是在Controller处理方法中创建的。除非你将其传递给另一个线程,否则它的范围将仅限于操作它的方法。换句话说,每次调用DAO方法时,都会有一个不同的Registration实例。这样就不会出现并发问题。 - Sotirios Delimanolis

0

你应该使用Spring将你的服务和DAO对象注入到控制器中,通过Spring注入的所有bean默认都是单例模式。


请再仔细阅读我的问题。没想到会得到这样的答案。我的问题不清楚吗?请告诉我。 - beinghuman
我们通常不会为每个请求创建服务和DAO对象,也不需要为这些对象创建线程池,尽管为每个请求实例化这两个对象不会在可测量的程度上明显减慢过程,除非它们本身具有大量依赖项需要实例化。 - Pratik Tari
好的,我觉得我可能漏掉了什么。每个请求都需要访问数据库中的某些内容。对于这种情况,我肯定不能使用单例服务和DAO对象,对吧? - beinghuman

0

我正在经历同样的困惑,但是在阅读了所有链接之后,我明白了以下内容(如果我理解有误,请纠正我):

注册bean将是原型或请求类型,因为可能会有两个或更多的注册请求同时进行,如果该对象是单例的,则会覆盖彼此的值。

DAO和服务将是单例的,因为它们只是方法,不修改任何全局变量,这些变量将被其他线程使用。它们有自己的堆栈。


是的。或者换句话说,在多线程环境中要共享不变的单例必须通过将任何实例变量设为final或在构造函数中加载来使其成为无状态的。 - theRiley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接