【化学药品管理平台——Servlet+Jsp实现 0102】后台用户

前言

   快一周了,第二篇博客才写完。写第一篇的时候感觉挺麻烦、挺累的,因为总想把细节讲清楚。在写第二篇博文的时候,才慢慢觉得没有那么累了,我想,以后会习惯上写博客了吧。

 

 

   这一篇讲Java后台,先介绍整体的class文件结构吧。

   

 

   本篇讲用户,所以我们先只介绍user部分。后台的代码分层开发,运行需要经过Servlet控制层、Service业务层、Dao持久层,所以我们也按这个顺序讲解。

JavaBean

   先介绍JavaBean吧。User很简单,只有四个属性,分别对应用户Id、用户名、用户密码、用户入学年份。

package com.rclv.domain;

public class User {
	
	private String uid;		//用户Id
	private String uname;		//用户名
	private String ugrade;		//用户入学年份
	private String upassword;	//用户密码
        // 省略get、set方法
}

Servlet

   Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

   Servlet主要完成以下任务:读取客户端(浏览器)发送的显式的数据、隐式的 HTTP 请求数据;处理数据并生成结果,这个过程可能需要访问数据库;发送显式的数据、隐式的 HTTP 响应到客户端(浏览器)。

   简单地说,在此项目中,Servlet的作用是:接受Web浏览器的请求,并可能访问数据库处理数据,最终发送响应到浏览器端。上一篇中head.jsp中的库存链接就是一个用Servlet处理的浏览器请求。

 

   Servlet生命周期:首先调用 init () 方法进行初始化,然后调用service() 方法来处理客户端的请求,最后调用 destroy() 方法终止(结束),并由JVM 的垃圾回收器回收。

package com.rclv.test;

public class ServletTest extends HttpServlet{
	// 1.Servlet初始化
	public void init(ServletConfig config) throws ServletException {
	
	}
	
	// 2.1调用service方法,若service没有被重写,则service会把请求转发到doGet或doPost方法;若service被重写,则不进行转发,并完成service方法。
	public void service(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
	
	}
	
	// 2.2doGet方法。
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	
	}
	
	// 2.3doPost方法
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	
	}
	
	// 3.Servlet销毁
	public void destroy() {
	}
}

   service()方法是Servlet的核心。每当客户端发出请求时,Servlet对象的service() 方法就要被调用,并递给Servlet一个ServletRequest对象和一个ServletResponse对象作为service()方法的参数。

   默认情况下,service()方法转发http请求到doGet或doPost方法中。如果重写了该方法,则默认操作被覆盖,不再进行转发操作,而是执行service()方法中的内容。

   所以一般情况下,需要重写doGet和doPost方法。但是这样一个Servlet类只能处理一个请求,而且每次都要重写doGet和doPost方法,如果一个项目需要很多请求方法,就会非常麻烦。而实际开发中,我们在完成某一类功能时,如果让一个Servlet可以处理多个请求方法,就会方便很多,提高了代码的复用性。

BaseServlet

   前面说过,重写service()方法则不再进行转发操作,Servlet直接执行该方法体内的代码。所以可以重写service()方法,来提高代码的复用性。下面我们通过重写HttpServlet继承类的service()方法,并利用反射机制实现对多种方法的调用。

   每个继承HttpServlet的类都会继承其service()方法,所以我们需要抽取一个继承HttpServlet类的基类BaseServlet,重写该类的service()方法即可。前面展示的文件结构中,总共有四个Servlet类,其他三个则都是继承BaseServlet的应用类。

package com.rclv.web.servlet;

/**
 * 通用的servlet
 */
public class BaseServlet extends HttpServlet {
	
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	try {
	  //1.获取子类  创建子类或者调用子类的时候 this代表的是子类对象
	  @SuppressWarnings("rawtypes")
	  Class clazz = this.getClass();
		
	  // 2.获取请求的方法
	  String m = request.getParameter("method");
		
	  // 3.获取方法对象
	  Method method =  clazz.getMethod(m, HttpServletRequest.class, HttpServletResponse.class);
		
	  // 4.方法执行,返回值为请求转发的路径
	  String s = (String) method.invoke(this, request, response);
		
	  // 5.判断s是否为空,否则完成请求转发
	  if(s!=null) {
		request.getRequestDispatcher(s).forward(request, response);
	  }
			
	} catch (Exception e) {
	  e.printStackTrace();
	  throw new RuntimeException();
	}
    }
}

   新建BaseServlet类,继承HttpServlet,重写service方法。

   第一步。try-catch语句捕获异常,通过this.getClass()获取当前运行时类的Class对象。我们应用BaseServlet是用其子类,所以此Class对象将是BaseServlet子类的Class对象。

   此篇讲用户,UserServlet是主要的Servlet,它继承了BaseServlet。所以,当运行UserServlet时,this.getClass()返回的就是UserServlet类的Class对象。下面对于BaseServlet的代码说明,我们都将以其子类UserServlet的视角讲解。

   第二步。获取前台传来的method属性,即获取将要执行的UserServlet中的方法。

   第三步。获取方法对象,即获取UserServlet中将要执行的方法对象。class.getMethod()方法的第一个参数是要获取的方法的方法名,后面的参数是该方法的形参类型。

   第四步。方法执行,并返回一个String类型的返回值。method.invoke()方法的第一个参数为当前类UserServlet的实例,后面两个参数对应执行方法中的参数。

  第五步。请求转发。第四步中的返回值,作为请求转发的路径,此路径是相对路径即可。因为在getRequestDispatcher(url)方法中封装了ServletContext.getRealPath()以获得相应的项目根路径,再通过字符串相加,从而可以获得一个完整的路径。

UserServlet

   UserServlet需要完成三个功能,用户注册、用户登陆、用户退出。UserServlet的访问路径为/user。

   (1)用户注册

<li style="position: relative; left:70%"><a href="${pageContext.request.contextPath }/user?method=loginUI">登录</a></li>
<li style="position: relative; left:70%"><a href="${pageContext.request.contextPath }/user?method=registUI">注册</a></li>

   看前台的注册链接,是一个UserServlet的访问,并带了一个method=registUI键值。根据前面讲的BaseServlet类中service()方法对子类方法的获取和执行,被访问的UserServlet将执行registUI方法。

   registUI方法只有一个返回值,为转发路径。之后将执行service()中的请求转发功能,跳转到/jsp/register.jsp页面。

<!-- 注册表单 -->
<form id="formId" action="${pageContext.request.contextPath }/user?method=regist" method="post">
    <table>
	<tr>
            <th>用户名:</th>
	    <th><input type="text" name="uname"/></th>
	</tr>
	<tr>
	    <th>密码:</th>
	    <th><input type="password" name="upassword"/></th>
	</tr>
	<tr>
	    <th>入学年份:</th>
	    <th><input type="text" name="ugrade"/></th>
	</tr>
	<tr>
	    <th></th>
	    <th align="right"><input type="submit" value="提交"/></th>
	</tr>
    </table>
</form>

   register.jsp页面主要是个简单的注册表单,需要填写用户名、用户密码、入学年份。表单的action发送路径仍是UserServlet请求,提交方式为post,提交表单之后,将会访问UserServlet类,并执行其中的regist方法。

public String regist(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// 1.新建一个User对象,用BeanUtils.populate()方法,将request.getParameterMap()从注册表单获得的 所有key-value的值映射到user中对应的属性。
	User user = new User();
	BeanUtils.populate(user, request.getParameterMap());
	
	// 2.用UUIDUtils.getId()方法给user设置uid属性,用MD5Utils.md5()方法加密user的upassword属性。
	user.setUid(UUIDUtils.getId());
	user.setUpassword(MD5Utils.md5(user.getUpassword()));
	
	// 3.用BeanFactory.getBean()获取一个UserService对象us,并调用us.regist(user)方法进入service层,参数为user。
	UserService us = (UserService) BeanFactory.getBean("UserService");
	us.regist(user);
	
	// 4.设置request域中的msg属性值,返回请求转发路径,完成注册功能。
	request.setAttribute("msg", "注册成功");
	return "/jsp/msg.jsp";
}

   regist方法。

   第一步。新建一个User对象,用BeanUtils.populate()方法,将request.getParameterMap()从注册表单获得的 所有key-value的值映射到user中对应的属性。

   第二步。用UUIDUtils.getId()方法给user设置uid属性。用MD5Utils.md5()方法加密user的upassword属性。

package com.rclv.utils;

import java.util.UUID;

public class UUIDUtils {
	/**
	 * 随机生成id
	 * @return
	 */
	public static String getId(){
		return UUID.randomUUID().toString().replace("-", "").toUpperCase();
	}

}
package com.rclv.utils;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Utils {
	
    public static String md5(String plainText) {
	byte[] secretBytes = null;
	try {
	    secretBytes = MessageDigest.getInstance("md5").digest(
		plainText.getBytes());
	} catch (NoSuchAlgorithmException e) {
	    throw new RuntimeException("找不到md5算法");
	}
	String md5code = new BigInteger(1, secretBytes).toString(16);
	for (int i = 0; i < 32 - md5code.length(); i++) {
	    md5code = "0" + md5code;
	}
	return md5code;
    }
}

   第三步。用BeanFactory.getBean()获取一个UserService对象us,并调用us.regist(user)方法进入service层,参数为user。

   第四步。设置request域中的msg属性值,返回请求转发路径,完成注册功能。

BeanFactory

   在说service层之前,我们先来讲一下BeanFactory。

public class BeanFactory {

    public static Object getBean(String id) {
	try {
	    // 1.通过类加载器获取ClassPath下的beans.xml文件资源,并用SAXReader解析该文件,返回一个Document类型的对象。
	    Document doc = new SAXReader().read(BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml"));
			
	    // 2.通过Xpath语法选取节点元素返回一个Element类型的元素对象。selectNodes()方法的参数格式:节点名[@属性名='属性值'],//代表文档中所有匹配选择的当前节点。
	    Element ele = (Element) doc.selectSingleNode("//bean[@id='"+id+"']");
			
	    // 3.返回元素属性值
	    String value = ele.attributeValue("class");
			
	    // 4.通过反射机制返回一个value对应的类实例。
	    return Class.forName(value).newInstance();
	} catch (Exception e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}
		
	return null;
    }
}

   第一步。通过类加载器获取ClassPath路径下的beans.xml文件资源,并用SAXReader解析该文件,返回一个Document类型的对象。

   第二步。通过Xpath语法选取节点元素返回一个Element类型的元素对象。selectNodes()方法的参数格式:节点名[@属性名='属性值']。

   第三部。返回元素的class属性值。

   第四步。通过反射机制,用Class.forName(value).newInstance()方法返回一个value对应的类实例。

<?xml version="1.0" encoding="UTF-8"?>
<beans>
	<bean id="AgentsService" class="com.rclv.service.impl.AgentsServiceImpl"/>
	<bean id="UserService" class="com.rclv.service.impl.UserServiceImpl"/>
	
	<bean id="AgentsDao" class="com.rclv.dao.impl.AgentsDaoImpl"/>
	<bean id="UserDao" class="com.rclv.dao.impl.UserDaoImpl"/>
	
</beans>

   从beans.xml中可以看到,bean标签中class属性对应的都是具体的接口实现类,id属性被用来查询获取元素节点。

   我们再回到UserServlet中。

    UserService us = (UserService) BeanFactory.getBean("UserService");
    us.regist(user);

   BeanFactory.getBean(“UserService”)此时传入的参数是UserService,此时将返回beans.xml中对应的UserServiceImpl的接口实现类。则UserService接口类创建的实例us就是该接口的实现类,返回类型需要强转为UserService类。

   这种方法就是面向切面编程的思想。在开发中,如果UserService有多个实现类,并且在多个类中都创建了UserService的某一个实现类,当项目有需求需要替换该实现类时,就需要在每个调用类中都修改相应代码,就会非常麻烦。而如果用BeanFactory这种工厂模式编程的话,只需要修改当前BeanFactory.getBean("")方法中的参数在bean.xml中对应的class属性即可。修改之后,该方法将返回当前接口的其他实现类。

UserService

   UserService是接口类,UserServiceImpl是其实现类。

package com.rclv.service;

import com.rclv.domain.User;

public interface UserService {

	void regist(User user) throws Exception;

	User login(String uname, String upassword) throws Exception;

}
package com.rclv.service.impl;

import com.rclv.dao.UserDao;
import com.rclv.domain.User;
import com.rclv.service.UserService;
import com.rclv.utils.BeanFactory;

public class UserServiceImpl implements UserService {

	@Override
	public void regist(User user) throws Exception {
		UserDao ud = (UserDao) BeanFactory.getBean("UserDao");
		ud.regist(user);
	}

	@Override
	public User login(String uname, String upassword) throws Exception {
		UserDao ud = (UserDao) BeanFactory.getBean("UserDao");
		return ud.getByNameAndPassword(uname, upassword);
	}

}

   继续看注册方法吧。Service层的代码很简单,先由BeanFactory.getBean(“Dao”)方法返回一个UserDao的实现类ud,然后调用ud.regist(user)方法进入dao层,参数为上一层传来的user。

   这里看似service层没有完成什么功能,只是返回了一个UserDao实现类ud,并调用ud的方法进入了dao层。其实是因为我们这里需要完成的功能比较简单,service层是用来完成业务逻辑的,带些逻辑的比如,通过验证用户名是否存在来决定是否调用dao层方法来完成数据库查询操作。

   在下一篇的药品管理AgentsService模块中,将会出现在一个方法内调用AgentsDao层两个不同的方法完成不同的数据库操作,从而体现service层的作用。

 

UserDao

   最后是持久层。

package com.rclv.dao;

import com.rclv.domain.User;

public interface UserDao {

	void regist(User user) throws Exception;

	User getByNameAndPassword(String uname, String upassword) throws Exception;

}
package com.rclv.dao.impl;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;

import com.rclv.dao.UserDao;
import com.rclv.domain.User;
import com.rclv.utils.DataSourceUtils;

public class UserDaoImpl implements UserDao {

	// 数据库添加user
	@Override
	public void regist(User user) throws Exception {
		// 1.Dbutils工具类操作数据库,c3p0连接池作为queryrunner构造器参数。
		QueryRunner qr = new QueryRunner(DataSourceUtils.getDataSource());
		// 2.设置sql查询语句。
		String sql = "insert into user values(?,?,?,?)";
		// 3.用qr.update()方法完成数据库添加操作,第一个参数为sql,后面几个参数为user表中要添加的字段值,分别对应sql中的占位符。
		qr.update(sql, user.getUid(), user.getUname(), user.getUgrade(), 
				user.getUpassword());
	}

	// 数据库查询user
	@Override
	public User getByNameAndPassword(String uname, String upassword) throws Exception {
		QueryRunner qr = new QueryRunner(DataSourceUtils.getDataSource());
		String sql = "select * from user where uname = ? and upassword = ? limit 1";
		return qr.query(sql, new BeanHandler<>(User.class), uname, upassword);
	}

}

   UserDao是接口,UserDaoImpl是其实现类。

   regist方法中,用Dbutils工具类结合c3p0连接池完成数据库操作。

   第一步。用构造器创建QueryRunner类对象qr,由DataSourceUtils返回的c3p0数据库连接池做参数,实现对数据库的链接。

   第二步。设置sql查询语句。

   第三步。用qr.update()方法完成数据库添加操作,第一个参数为sql,后面几个参数为user表中要添加的字段值,分别对应sql中的占位符。

   至此,完成数据库添加操作,完成用户注册功能。

c3p0数据库连接池

   c3p0是一种JDBC数据库连接池。使用连接池的原因是,数据库的每次使用都需要发送请求、验证用户,执行完数据库操作再断开连接,这样非常消耗时间和资源。而数据库连接池是在系统初始化的时候,就建立好的空闲连接;当需要进行数据库连接的时候,只需从连接池中取出一个连接对象,使用完之后,不用关闭连接,而是将其放回连接池中。

   数据库连接池使得资源可重复使用,减少了系统的相应时间,提高了应用程序的性能。

   c3p0的配置非常简单。在src目录下创建一个c3p0.properties文件,配置内容如下。

c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql://localhost:3306/cupboard
c3p0.user=root
c3p0.password=root

   (2)用户登陆

<li style="position: relative; left:70%"><a href="${pageContext.request.contextPath }/user?method=loginUI">登录</a></li>
<li style="position: relative; left:70%"><a href="${pageContext.request.contextPath }/user?method=registUI">注册</a></li>

   和用户注册一样,前台登陆链接访问UserServlet的loginUI方法,返回请求转发路径,跳转到login.jsp登陆页面。

public String registUI(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    return "/jsp/register.jsp";
}
<!-- 登录表单 -->
<form id="formId" action="${pageContext.request.contextPath }/user?method=login" method="post">
	<table>
		<tr>
			<td>用户名:</td>
			<td><input type="text" name="uname"/></td>
		</tr>
		<tr>
			<td>密码:</td>
			<td><input type="password" name="upassword"/></td>
		</tr>
		<tr>
			<td colspan="2" align="center"><input type="submit" value="提交" /></td>
		</tr>
	</table>
</form>

   login.jsp是一个非常简单的登陆表单,action发送请求访问UserServlet类的login方法。

public String login(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// 1.获取前台参数,并对upssword进行加密
	String uname = request.getParameter("uname");
	String upassword = request.getParameter("upassword");
	upassword = MD5Utils.md5(upassword);
	
	// 2.返回一个UserService实例,并调用login方法进入service层
	UserService us = (UserService) BeanFactory.getBean("UserService");
	User user = us.login(uname, upassword);
	
	// 3.判断user是否查询返回成功。若不成功,则重新跳转到login.jsp登陆页面
	if (user == null) {
		request.setAttribute("msg", "登录失败");
		return "/jsp/login.jsp";
	}
	
	// 4.若user返回成功,则在session域中添加user对象,并重定向到首页。
	request.getSession().setAttribute("user", user);
	response.sendRedirect(request.getContextPath() + "/");
	
	return null;
}

   控制层:login(HttpServletRequest request, HttpServletResponse response)方法。

   第一步。用request.getParameter()方法获得前台传来的uname和upassword属性,并对upassword进行md5加密。

   第二步。调用BeanFactory.getBean("UserService")方法返回UserService实现类us,并调用us.login(uname, upassword)方法进入service层,完成查询业务后返回一个user对象。

   第三步。判断user是否返回成功。若user==null返回不成功,则设置request域的msg属性,并return转发路径,重新跳转到登陆页面。

第四步。user返回成功,则将该user对象设置给session域的user属性,并用response.sendRedirect(request.getContextPath() + "/")方法重定向到首页。request.getContextPath()获得的是项目的根路径,则根据web.xml中的<welcome-file-list>配置

默认打开首页。

 

   业务层:User UserService.login(String uname, String upassword)方法。

   和之前一样,BeanFactory工厂返回一个UserDao对象ud,ud调用ud.getByNameAndPassword(uname, upassword)方法进入dao层。

 

   持久层:User getByNameAndPassword(String uname, String upassword)方法。

   前两步。Dbutils连接数据库,创建QueryRunner对象qr,创建查询语句sql,条件查询user表中uname、upassword和方法传入参数都对应相同的一个数据。

   第三步。执行query查询语句,封装结果集参数 new BeanHandler<>(User.class)表示查询返回单个JavaBean(User)对象。

   至此,完成用户登录功能。

 

   我们可以再回到前端head.jsp。

<!-- 登录和注册 -->
<c:if test="${empty user }">
	<li style="position: relative; left:70%"><a href="${pageContext.request.contextPath }/user?method=loginUI">登录</a></li>
	<li style="position: relative; left:70%"><a href="${pageContext.request.contextPath }/user?method=registUI">注册</a></li>
</c:if>
<!-- 用户名和退出登录 -->
<c:if test="${not empty user }">
	<li style="position: relative; left:68%"><a href="javascript:void(0);">${user.uname }_${user.ugrade }:您好</a></li>
	<li style="position: relative; left:68%"><a href="${pageContext.request.contextPath }/user?method=logout">退出</a></li>
</c:if>

   如果后台登陆成功,就会返回一个session域的user对象。head.jsp中会判断当前页面中是否有user对象,若有则El显示用户名和用户入学年份;若没有则显示登陆按钮。

 

   (1)用户退出

public String logout(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// 使用request。getSession().invalidate()方法销毁当前session域中的所有属性
	request.getSession().invalidate();
	response.sendRedirect(request.getContextPath());
		
	return null;
}

   用户退出的功能就更简单了,登陆是往session域中添加一个user对象,退出则是删除这个user对象。使用getSession().invalidate()方法,可以销毁当前session域中的所有属性,达到删除session中user对象的作用。

   销毁session之后,重定向返回首页,完成用户退出功能。

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2018-07-07 14:40  XD_Yangf  阅读(10)  评论(0编辑  收藏  举报