SpringMVC(7):单例模式在MVC框架的应用和分析常见缺陷(查询增加后的数据错误)

2018/1/7

   SpringMVC 与单例设计 :


    通过《Spring(20):单例模式详解与示例分析(懒汉模式/饿汉模式/静态内部类改造)》我们知道了单例模式的概念,SpringMVC 框架中也对其单例进行了运用,而且Controller 类默认是单例的,了解这一点很重要!!当然,这样也是基于性能的考虑,因为Controller设计为单例,就不用每次都创建实例,速度和性能自然优越。注意:使用的时候也要注意 --  一般不在Controller 中定义成员变量,因为会导致严重线程安全安全和资源使用情况


    SpringMVC 实例:


一、无查询条件

【0】新建 User.java 文件,一定包含有参数的构造器:

package com.smbms.entities;

import java.util.Date;
import java.util.List;

public class User {
	private Integer id;
	private String userCode;
	private String userName;
	private String userPassword;
	private Integer gender;
	private Date birthday;
	private String phone;
	private String address ;
	private Integer userRole;
	private Integer createdBy;
	private Date creationDate;
	private Integer modifyBy;
	private Date modifyDate;

	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getUserCode() {
		return userCode;
	}
	public void setUserCode(String userCode) {
		this.userCode = userCode;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getUserPassword() {
		return userPassword;
	}
	public void setUserPassword(String userPassword) {
		this.userPassword = userPassword;
	}
	public Integer getGender() {
		return gender;
	}
	public void setGender(Integer gender) {
		this.gender = gender;
	}
	public Date getBirthday() {
		return birthday;
	}
	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}
	public String getPhone() {
		return phone;
	}
	public void setPhone(String phone) {
		this.phone = phone;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public Integer getUserRole() {
		return userRole;
	}
	public void setUserRole(Integer userRole) {
		this.userRole = userRole;
	}
	public Integer getCreatedBy() {
		return createdBy;
	}
	public void setCreatedBy(Integer createdBy) {
		this.createdBy = createdBy;
	}
	public Date getCreationDate() {
		return creationDate;
	}
	public void setCreationDate(Date creationDate) {
		this.creationDate = creationDate;
	}
	public Integer getModifyBy() {
		return modifyBy;
	}
	public void setModifyBy(Integer modifyBy) {
		this.modifyBy = modifyBy;
	}
	public Date getModifyDate() {
		return modifyDate;
	}
	public void setModifyDate(Date modifyDate) {
		this.modifyDate = modifyDate;
	}
	public User(Integer id, String userCode, String userName, String userPassword, Integer gender, Date birthday,
			String phone, String address, Integer userRole, Integer createdBy, Date creationDate, Integer modifyBy,
			Date modifyDate) {
		super();
		this.id = id;
		this.userCode = userCode;
		this.userName = userName;
		this.userPassword = userPassword;
		this.gender = gender;
		this.birthday = birthday;
		this.phone = phone;
		this.address = address;
		this.userRole = userRole;
		this.createdBy = createdBy;
		this.creationDate = creationDate;
		this.modifyBy = modifyBy;
		this.modifyDate = modifyDate;
	}
	public User() {
		super();
		// TODO 自动生成的构造函数存根
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", userCode=" + userCode + ", userName=" + userName + ", userPassword=" + userPassword
				+ ", gender=" + gender + ", birthday=" + birthday + ", phone=" + phone + ", address=" + address
				+ ", userRole=" + userRole + ", createdBy=" + createdBy + ", creationDate=" + creationDate
				+ ", modifyBy=" + modifyBy + ", modifyDate=" + modifyDate + "]";
	}
	
}


【1】写控制器 IndexController.java:

package com.smbms.controller;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.smbms.entities.User;

@Controller 
@RequestMapping("/a")
public class IndexController {
	private Logger log = Logger.getLogger(IndexController.class.getName());
	private ArrayList<User> userList  = new ArrayList<User>(); 
	
	public IndexController(){
		try{
			userList.add(new User(11,"test001","测试用户001","1111111",1,new SimpleDateFormat("yyyy-MM-dd").parse("1986-12-10"),"13456666998","北京朝阳区北苑",1,1,new Date(),1,new Date()));
			userList.add(new User(12,"zhaoyan","赵燕","2222222",1,new SimpleDateFormat("yyyy-MM-dd").parse("19846-12-10"),"13456666998","北京朝阳区车姑路",1,1,new Date(),1,new Date()));
			userList.add(new User(13,"test003","测试用户003","333333",1,new SimpleDateFormat("yyyy-MM-dd").parse("1986-12-10"),"1345666766998","北京朝阳区北苑",1,1,new Date(),1,new Date()));
			userList.add(new User(11,"wanglin","王林","1111111",1,new SimpleDateFormat("yyyy-MM-dd").parse("1989-12-10"),"13457777998","北京朝阳区学府",1,1,new Date(),1,new Date()));
			userList.add(new User(11,"zhaojing","赵静","1111111",1,new SimpleDateFormat("yyyy-MM-dd").parse("1981-12-10"),"13874366998","北京朝阳区五山",1,1,new Date(),1,new Date()));
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
  //POJO+Model
  @RequestMapping(value="/hello",method=RequestMethod.GET)
  public String hello5(Model model){
	  log.info("无查询条件下,获取userlist(公共查询)====userlist");	
  	  model.addAttribute("queryUserList",userList);
	  return "helloB";
  }
  
}  

解释:没有用到数据库,因此,id重复暂不做处理。



【2】写一个超链接文本index.jsp:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
	<a href="http://www.baidu.com">click</a>		
	<a href="a/hello?username=mmb">hello-username</a>
	<a href="a/hello?usernameA=mmb">hello-usernameA</a>
	<a href="a/hello?usernameB=mmb">hello-usernameB</a>
	<table width="400">
		<a href="a/hello"> userlist </a>
	</table>
</body>
</html>

解释:点击userlist连接。

<a href="a/hello"> userlist </a>


【3】写响应视图 helloB.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>查询用户列表</h1>
	<c:forEach var="user" items="${queryUserList}">
		<div>
			id:${user.id} ---
			userCode:${user.userCode} ---
			userName:${user.userName} ---
			code:${user.userPassword} ---
			birthday:${user.birthday} ---
			address:${user.address} ---
		</div>
	</c:forEach>
</body>
</html>


解释:(1)使用了JSTL标签和EL表达式,注意加入依赖包;

(2)<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>需要依赖包;按照查到的资料,JSTL taglib需要jstl.jar来支持。
在1.0和1.1版本的时候,还需要standard.jar来配合。但从1.2版本开始,jar文件名字变成了jstl-1.2.jar,也不再需要standard.jar了。


【4】输出结果:

18/01/08 23:09:00 INFO controller.IndexController: 无查询条件下,获取userlist(公共查询)====userlist



上面的无查询条件下,获取userlist正常显示。但如果增加了查询条件,就会出问题了。


二、加入查询条件


【1】修改IndexController.java,添加以下内容:

  private ArrayList<User> queryUserList  = new ArrayList<User>();
  //增加查询条件:username
  @RequestMapping(value="/list" ,method=RequestMethod.POST)
  public String hello6(@RequestParam(value="userName",required=false)
                          String userName,Model model){
	  log.info("加入查询条件: username: "+ userName+", ====userlist");
	  if(userName != null && !userName.equals("")){
		  for(User user:userList){
			  if(user.getUserName().contains(userName)){
				  queryUserList.add(user);
//				  log.info("user.toString()"+user.toString());
			  }
		  }
		  model.addAttribute("quertuserList",queryUserList);
	  }else{
		  model.addAttribute("quertuserList",userList);
	  }
	  return "helloC";
  }

解释:(1)再添加一个处理器hello6()方法,响应的连接为“a/list”(是通过helloB页面添加的一个提交表单)。

(2)进行用户名的模糊查询,如果已有的数据包含输入字段,则再次通过页面展现。


【2】修改helloB.jsp ,添加了以下内容,多了一个提交按钮:

	<h1>有条件查询用户列表</h1>
	<form action="${pageContext.request.contextPath}/a/list" method="post">
		用户名称name:<input type="text" name="userName" value=""/>
		<input type="submit" name="查询check"/>
	</form>
 【3】新建  
helloC.jsp ,和 helloB.jsp 大同小异 ,展示条件筛选后的结果: 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>有条件查询用户列表结果</h1>
	<c:forEach var="user" items="${quertuserList}">
		<div>
			id:${user.id} ---
			userCode:${user.userCode} ---
			userName:${user.userName} ---
			code:${user.userPassword} ---
			birthday:${user.birthday} ---
			address:${user.address} ---
		</div>
	</c:forEach>

</body>
</html>

【4】输出结果:

控制台:

18/01/09 00:02:10 INFO controller.IndexController: 无查询条件下,获取userlist(公共查询)====userlist
18/01/09 00:02:26 INFO controller.IndexController: 加入查询条件: username: 3, ====userlist

Web:


图1



图2

(因为测试了两次,因此ArrayList存放了两个数据)



解释:(1)在高并发模式下,用户a和b同时输入不同的查询条件后,有可能数据显示不正确 --- 即b用户的CPU抢占了资源,那么a用户显示的就是b的查询结果了。

(2)根据业务需求就要更改Controller的scope 为prototype,在@Controller之前增加@Scope("prototype"),就不再是单例了,但这种做法很少用。SpringMVC 框架设计为单例,自然为了提高性能。

(3)上面配的两幅图,是单用户查询结果,所以是正确的。。


三、总结

    通过以上实例,SpringMVC的Controller是单例的。其内部的成员变量是公开的,开发的时候需要注意资源使用的问题。上面讲到了按条件查询,实际运用还有更新(update),会出现类似:一个线程修改了某个成员变量,其他线程都会拿到修改后的结果,显然是不对的。

   因此,在Controller 内,我们可以声明Service 成员变量,通过@Resource 或者 @Autowired 方式装载。

eg:

	@Resource
	private Userservice userService;

由于Service 是公用的,并且它是接口,接口内不含有成员变量,都是方法,而方法内的变量也都是线程级的,故不会出现数据资源抢占或内存溢出。所以一般情况下,Controller 内的成员变量就只有Service 对象了



以上是博文的全部内容。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值