JavaEE开发-第六节 JavaEE项目实战(二)
面向接口编程
-
当前的项目目录如下:
bean BaseBean Education Website dao EducationDao WebsiteDao service EducationService WebsiteService servlet BaseServlet EducationServlet WebsiteServlet
- 在dao、service文件夹下分别新建文件夹impl
- 将所有的dao跟service的类名修改后面全部添加Impl,然后全部拖拽到相应的Impl文件夹中
- 比如BaseDao修改为WebsiteDaoImpl
- 在dao跟service文件夹下新建每个类的接口,右击new-JavaClass(选择Interface)
- 比如在dao下新建BaseDao这个接口
dao impl EducationDaoImpl WebsiteDaoImpl EducationDao WebsiteDao service impl EducationServiceImpl WebsiteServiceImpl EducationService WebsiteService
- 将Impl类中的方法都抽取到对应的接口中
-
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(); }
-
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(); }
-
-
Impl类都添加接口实现
public class EducationDaoImpl implements EducationDao {} public class WebsiteDaoImpl implements WebsiteDao {} public class EducationServiceImpl implements EducationService {} public class WebsiteServiceImpl implements WebsiteService {}
-
目前的对象拥有连接是: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层抽取
- 接口抽取
-
在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(); }
-
EducationDao跟WebsiteDao如下:
public interface EducationDao extends BaseDao<Education>{} public interface WebsiteDao extends BaseDao<Website> {}
-
- Impl抽取
-
在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)); } }
-
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)); } }
-
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抽取
- 接口抽取
-
同理抽取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(); }
-
EducationService/WebsiteService
public interface WebsiteService extends BaseService<Website> {} public interface EducationService extends BaseService<Education>{}
-
- Impl抽取
-
新建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(); } }
-
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(); } }
-
专业技能
- 参照教育经验,依次新建bean(skill)->Dao(SkillDao、SkillDaoImpl)->Service(SkillService/SkillServiceImpl)->Servlet(SkillServlet)->skill.jsp即可完成
自定义错误页面
404页面定义
- 当用户访问了一个不存在的页面(IP+端口对的,但是页面不存在),会报404,通常这个页面不够友好,如何自定义呢?
-
在项目的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>
- 一旦访问结果为404,就跳转到404.jsp,比如:
http://localhost:8080/xr/skill.jsp
500页面定义
-
什么情况服务器会抛出500呢? 当代码的异常直接抛给Tomcat时,就会报500异常
//将BaseServlet中的doget方法中的异常直接抛向外抛出 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { ... } catch (Exception e) { e.printStackTrace(); //异常直接向外抛出 throw new ServletException("方法找不到"); } }
- 浏览器访问:
http://localhost:8080/xr/skill/adminxxxx就会转发到500.jsp
页面
- 浏览器访问:
错误路径访问页面定义
- 上面讲过500是程序代码出现异常直接抛出给了Tomcat才出现500,但是如果异常没有抛出去呢?我们直接拦截了呢?此时如输错误路径,整个页面会显示空白
-
解决办法通过内部转发到自定义的错误页面
//将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); } }
图片上传功能
- 图片如何存储到服务器?
- 服务器将客户端发过来的图片数据直接写入到服务器的硬盘中,然后将图片路径地址存储到数据库中,并不是将图片数据直接存储到数据库中
获奖成就模块
- 参照教育经验,依次新建bean(Award)->Dao(AwardDao、AwardDaoImpl)->Service(AwardService/AwardServiceImpl)->Servlet(AwardServlet)->award.jsp即可完成
form文件上传-前端
-
如果要支持文件上传,必须设置2个属性
method="post" enctype="multipart/form-data"
-
一旦form设置了这一项,Java后台就无法通过request.getParameter获取参数
文件上传-后台
- Java后台中常使用
commons-fileupload
来接收客户端上传的文件 -
pom.xml中添加
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>
-
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); } }
-
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); } }
文件上传功能封装
- 很多模块都会用到上传功能,那么就需要将其封装起来
- 在util文件夹下新建一个工具类Uploads
-
代码举例:
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; } }
-
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); } }
问题分析
- 刚上传完毕的图片,有可能会出现无法实时预览的问题(要等一会再次刷新才能预览成功)
-
把Tomcat的缓存资源功能关掉即可,在%TOMCAT_HOME%/conf/context.xml中增加Resources标签
//在Context标签中添加 <Resources cachingAllowed="false" cacheMaxSize="0" />
Servlet抽取
- 将各个servlet中的转发与重定向重复代码抽取到BaseServlet中
-
封装3个函数,共其他servlet调用
redirect forward forwardError