开发模式的介绍

在web开发模式中,有两个主要的开发结构,称为模式一和模式二。

相关概念:

  • DAO(Data Access Object):主要对数据的操作,增加,修改,删除等原子性操作。
  • Web层:界面+控制器,也就是说JSP【界面】+Servlet【控制器】。
  • Service业务层:将多个原子性的DAO操作进行组合,组合成一个完整的业务逻辑。
  • 控制层:主要使用Servlet进行控制。
  • 数据访问层:使用DAO、Hibernate、JDBC技术实现对数据的增删改查。
  • JavaBean用于封装数据,处理部分核心逻辑,每一层中都用到。

模式一

模式一指的就是在开发中将显示层、控制层、数据层的操作统一交给JSP或者JavaBean来进行处理。

模式一有两种情况:

1)完全使用JSP做开发

  • 优点:开发速度快,只要写JSP就行了,JavaBean和Servlet都不用设计;小幅度修改代码方便,直接修改JSP页面交给WEB容器就行了,不像Servlet还有编译成.class文件再交给服务器。
  • 缺点:程序的可读行差、复用性低、代码复杂。

2)使用JSP+JavaBean做开发

  • 优点:程序的可读性较高,大部分的代码都写在JavaBean上,不会和HTML代码混合在一起,可读性还行的;可重复利用高,核心的代码都由JavaBean开发了,JavaBean的设计就是用来重用、封装、大大减少编写重复代码的工作。
  • 缺点:没有流程控制,程序中的JSP页面都需要检查请求的参数是否正确,异常发生时的处理。显示操作和业务逻辑代码工作会紧密耦合在一起的。日后维护会困难。

 应用实例:

使用JSP+JavaBean开发一个简易的计算器:

首先开发JavaBean对象

public class Calculator {
    private double firstNum;
    private double secondNum;
    private char operator='+';
    private double result;

    public void calculate() {
        switch (this.operator) {
            case '+':
                this.result = this.firstNum + this.secondNum;
                break;
            case '-':
                this.result = this.firstNum - this.secondNum;
                break;
            case '*':
                this.result = this.firstNum * this.secondNum;
                break;
            case '/':
                if (this.secondNum == 0) {
                    throw new RuntimeException("除数不能为0");
                }
                this.result = this.firstNum / this.secondNum;
                break;
            default:
                throw new RuntimeException("传入非法字符");
        }
    }

    public double getFirstNum() {
        return firstNum;
    }

    public void setFirstNum(double firstNum) {
        this.firstNum = firstNum;
    }

    public double getSecondNum() {
        return secondNum;
    }

    public void setSecondNum(double secondNum) {
        this.secondNum = secondNum;
    }

    public char getOperator() {
        return operator;
    }

    public void setOperator(char operator) {
        this.operator = operator;
    }

    public double getResult() {
        return result;
    }

    public void setResult(double result) {
        this.result = result;
    }
}

在开发显示页面

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
<head>
    <title>Title</title>
    <jsp:useBean id="calculator" class="com.company.Calculator" scope="page"></jsp:useBean>
    <jsp:setProperty name="calculator" property="*"></jsp:setProperty>

    <jsp:scriptlet>
        calculator.calculate();
    </jsp:scriptlet>
    <c:out value="计算得出的结果是:"/>
    <jsp:getProperty name="calculator" property="firstNum"/>
    <jsp:getProperty name="calculator" property="operator"/>
    <jsp:getProperty name="calculator" property="secondNum"/>
    <c:out value="="/>
    <jsp:getProperty name="calculator" property="result"/>
</head>
<body>
<form action="Calculator.jsp" method="post">
    <table border="1">
        <tr>
            <td colspan="2">简单计数器</td>
        </tr>
        <tr>
            <td>第一个参数:</td>
            <td><input type="text" name="firstNum"></td>
        </tr>
        <tr>
            <td>运算符</td>
            <td>
                <select name="operator">
                    <option value="+">+</option>
                    <option value="-">-</option>
                    <option value="*">*</option>
                    <option value="/">/</option>
                </select>
            </td>
        </tr>
        <tr>
            <td>第二个参数:</td>
            <td><input type="text" name="secondNum"></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="提交">
            </td>
        </tr>
    </table>
</form>
</body>
</html>

效果:

只用了一个JSP页面和一个JavaBean就完成了一个简易计算器的开发。

模式一适合小型的开发,复杂程度低的开发,因为模式一的特点就是开发速度快,但在进行维护的时候就要付出更大的代价。

模式二

模式二中所有的开发都是以Servlet为主体展开的,由Servlet接收所有的客户端请求,然后根据请求调用相应的JavaBean,并所有的显示结果交给JSP完成,也就是俗称的MVC设计模式。

MVC设计模式

  • 显示层(View):主要负责接收Servlet传递的内容,调用JavaBean,将内容显示给用户。
  • 控制层(Controller):主要负责所有用户的请求参数,判断请求参数是否合法,根据请求的类型调用JavaBean,将最终的处理结果交给显示层显示。
  • 模型层(Model):模型层包括了业务层,DAO层。

应用实例:

使用MVC模式开发一个简单的用户注册登陆的案例,作为一个简单的用户登陆注册,直接使用XML文档当做小型数据库。

搭建开发环境

  • 导入相对应的开发包:(dom4j-1.6.1.jar和jaxen-1.2.0.jar)操作XML文件,servlet-api.jar 处理Servlet,(commons-beanutils-1.8.3.jar和commons-logging-api-1.1.jar)将表单数据填充到对象中,
  • 创建程序的包名。
  • 创建XML文件,当做小型的数据库。

开发实体User

public class User {
    private String id;
    private String username;
    private String password;
    private String email;
    private Date birthday;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                ", birthday=" + birthday +
                '}';
    }
}

开发Dao

注册(外界传递一个User对象进来,可以在XML文档中新增一条信息)。登陆(外界传递用户名和密码过来,就在XML文档中查找有没有改用户名和密码,如果有就返回一个User对象)。

定义一个接口

public interface UserDao {
    //登陆时调用该方法
    User find(String username, String password);
    //注册时调用该方法
    void register(User user);
}

实现上面的接口

public class UserImplDao implements UserDao {
    @Override
    public User find(String username, String password) {
        try {
            //得到XML文档的流对象
            InputStream inputStream = UserImplDao.class.getClassLoader().getResourceAsStream("user.xml");
            //得到dom4j的解析器对象
            SAXReader saxReader = new SAXReader();
            //解析XML文档
            Document read = saxReader.read(inputStream);
            //使用XPATH技术,查找XML文档中是否有传递进来的username和password
            Element element = (Element) read.selectSingleNode("//user[@username='" + username + "'and@password='"+password+"']");
            if (element == null) {
                return null;
            }
            //如果有,就把XML查出来的的节点信息封装到User对象,返回出去
            User user = new User();  
            user.setId(element.attributeValue("id"));
            user.setUsername(element.attributeValue("username"));
            user.setPassword(element.attributeValue("password"));
            user.setEmail(element.attributeValue("email"));
            //生日需要转换一下,XML文档保存的是字符串,User对象需要的是Date类型
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            Date birthday = simpleDateFormat.parse(element.attributeValue("birthday"));
            user.setBirthday(birthday);
            //返回User对象
            return user;
        } catch (DocumentException e) {
            e.printStackTrace();
            throw new RuntimeException("初始化时候出错了");
        } catch (ParseException e) {
            e.printStackTrace();
            throw new RuntimeException("查询的时候出错了");
        }
        return null;
    }

    @Override
    public void register(User user) {
        try {
            //获取XML文档路径
            String path = UserImplDao.class.getClassLoader().getResource("user.xml").getPath();
            //获取dom4j的解析器,解析XML文档
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(path);
            //在XML文档中创建新的节点
            Element userElement = DocumentHelper.createElement("user");
            userElement.addAttribute("id", String.valueOf(user.getId()));
            userElement.addAttribute("username", user.getUsername());
            userElement.addAttribute("password", user.getPassword());
            userElement.addAttribute("emial", user.getEmail());
            //日期返回的是指定格式的日期
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String date = simpleDateFormat.format(user.getBirthday());
            userElement.addAttribute("birthday", date);
            //把新创建的节点增加到父节点上
            document.getRootElement().add(userElement);
            //把XML内容中文档的内容写到硬盘文件上
            OutputFormat outputFormat = OutputFormat.createPrettyPrint();
            outputFormat.setEncoding("UTF-8");
            org.dom4j.io.XMLWriter xmlWriter = new org.dom4j.io.XMLWriter(new FileWriter(path), outputFormat);
            xmlWriter.write(document);
            xmlWriter.close();
            System.out.println("保存成功");
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

读取XML文件使用的类装载器的方法,在编译后,按照WEB的结构目录,XML文件的读写是在WEB-INF的classes目录下的。

开发service层

service层就是将多个原子性的DAO操作进行组合,组合成一个完整的业务逻辑。简单来说:对web层提供所有的业务服务的。

public class UserService {
    private UserImplDao userImplDao=new UserImplDao();

    public void register(User user){
        userImplDao.register(user);
    }

    public User find(String username,String password){
        User user=userImplDao.find(username,password);
        return user;
    }
}

开发web层

注册界面

开发注册界面的jsp:register.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>欢迎来到注册界面</h1>
${pageContext.request.contextPath}
<form action="${pageContext.request.contextPath}/RegisterServlet" method="post">
    <table>
        <tr>
            <td>用户名</td>
            <td>
                <input type="text" name="username"/>
            </td>
        </tr>
        <tr>
            <td>密码</td>
            <td>
                <input type="text" name="password"/>
            </td>
        </tr>
        <tr>
            <td>用户名</td>
            <td>
                <input type="text" name="pasword"/>
            </td>
        </tr>
        <tr>
            <td>邮箱</td>
            <td>
                <input type="text" name="email"/>
            </td>
        </tr>
        <tr>
            <td>生日</td>
            <td>
                <input type="text" name="birthday"/>
            </td>
        </tr>
        <tr>
            <td> <input type="submit" name="提交"/></td>
            <td>
                <input type="reset" name="重置"/>
            </td>
        </tr>
    </table>
</form>
</body>
</html>

效果:

开发处理jsp的servlet

    protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
        try {
            //获取页面参数
            String username = request.getParameter("username");
            String password=request.getParameter("password");
            String email=request.getParameter("email");
            String date=request.getParameter("birthday");
            //对生日参数进行处理转换为日期类型
            SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd");
            Date birthday = simpleDateFormat.parse(date);
            //将参数封装到User对象中
            User user=new User();
            String id=Generator.makeId();
            user.setId(id);
            user.setUsername(username);
            user.setPassword(password);
            user.setEmail(email);
            user.setBirthday(birthday);
            //注册操作
            UserService userService=new UserService();
            userService.register(user);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

使用反射机制中的BeanUtils开发包,为了更好地重用,将它写成一个工具类。

   /*
     *将Parameter参数的数据封装到Bean中,为了外边不用强制,这里就使用泛型了。
     *@request  由于要获取的是Parameter参数的信息,所以需要有request对象。
     *@tClass 本身是不知道封装什么对象的,所以用class
     */ 
   public static <T> T requestToBean(HttpServletRequest request, Class<T> tClass) {
        try {
            //创建tClass的对象
            T bean = tClass.newInstance();
            //日期转换器确保日期参数转换bean中对应日期属性
            ConvertUtils.register(new DateLocaleConverter(),Date.class);
/*            ConvertUtils.register(new Converter() {
                @Override
                public Object convert(Class aClass, Object o) {
                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
                    try {
                        return simpleDateFormat.parse(o.toString());
                    } catch (ParseException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    return null;
                }
            }, Date.class);*/
            //获取得到Parameter中全部的参数的名称
            Enumeration<String> parameterNames = request.getParameterNames();
            //遍历上面的集合
            while (parameterNames.hasMoreElements()) {
                //获取得到每一个带过来参数的名字
                String name = (String) parameterNames.nextElement();
                //获取参数对应的值
                String value = request.getParameter(name);
               //把数据封装到Bean对象中
                BeanUtils.setProperty(bean, name, value);
            }
            return bean;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

如果没有日期转换器,日期不能直接封装到Bean对象中,会直接报出异常。当BeanUtils的setProperty()方法检测到日期时,会自动调用日期转换器对日期进行转换,从而实现封装。

//日期转换器
ConvertUtils.register(new DateLocaleConverter(), Date.class);

还有一个问题,用户的id不是自己输入的,是由程序生成的。避免id的重复,就使用UUID生成用户的id,为了更好的重用,把它封装成一个方法。

public static String makeId(){
       return UUID.randomUUID().toString();
}

上面的代码是不够完善的(没有检验用户输入的信息、注册成功或失败都没有给出提示等等)

校验用户输入的信息,如果用户输入的信息不合法,那就直接跳转回注册的界面。之前,用BeanUtils把Parameter的信息全部封装到User对象中,但现在要验证用户提交表单的数据,也应该把表单的数据用一个对象保存着【面向对象的思想、封装、重用】。流程:当用户提交表单数据的时候,就把表单数据封装到我们设计的表单对象上,调用表单对象的方法,验证数据是否合法。

开发一个表单对象,定义一个validate()方法。

public class FormBean {
    private String username;
    private String password;
    private String password2;
    private String email;
    private String birthday;

    private HashMap<String,String> errors=new HashMap<>();

    public HashMap<String, String> getErrors() {
        return errors;
    }

    public void setErrors(HashMap<String, String> errors) {
        this.errors = errors;
    }
    //判断表单数据是否合法
    public boolean validate(){
        if(this.username==null||this.username.trim().equals("")){
            errors.put("username","用户名不能为空,且长度为3到8的字符");
            return false;
        }else{
            if(!this.username.matches("[a-zA-Z]{3,8}")){
                errors.put("username","用户名不能为空,且长度为3到8的字符");
                return false;
            }
        }

        if(this.password==null||this.password.trim().equals("")){
            errors.put("password", "密码不能为空,并且要是3-8的数字");
            return false;
        }else{
            if(!this.password.matches("\\d{3,8}")){
                errors.put("password", "密码不能为空,并且要是3-8的数字");
                return false;
            }
        }

        if(this.password2!=null&&!this.password2.equals(this.password)){
            errors.put("password2", "两次密码要一致");
            return false;
        }

        if(this.email!=null&&!this.email.trim().equals("")&&this.email.matches("\\w+@\\w+(\\.\\w+)+")){
            errors.put("email", "邮箱不合法!");
            System.out.println("邮箱格式错误");
            return false;
        }

        if(this.birthday!=null&&!this.birthday.trim().equals("")){
            try {
                DateLocaleConverter dateLocaleConverter=new DateLocaleConverter();
                dateLocaleConverter.convert(this.birthday);
            } catch (Exception e) {
                System.out.println("日期格式错误");
                errors.put("birthday", "日期不合法!");
                e.printStackTrace();
            }
        }

        return true;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword2() {
        return password2;
    }

    public void setPassword2(String password2) {
        this.password2 = password2;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getBirthday() {
        return birthday;
    }

    public void setBirthday(String birthday) {
        this.birthday = birthday;
    }
}

当提交的表单数据中,日期没有填写,就抛出错误。原因很简单:表单数据提交给Servlet,Servlet将表单的数据(Parameter中的数据)用BeanUtils封装到User对象中,当封装到日期的时候,发现日期为null,无法转化成日期对象。

首先,设定的时候,已经允许了email和birthday可以为空,那么在DAO层UserImplDao.java就应该有相应的逻辑判断email和birthday是否为空。

            if(user.getEmail()==null){
                userElement.addAttribute("email","");
            }else{
                userElement.addAttribute("email", user.getEmail());
            }
            if(user.getBirthday()!=null) {
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
                String date = simpleDateFormat.format(user.getBirthday());
                userElement.addAttribute("birthday", date);
            }else{
                userElement.addAttribute("birthday","");
            }

解决方法:BeanUtil.java  Parameter中的数据如果是"",就不把数据封装到User对象中,执行下一次循环。

if(value==""){
      continue;
}else {
      BeanUtils.setProperty(bean, name, value);
}

将数据封装到User对象中还有另外一个方法:

BeanUtils有个copyProperties()方法,可以将某个对象的成员数据拷贝到另外一个对象的成员变量数据上(前提是成员变量的名称相同),FormBean对象的成员变量名称和User对象的成员变量的名称是一致的。并且,前面在验证的时候,已经把parameter中带过来的数据封装到了FormBean对象中了,所以可以使用copyProperties()方法。

处理表单的Servlet完整代码如下:

        FormBean formBean = BeanUtil.requestToBean(request, FormBean.class);
        if(!formBean.validate()){
            request.setAttribute("formbean",formBean);
            request.getRequestDispatcher("Register.jsp").forward(request,response);
            return;
        }
        //方法一
        /*User user = new User();
            user.setId(WebUtils.makeId());
            BeanUtils.copyProperties(user,formBean);*/

        //方法二
        User user = BeanUtil.requestToBean(request, User.class);
        user.setId(Generator.makeId());
        UserService userService=new UserService();
        userService.register(user);
        response.setContentType("text/html;charset=UTF-8");
        response.getWriter().write("注册成功了");

当发现用户输入的信息不合法时,把错误的信息记录下来,等到返回注册页面,就提示用户哪里出错了。

前面写的代码,但页面出错,并没有一次性标记出来给用户。要改也十分简单:在验证的时候,用一个布尔变量标记,最后返回布尔变量即可。

   public boolean validate(){
        boolean flag=true;
        if(this.username==null||this.username.trim().equals("")){
            errors.put("username","用户名不能为空,且长度为3到8的字符");
            flag=false;
        }else{
            if(!this.username.matches("[a-zA-Z]{3,8}")){
                errors.put("username","用户名不能为空,且长度为3到8的字符");
                flag=false;
            }
        }

        if(this.password==null||this.password.trim().equals("")){
            errors.put("password", "密码不能为空,并且要是3-8的数字");
            flag=false;
        }else{
            if(!this.password.matches("\\d{3,8}")){
                errors.put("password", "密码不能为空,并且要是3-8的数字");
                flag=false;
            }
        }

        if(this.password2!=null&&!this.password2.equals(this.password)){
            errors.put("password2", "两次密码要一致");
            flag=false;
        }

        if(this.email!=null&&!this.email.trim().equals("")&&this.email.matches("\\w+@\\w+(\\.\\w+)+")){
            errors.put("email", "邮箱不合法!");
            System.out.println("邮箱格式错误");
            flag=false;
        }

        if(this.birthday!=null&&!this.birthday.trim().equals("")){
            try {
                DateLocaleConverter dateLocaleConverter=new DateLocaleConverter();
                dateLocaleConverter.convert(this.birthday);
            } catch (Exception e) {
                System.out.println("日期格式错误");
                errors.put("birthday", "日期不合法!");
                e.printStackTrace();
            }
        }

        return flag;
    }

登陆界面

Login.jsp

<h1>这是登陆界面</h1>
<form method="post" action="${pageContext.request.contextPath}/LoginServlet">
    <table>
        <tr>
            <td>用户名</td>
            <td>
                <input type="text" name="username"/>
            </td>
        </tr>
        <tr>
            <td>密码</td>
            <td>
                <input type="password" name="password"/>
            </td>
        </tr>
        <tr>
            <td>
                <input type="submit" name="登陆"/>
            </td>
            <td>
                <input type="reset" name="重置"/>
            </td>
        </tr>
    </table>
</form>

处理登陆表单的Servlet

        String username = request.getParameter("username");
        String password = request.getParameter("password");
        UserService userService = new UserService();
        User user = userService.find(username, password);
        response.setContentType("text/html;charset=UTF-8");
        if (user == null) {
            response.getWriter().write("登陆失败");
        } else {
            response.getWriter().write("登陆成功");
        }

把注册和登陆都放在首页

  <h1>这是首页!</h1>

  <a href="${pageContext.request.contextPath}/LoginUIServlet">登陆</a>
  <a href="${pageContext.request.contextPath}/RegisterUIServlet">注册</a>
  </body>

总结

  • 使用JSP+JavaBean开发一个简单计算器,是非常容易的,显示页面和请求都是交由JSP来做。
  • MVC模式开发使用Servlet来做处理请求,代码量略大,但层次清晰。
  • 使用BeanUtils开发组件可以将request请求的参数封装到JavaBean对象中,Date属性要另外处理。
  • 校验的功能也是使用一个JavaBean来完成,目的就是为了可重用性,职责分工。同时,可以在该JavaBean设置一个Map集合来保存错误的信息,以便在前台上展示错误的信息。

注:程式报错:m4j java.lang.NoClassDefFoundError: org/jaxen/JaxenException,使用dom4j解析XML时,要快速获取某个节点的数据,使用XPath是个不错的方法,dom4j的快速手册也建议使用这种方式。但会报出异常,因为不光要有dom4j包,还要有jaxen包。

 

 posted on 2019-06-17 22:03  会飞的金鱼  阅读(207)  评论(0)    收藏  举报