JavaEE开发-第六节 JavaEE项目实战(二)

面向接口编程

  1. 当前的项目目录如下:

     bean
         BaseBean
         Education
         Website
     dao
         EducationDao
         WebsiteDao
     service
         EducationService
         WebsiteService
     servlet
         BaseServlet
         EducationServlet
         WebsiteServlet
    
  2. 在dao、service文件夹下分别新建文件夹impl
  3. 将所有的dao跟service的类名修改后面全部添加Impl,然后全部拖拽到相应的Impl文件夹中
    1. 比如BaseDao修改为WebsiteDaoImpl
  4. 在dao跟service文件夹下新建每个类的接口,右击new-JavaClass(选择Interface)
    1. 比如在dao下新建BaseDao这个接口
     dao
         impl
             EducationDaoImpl
             WebsiteDaoImpl
         EducationDao
         WebsiteDao
     service
         impl
             EducationServiceImpl
             WebsiteServiceImpl
         EducationService
         WebsiteService
    
  5. 将Impl类中的方法都抽取到对应的接口中
    1. EducationDao与EducationService一样

       public interface EducationDao {
           boolean remove(Integer id);
           boolean remove(List<Integer> ids);
           boolean save(Education education);
           Education get(Integer id);
           List<Education> list();
           int count();
       }
      
    2. WebsiteDao与WebsiteService一样

       public interface WebsiteDao {
           boolean remove(Integer id);
           boolean remove(List<Integer> ids);
           boolean save(Website education);
           Website get(Integer id);
           List<Website> list();
           int count();
       }
      
  6. Impl类都添加接口实现

     public class EducationDaoImpl implements EducationDao {}
     public class WebsiteDaoImpl implements WebsiteDao {}
     public class EducationServiceImpl implements EducationService {}
     public class WebsiteServiceImpl implements WebsiteService {}
    
  7. 目前的对象拥有连接是:Servlet拥有Service对象,Service对象拥有Dao对象,现在分别将Servlet与ServiceImpl对象中的拥有修改成接口引用

     Servlet拥有Service接口
     private EducationService service = new EducationServiceImpl();
     private WebsiteService service = new WebsiteServiceImpl();
        
     ServiceImpl对象拥有Dao接口
     private EducationDao dao = new EducationDaoImpl();
     private WebsiteDao dao = new WebsiteDaoImpl();
    

dao层抽取

  1. 接口抽取
    1. 在dao下新建一个BaseDao,抽取代码如下

       package com.zh.xr.dao;
       import java.util.List;
              
       public interface BaseDao<T> {
           boolean remove(Integer id);
           boolean remove(List<Integer> ids);
           boolean save(T bean);
           T get(Integer id);
           List<T> list();
           int count();
       }
      
    2. EducationDao跟WebsiteDao如下:

       public interface EducationDao extends BaseDao<Education>{}
       public interface WebsiteDao extends BaseDao<Website> {}
      
  2. Impl抽取
    1. 在dao文件夹下新建一个BaseDaoImpl,将Impl中的count、remove方法抽取出来

       public abstract class BaseDaoImpl<T> implements BaseDao<T> {
           private String table = table();
           //要求子类实现,告知表名
           protected abstract String table();
              
           public boolean remove(Integer id) {
               String sql = "DELETE FROM" + table + " WHERE id = ? ";
               return Dbs.getTpl().update(sql,id) >0 ;
           }
              
           public boolean remove(List<Integer> ids) {
               List<Object> args = new ArrayList<>();
               StringBuilder sql = new StringBuilder();
               sql.append("DELETE FROM ").append(table).append(" WHERE id in (");
               for (Integer id: ids) {
                   args.add(id);
                   sql.append("?, ");
               }
               sql.replace(sql.length()-2,sql.length(),")");
              
               return Dbs.getTpl().update(sql.toString(),args.toArray()) >0 ;
           }
           public int count() {
               String sql = "SELECT COUNT(*) FROM " + table;
               return Dbs.getTpl().queryForObject(sql, new BeanPropertyRowMapper<>(Integer.class));
           }
       }
      
    2. EducationDaoImpl

       public class EducationDaoImpl extends BaseDaoImpl<Education> implements EducationDao {
      
           @Override
           protected String table() {
               return "education";
           }
           public boolean save(Education education) {
               Integer id = education.getId();
               List<Object> args = new ArrayList<>();
               args.add(education.getName());
               args.add(education.getType());
               args.add(education.getIntro());
               args.add(education.getBeginDay());
               args.add(education.getEndDay());
               String sql = "";
               if(id == null || id <1){
                   sql = "INSERT INTO education(name,type,intro,begin_day,end_day) VALUES(?, ?, ?, ?, ?)";
               }else {
                   sql = "UPDATE education SET name = ?, type = ?, intro = ?, begin_day = ?, end_day = ? WHERE id = ?";
                   args.add(id);
               }
               return Dbs.getTpl().update(sql,args.toArray()) >0 ;
           }
              
           public Education get(Integer id) {
               String sql = "SELECT id, created_time, name, type, intro, begin_day, end_day FROM education WHERE id = ?";
               return Dbs.getTpl().queryForObject(sql,new BeanPropertyRowMapper<>(Education.class),id);
           }
           public List<Education> list() {
               String sql = "SELECT id, created_time, name, type, intro, begin_day, end_day FROM education";
               return Dbs.getTpl().query(sql,new BeanPropertyRowMapper<>(Education.class));
           }
       }
      
    3. WebsiteDaoImpl

       public class WebsiteDaoImpl extends BaseDaoImpl<Website> implements WebsiteDao {
           @Override
           protected String table() {
               return "website";
           }
           public boolean save(Website website) {
               Integer id = website.getId();
               List<Object> args = new ArrayList<>();
               args.add(website.getFooter());
               String sql = "";
               if(id == null || id <1){
                   sql = "INSERT INTO website(footer) VALUES(?)";
               }else {
                   sql = "UPDATE website SET footer = ? WHERE id = ?";
                   args.add(id);
               }
               return Dbs.getTpl().update(sql,args.toArray()) >0 ;
           }
           public Website get(Integer id) {
               return null;
           }
           
           public List<Website> list() {
               String sql = "SELECT id, created_time, footer FROM website";
               return Dbs.getTpl().query(sql,new BeanPropertyRowMapper<>(Website.class));
           }
       }
      

service抽取

  1. 接口抽取
    1. 同理抽取service,新建一个BaseService,然后抽取

       public interface BaseService<T>{
           boolean remove(Integer id);
           boolean remove(List<Integer> ids);
           boolean save(T bean);
           T get(Integer id);
           List<T> list();
           int count();
       }
      
    2. EducationService/WebsiteService

       public interface WebsiteService extends BaseService<Website> {}
       public interface EducationService extends BaseService<Education>{}
      
  2. Impl抽取
    1. 新建BaseServiceImpl

       public abstract class BaseServiceImpl<T> implements BaseService<T> {
           private BaseDao<T> dao = dao();
           protected abstract  BaseDao<T> dao();
           public boolean remove(Integer id) {
               return dao.remove(id);
           }
           public boolean remove(List<Integer> ids) {
               return dao.remove(ids);
           }
           public boolean save(T bean) {
               return dao.save(bean) ;
           }
           public T get(Integer id) {
               return dao.get(id);
           }
           public List<T> list() {
               return dao.list();
           }
           public int count() {
               return dao.count();
           }
       }
      
    2. EducationServiceImpl、WebsiteServiceImpl

       public class WebsiteServiceImpl extends BaseServiceImpl<Website> implements WebsiteService {
           @Override
           protected BaseDao<Website> dao() {
               return new WebsiteDaoImpl();
           }
       }
       public class EducationServiceImpl extends BaseServiceImpl<Education> implements EducationService {
           @Override
           protected BaseDao<Education> dao() {
               return new EducationDaoImpl();
           }
       }
      

专业技能

  1. 参照教育经验,依次新建bean(skill)->Dao(SkillDao、SkillDaoImpl)->Service(SkillService/SkillServiceImpl)->Servlet(SkillServlet)->skill.jsp即可完成

自定义错误页面

404页面定义

  1. 当用户访问了一个不存在的页面(IP+端口对的,但是页面不存在),会报404,通常这个页面不够友好,如何自定义呢?
  2. 在项目的WEB-INF下的web.xml下配置

     <!-- 配置404页面 -->
     <error-page>
         <error-code>404</error-code>
         <location>/WEB-INF/page/404.jsp</location>
     </error-page>
    
     <!-- 配置500页面 -->
     <error-page>
         <error-code>500</error-code>
         <location>/WEB-INF/page/500.jsp</location>
     </error-page>
    
  3. 一旦访问结果为404,就跳转到404.jsp,比如:http://localhost:8080/xr/skill.jsp

500页面定义

  1. 什么情况服务器会抛出500呢? 当代码的异常直接抛给Tomcat时,就会报500异常

     //将BaseServlet中的doget方法中的异常直接抛向外抛出
     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         try {
            ...
         } catch (Exception e) {
             e.printStackTrace();
             //异常直接向外抛出
             throw new ServletException("方法找不到");
         }
     }
    
    1. 浏览器访问:http://localhost:8080/xr/skill/adminxxxx就会转发到500.jsp页面

错误路径访问页面定义

  1. 上面讲过500是程序代码出现异常直接抛出给了Tomcat才出现500,但是如果异常没有抛出去呢?我们直接拦截了呢?此时如输错误路径,整个页面会显示空白
  2. 解决办法通过内部转发到自定义的错误页面

     //将BaseServlet中的doget方法中的异常不向外抛出
     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         try {
            ...
         } catch (Exception e) {
             e.printStackTrace();
             //异常不向外抛出,转发
             //目的是找到最终引起故障的原因
             Throwable cause = e;
             while (cause.getCause() != null){
                 cause = cause.getCause();
             }
             request.setAttribute("error",cause.getClass().getName());
             request.getRequestDispatcher("/WEB-INF/page/error.jsp").forward(request,response);
         }
     }
    

图片上传功能

  1. 图片如何存储到服务器?
    1. 服务器将客户端发过来的图片数据直接写入到服务器的硬盘中,然后将图片路径地址存储到数据库中,并不是将图片数据直接存储到数据库中

获奖成就模块

  1. 参照教育经验,依次新建bean(Award)->Dao(AwardDao、AwardDaoImpl)->Service(AwardService/AwardServiceImpl)->Servlet(AwardServlet)->award.jsp即可完成

form文件上传-前端

  1. 如果要支持文件上传,必须设置2个属性

     method="post"
     enctype="multipart/form-data"
    
  2. 一旦form设置了这一项,Java后台就无法通过request.getParameter获取参数

文件上传-后台

  1. Java后台中常使用commons-fileupload来接收客户端上传的文件
  2. pom.xml中添加

     <dependency>
         <groupId>commons-fileupload</groupId>
         <artifactId>commons-fileupload</artifactId>
         <version>1.4</version>
     </dependency>
    
  3. AwardServlet中的save方法如下:

     public void save(HttpServletRequest request, HttpServletResponse response) throws Exception {
         DiskFileItemFactory factory = new DiskFileItemFactory();
         ServletFileUpload upload = new ServletFileUpload(factory);
         //解决中文文件名乱码问题
         upload.setHeaderEncoding("UTF-8");
         //一个FileItem代表一个请求参数,包括文件参数、非文件参数。
         List<FileItem> items = upload.parseRequest(request);
         //存储非文件参数
         Map<String,Object> params = new HashMap<>();
         //存储到数据库的文件路径
         String imagurl = null;
         for (FileItem item : items) {
             //参数名称
             String fieldName = item.getFieldName();
             //参数值
             String value = item.getString();
             if (item.isFormField()){//非文件参数
                 System.out.println(fieldName + "_" + item.getString("UTF-8"));
                 params.put(item.getFieldName(),item.getString("UTF-8"));
             }else {//是文件参数
                 System.out.println(fieldName + "_" + item.getName());
                 //文件名称
                 //System.out.println(item.getName());
                 //参数名称
                 //System.out.println(item.getFieldName());
                 //具体数据
                 //System.out.println(item.getString());
                 //将文件数据写到硬盘中
                 //1. 拿到当前项目在磁盘上的绝对路径(打包后的项目路径)+自定义文件存放路径
                 String dir = request.getServletContext().getRealPath("upload/img");
                 //2. 设置文件名(不能直接使用客户端直接传递过来的文件名,防止冲突覆盖)
                 //随机码+扩展名
                 String fileName = UUID.randomUUID() + "." + FilenameUtils.getExtension(item.getName());
                 //3.最终文件路径
                 imagurl = "upload/img" +"/" + fileName;
                 String filepath = dir +"/" + fileName;
                 System.out.println(filepath);
                 //4. 将文件写入到路径中
                 //使用原生解析
                 //FileOutputStream outputStream = new FileOutputStream(new File(filepath));
                 //读取客户端发送过来的文件数据
                 //InputStream Ins = item.getInputStream();
                 //byte[] buffer = new byte[4096];
                 //int len;
                 //while ((len = Ins.read(buffer)) != -1){
                 //    outputStream.write(buffer,0,len);
                 //}
                 //Ins.close();
                 //outputStream.close();
                 //使用第三方框架接收
                 FileUtils.copyInputStreamToFile(item.getInputStream(),new File(filepath));
             }
         }
         //5. 将文件路径写入到数据库中
         //注意:客户端访问图片时,域名+固定路径,而不是上面的filepath,因此存入到数据库的仅仅是上面的`upload/img/xxxxx.png`
         //imageUrl
         Award award = new Award();
         //正常参数
         BeanUtils.populate(award, params);
         //图片路径参数
         //因为编辑的时候,如果没有重新上传图片,那就不需要再次走文件上传了
         if(award.getImage() == null){
             award.setImage(imagurl);
         }
         if (service.save(award)) {//保存成功
             //重定向到admin
             response.sendRedirect(request.getContextPath() + "/award/admin");
         }else {//保存失败
             request.setAttribute("error","获奖成就保存失败");
             request.getRequestDispatcher("/WEB-INF/page/error.jsp").forward(request,response);
         }
     }
    
  4. save精简后的代码

     public void save(HttpServletRequest request, HttpServletResponse response) throws Exception {
         DiskFileItemFactory factory = new DiskFileItemFactory();
         ServletFileUpload upload = new ServletFileUpload(factory);
         //解决中文文件名乱码问题
         upload.setHeaderEncoding("UTF-8");
         //一个FileItem代表一个请求参数,包括文件参数、非文件参数。
         List<FileItem> items = upload.parseRequest(request);
         //存储非文件参数
         Map<String,Object> params = new HashMap<>();
         //存储文件数据
         Map<String,FileItem> fileParams = new HashMap<>();
         //存储到数据库的文件路径
         for (FileItem item : items) {
             //参数名称
             String fieldName = item.getFieldName();
             if (item.isFormField()){//非文件参数
                 params.put(fieldName,item.getString("UTF-8"));
             }else {//是文件参数
                 fileParams.put(fieldName,item);
             }
         }
         Award award = new Award();
         BeanUtils.populate(award, params);
         if (award.getImage() != null && award.getImage().length() == 0){
             award.setImage(null);
         }
         FileItem item = fileParams.get("imageFile");
         if(item != null){
             InputStream is = item.getInputStream();
             if (is.available() >0 ){
                 //将文件数据写到硬盘中
                 String fileName = UUID.randomUUID() + "." + FilenameUtils.getExtension(item.getName());
                 //3.最终文件路
                 String image  = "upload/img" +"/" + fileName;
                 String filepath = request.getServletContext().getRealPath(image);
                 System.out.println(filepath);
                 //4. 将文件写入到路径中
                 //使用第三方框架接收
                 FileUtils.copyInputStreamToFile(item.getInputStream(),new File(filepath));
                 award.setImage(image);
             }
         }
         if (service.save(award)) {//保存成功
             //重定向到admin
             response.sendRedirect(request.getContextPath() + "/award/admin");
         }else {//保存失败
             request.setAttribute("error","获奖成就保存失败");
             request.getRequestDispatcher("/WEB-INF/page/error.jsp").forward(request,response);
         }
     }
    

文件上传功能封装

  1. 很多模块都会用到上传功能,那么就需要将其封装起来
  2. 在util文件夹下新建一个工具类Uploads
  3. 代码举例:

     public class Uploads {
         private static final String BASE_DIR = "upload";
         private static final String IMG_DIR = "img";
        
         /**
          * 图片上传
          * @param item 文件参数
          * @param request 请求
          * @param oldImage 以前的图片路径
          * @return 存储到数据库的图片路径
          * @throws Exception
          */
         public static String uploadImage(FileItem item, HttpServletRequest request, String oldImage) throws Exception {
             // 如果oldImage是空字符串,就设置为null
             if (oldImage != null && oldImage.length() == 0) {
                 oldImage = null;
             }
        
             if (item == null) return oldImage;
             InputStream is = item.getInputStream();
             if (is.available() == 0) return oldImage;
        
             ServletContext ctx = request.getServletContext();
        
             String filename = UUID.randomUUID() + "." + FilenameUtils.getExtension(item.getName());
             String image = BASE_DIR + "/" + IMG_DIR + "/" + filename;
             String filepath = ctx.getRealPath(image);
             FileUtils.copyInputStreamToFile(item.getInputStream(), new File(filepath));
        
             // 删除旧的文件,防止旧图片累积
             if (oldImage != null) {
                 // 如果oldImage是空串,那么就会把整个web项目的文件夹给删掉
                 FileUtils.deleteQuietly(new File(ctx.getRealPath(oldImage)));
             }
        
             return image;
         }
     }
    
  4. AwardServlet中save代码

     public void save(HttpServletRequest request, HttpServletResponse response) throws Exception {
         DiskFileItemFactory factory = new DiskFileItemFactory();
         ServletFileUpload upload = new ServletFileUpload(factory);
         //解决中文文件名乱码问题
         upload.setHeaderEncoding("UTF-8");
         //一个FileItem代表一个请求参数,包括文件参数、非文件参数。
         List<FileItem> items = upload.parseRequest(request);
         //存储非文件参数
         Map<String,Object> params = new HashMap<>();
         //存储文件数据
         Map<String,FileItem> fileParams = new HashMap<>();
         //存储到数据库的文件路径
         for (FileItem item : items) {
             //参数名称
             String fieldName = item.getFieldName();
             if (item.isFormField()){//非文件参数
                 params.put(fieldName,item.getString("UTF-8"));
             }else {//是文件参数
                 fileParams.put(fieldName,item);
             }
         }
         //5. 将文件路径写入到数据库中
         //注意:客户端访问图片时,域名+固定路径,而不是上面的filepath,因此存入到数据库的仅仅是上面的`upload/img/xxxxx.png`
         //imageUrl
         Award award = new Award();
         BeanUtils.populate(award, params);
         FileItem item = fileParams.get("imageFile");
         award.setImage(Uploads.uploadImage(item,request,award.getImage()));
         if (service.save(award)) {//保存成功
             //重定向到admin
             response.sendRedirect(request.getContextPath() + "/award/admin");
         }else {//保存失败
             request.setAttribute("error","获奖成就保存失败");
             request.getRequestDispatcher("/WEB-INF/page/error.jsp").forward(request,response);
         }
     }
    

问题分析

  1. 刚上传完毕的图片,有可能会出现无法实时预览的问题(要等一会再次刷新才能预览成功)
  2. 把Tomcat的缓存资源功能关掉即可,在%TOMCAT_HOME%/conf/context.xml中增加Resources标签

     //在Context标签中添加
     <Resources
       cachingAllowed="false"
       cacheMaxSize="0"
     />
    

Servlet抽取

  1. 将各个servlet中的转发与重定向重复代码抽取到BaseServlet中
  2. 封装3个函数,共其他servlet调用

     redirect
     forward
     forwardError
    
Table of Contents