CS和BS CS:客户端服务器架构模式
BS∶浏览器服务器架构模式
优点︰客户端不需要安装;维护成本较低
缺点∶所有的计算和存储任务都是放在服务器端的,服务器的负荷较重∶在服务端计算完成之后把结果再传输给客户端,因此客户端和服务器端会进行非常频繁的数据通信,从而网络负荷较重
Tomcat 版本对应关系
Tomcat集成maven进行数据库操作时的500错误
记录一个折磨我6小时之久的异常。在IDEA中tomcat集成maven,相关依赖明明都已经导入,且这些包下的类也能正常使用,但部署到tomcat后却显示找不到这些包的类
解决办法:在WEB-INF下lib中引入maven中依赖的包(?为什么用了maven还是需要导包)
Servlet 什么是Servlet Servlet(Server Applet),全称Java Servlet,未有中文译文。是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。
Servlet运行于支持Java的应用服务器中。从实现上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。
Servlet的工作模式
客户端发送请求至服务器
服务器启动并调用Servlet,Servlet根据客户端请求生成响应内容并将其传给服务器
服务器将响应返回客户端
Servlet 的使用方法
Servlet技术的核心是Servlet,它是所有Servlet类必须直接或者间接实现的一个接口 。在编写实现Servlet的Servlet类时,直接实现它。在扩展实现这个这个接口的类时,间接实现它。
工作原理 Servlet接口定义了Servlet与servlet容器之间的契约。这个契约是:Servlet容器将Servlet类载入内存,并产生Servlet实例和调用它具体的方法。但是要注意的是,在一个应用程序中,每种Servlet类型只能有一个实例。
用户请求致使Servlet容器调用Servlet的Service()方法,并传入一个ServletRequest对象和一个ServletResponse对象。ServletRequest对象和ServletResponse对象都是由Servlet容器(例如TomCat)封装好的,并不需要程序员去实现,程序员可以直接使用这两个对象。
ServletRequest中封装了当前的Http请求,因此,开发人员不必解析和操作原始的Http数据。ServletResponse表示当前用户的Http响应,程序员只需直接操作ServletResponse对象就能把响应轻松的发回给用户。
对于每一个应用程序,Servlet容器还会创建一个ServletContext对象。这个对象中封装了上下文(应用程序)的环境详情。每个应用程序只有一个ServletContext。每个Servlet对象也都有一个封装Servlet配置的ServletConfig对象。
获取参数并发送给数据库 调用req.getParameter方法。
Servlet容器对于接受到的每一个Http请求,都会创建一个ServletRequest对象,并把这个对象传递给Servlet的Sevice( )方法。其中,ServletRequest对象内封装了关于这个请求的许多详细信息。
部分接口
public interface ServletRequest {
1 2 3 4 5 int getContentLength () ; String getContentType () ; String getParameter (String var1) ;
示例程序 AddServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.yumo.servlets;import com.yumo.fruit.dao.FruitDAO;import com.yumo.fruit.dao.Impl.FruitDAOImpl;import com.yumo.fruit.pojo.Fruit;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class AddServlet extends HttpServlet { @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8" ); String fname = req.getParameter("fname" ); String priceStr = req.getParameter("price" ); Integer price = Integer.parseInt(priceStr); String fcountStr = req.getParameter("fcount" ); Integer fcount=Integer.parseInt(fcountStr); String remark = req.getParameter("remark" ); System.out.println(fname); FruitDAO fruitDAO = new FruitDAOImpl (); boolean flag = fruitDAO.addFruit(new Fruit (10 , fname, price, fcount, remark)); System.out.println(flag?"添加成功" :"添加失败" ); } }
Fruit.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.yumo.fruit.pojo;import lombok.Data;@Data public class Fruit { private Integer fid; private String fname; private Integer price; private Integer fcount; private String remark; public Fruit (Integer fid, String fname, Integer price, Integer fcount, String remark) { this .fid = fid; this .fname = fname; this .price = price; this .fcount = fcount; this .remark = remark; } public Fruit () { } }
FruitDAO.java
1 2 3 4 5 6 7 package com.yumo.fruit.dao;import com.yumo.fruit.pojo.Fruit;public interface FruitDAO { boolean addFruit (Fruit fruit) ; }
FruitDAOImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package com.yumo.fruit.dao.Impl;import com.yumo.fruit.dao.FruitDAO;import com.yumo.fruit.pojo.Fruit;import com.yumo.fruit.utils.JDBCUtilByDruid;import org.apache.commons.dbutils.QueryRunner;import java.sql.Connection;import java.sql.SQLException;public class FruitDAOImpl implements FruitDAO { private QueryRunner qr=new QueryRunner (); @Override public boolean addFruit (Fruit fruit) { Connection connection=null ; Integer fid = fruit.getFid(); String fname = fruit.getFname(); Integer price = fruit.getPrice(); Integer fcount = fruit.getFcount(); String remark = fruit.getRemark(); try { String sql="INSERT INTO fruit VALUES(?,?,?,?,?)" ; connection= JDBCUtilByDruid.getConnection(); int update = qr.update(connection, sql,fid,fname,price,fcount,remark); return update != 0 ; } catch (SQLException e) { throw new RuntimeException (e); }finally { JDBCUtilByDruid.close(null ,null ,connection); } } public void test () { Connection connection=null ; Integer fid = 4 ; String fname = "abc" ; Integer price = 2 ; Integer fcount = 30 ; String remark = "OK" ; try { String sql="INSERT INTO fruit VALUES(?,?,?,?,?)" ; connection= JDBCUtilByDruid.getConnection(); int update = qr.update(connection, sql,fid,fname,price,fcount,remark); System.out.println(update>0 ?"成功" :"失败" ); } catch (SQLException e) { throw new RuntimeException (e); }finally { JDBCUtilByDruid.close(null ,null ,connection); } } }
JDBCUtilByDruid.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.yumo.fruit.utils;import com.alibaba.druid.pool.DruidDataSourceFactory;import javax.sql.DataSource;import java.io.FileInputStream;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import java.util.Properties;public class JDBCUtilByDruid { private static DataSource ds; static { Properties properties = new Properties (); try { properties.load(new FileInputStream ("E:\\WebJava\\src\\druid.properties" )); ds= DruidDataSourceFactory.createDataSource(properties); }catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection () throws SQLException { return ds.getConnection(); } public static void close (ResultSet resultSet, Statement statement,Connection connection) { try { if (resultSet!=null ){ resultSet.close(); } if (statement!=null ){ statement.close(); } if (connection!=null ){ connection.close(); } }catch (SQLException e) { throw new RuntimeException (e); } } }
设置编码 调用req.setCharacterEncoding方法。
tomcat8之前设置编码
tomcat8之后设置编码,只需要针对post方式
req.setCharacterEncoding(“UTF-8”);
Servlet的继承关系 HttpServlet -> GenericServlet -> Servlet
核心方法:
void init(ServletConfig var1) 初始化
void service (ServletRequest var1, ServletResponse var2) 服务
void destroy() 销毁
基本流程:
当有请求过来时,service方法会自动响应(tomcat容器调用)
在HttpServlet中去分析请求的方式是get、post、head还是delete等等,然后决定调用哪个do开头的方法
在HttpServlet中这些do方法默认都是405的风格-要我们子类去实现方法,否则报405错误
因此,我们在新建Servlet时才回去考虑请求方法,从而决定重写哪个do方法
源码分析 可见最顶层的Servlet接口定义了如下的方法
1 2 3 4 5 6 7 8 9 10 11 public interface Servlet { void init (ServletConfig var1) throws ServletException; ServletConfig getServletConfig () ; void service (ServletRequest var1, ServletResponse var2) throws ServletException, IOException; String getServletInfo () ; void destroy () ; }
并由其子类GenericServlet继承了其方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public abstract class GenericServlet implements Servlet , ServletConfig, Serializable {...... public void destroy () { } public void init (ServletConfig config) throws ServletException { this .config = config; this .init(); } public void init () throws ServletException { } public abstract void service (ServletRequest var1, ServletResponse var2) throws ServletException, IOException; }
其子类HttpServlet具体实现了service方法,并定义了一系列do方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public abstract class HttpServlet extends GenericServlet { protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); long lastModified; if (method.equals("GET" )) { lastModified = this .getLastModified(req); if (lastModified == -1L ) { this .doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader("If-Modified-Since" ); } catch (IllegalArgumentException var9) { ifModifiedSince = -1L ; } if (ifModifiedSince < lastModified / 1000L * 1000L ) { this .maybeSetLastModified(resp, lastModified); this .doGet(req, resp); } else { resp.setStatus(304 ); } } } else if (method.equals("HEAD" )) { lastModified = this .getLastModified(req); this .maybeSetLastModified(resp, lastModified); this .doHead(req, resp); } else if (method.equals("POST" )) { this .doPost(req, resp); } else if (method.equals("PUT" )) { this .doPut(req, resp); } else if (method.equals("DELETE" )) { this .doDelete(req, resp); } else if (method.equals("OPTIONS" )) { this .doOptions(req, resp); } else if (method.equals("TRACE" )) { this .doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented" ); Object[] errArgs = new Object []{method}; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501 , errMsg); } } }
该方法String method = req.getMethod();获取请求方法,接下来进行判断并执行其对应的do方法
例如doGet
1 2 3 4 protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String msg = lStrings.getString("http.method_get_not_supported" ); this .sendMethodNotAllowed(req, resp, msg); }
可见其方法默认报405错误,因此需要其子类去重写do方法。
Servlet的生命周期 生命周期 从从出生到死亡的过程。在这里即是实例化初始化,然后服务,最终销毁的过程
默认情况下,第一次接收请求时,这个servlet会实例化(tomcat底层通过反射调用)和初始化,然后再服务。
好处:提高系统的启动速度
缺点:第一次请求时耗时较长
因此:如果要提高系统的启动速度,就选择当前默认的情况。若要提高系统的响应速度,应该设置servlet的初始化时机。
由下面的示例程序可见,servlet是单例 的。
设置servlet的初始化时机 在web.xml中的对应< servlet >标签下,通过< load-on-startuo >来设置servlet启动的先后顺序,数字越小,启动越靠前,最小值为0.
Servlet是线程不安全的 因为Servlet是线程不安全的,所有尽量不要再servlet中定义成员变量。若不得不定义,则不要通过成员变量来进行逻辑判断。
注解 Servlet在3.0后支持注解:
从而可以把如下代码
1 2 3 4 5 6 7 8 9 <servlet > <servlet-name > IndexServlet</servlet-name > <servlet-class > com.yumo.servlets.IndexServlet</servlet-class > </servlet > <servlet-mapping > <servlet-name > IndexServlet</servlet-name > <url-pattern > /index</url-pattern > </servlet-mapping >
改为在IndexServlet类上添加注解
1 2 @WebServlet("/index") public class IndexServlet extends ViewBaseServlet {......}
示例程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.yumo.servlets;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class ServletDemo02 extends HttpServlet { public ServletDemo02 () { System.out.println("实例化..." ); } @Override public void init () throws ServletException { System.out.println("初始化..." ); } @Override protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("服务..." ); } @Override public void destroy () { System.out.println("销毁" ); } }
Http协议 Http称之为超文本传输协议 。
Http是无状态 的。
Http请求响应包含两个部分:请求和响应
会话 Http是无状态的:服务器无法判断这两次请求是同一个客户端发过来的,还是不同的客户端发过来的。
无状态带来的现实问题:第一次请求是添加商品到购物车,第二次请求是结账;如果这两次请求服务器无法区分是同一个用户的,那么就会导致混乱
可以通过会话跟踪技术来解决无状态问题
会话跟踪技术 客户端第一次发请求给服务器,服务器会获取session,如过获取不到,则创建新的,然后响应给客户端。
下次客户端给服务器发请求时,会把sessionId带给服务器,服务器从而获取到session,那么服务器就判断这一次请求和上次某次是同一个客户端,从而进行区分
常用API request.getSession()
获取当前的会话,没有则创建一个新的会话
request.getSession(true)
效果和不带参数相同
request.getSession(false)
获取当前会话,没有则返回null,不会创建新的
session.getId()
获取sessionId
session.isNew()
判断当前session是否是最新的
session.getCreationTime()
获取session的创建时间session.getLastAccessedTime()
获取session的最后一次激活时间
session.getMaxInactiveInterval()
获取session的非激活间隔时长,默认1800秒
session.setMaxInactiveInterval()
设置session的非激活间隔时长
session.invalidate()
强制让会话立即失效
session保存作用域 session保存作用域适合具体的某一个session对应的
常用API void session,setAttribute(k,v)
Object session.getAttribute(k)
void removeAttribute
服务器内部转发和客户端重定向 服务器内部转发 :req.getRequestDispatcher("......").forward(req,resp);
一次请求响应的过程,对于客户端而言,内部经过了多少次转发,客户端不知道。
浏览器上显示的URL不发生变化
客户端重定向 :resp.sendRedirect("......");
两次请求响应的过程,客户端知道请求URL发生变化。
浏览器上显示的URL发生变化。
Thymeleaf thymeleaf是一种视图模板技术
使用步骤 1、添加thymeleaf相关jar包依赖
2、新建一个Servlet类ViewBaseServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.yumo.servlets;import org.thymeleaf.TemplateEngine;import org.thymeleaf.context.WebContext;import org.thymeleaf.templatemode.TemplateMode;import org.thymeleaf.templateresolver.ServletContextTemplateResolver;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class ViewBaseServlet extends HttpServlet { private TemplateEngine templateEngine; @Override public void init () throws ServletException { ServletContext servletContext = this .getServletContext(); ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver (servletContext); templateResolver.setTemplateMode(TemplateMode.HTML); String viewPrefix = servletContext.getInitParameter("view-prefix" ); templateResolver.setPrefix(viewPrefix); String viewSuffix = servletContext.getInitParameter("view-suffix" ); templateResolver.setSuffix(viewSuffix); templateResolver.setCacheTTLMs(60000L ); templateResolver.setCacheable(true ); templateResolver.setCharacterEncoding("utf-8" ); templateEngine = new TemplateEngine (); templateEngine.setTemplateResolver(templateResolver); } protected void processTemplate (String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html;charset=UTF-8" ); WebContext webContext = new WebContext (req, resp, getServletContext()); templateEngine.process(templateName, webContext, resp.getWriter()); } }
3、在web.xml中添加配置
配置前缀 view-prefix
配置后缀 view-suffix
1 2 3 4 5 6 7 8 <context-param > <param-name > view-prefix</param-name > <param-value > /</param-value > </context-param > <context-param > <param-name > view-suffix</param-name > <param-value > .html</param-value > </context-param >
4、使IndexServlet继承ViewBaseServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.yumo.servlets;import com.yumo.fruit.dao.FruitDAO;import com.yumo.fruit.dao.Impl.FruitDAOImpl;import com.yumo.fruit.pojo.Fruit;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.IOException;import java.util.List;@WebServlet("/index") public class IndexServlet extends ViewBaseServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { FruitDAO fruitDAO=new FruitDAOImpl (); List<Fruit> fruitList = fruitDAO.getFruitList(); HttpSession session = req.getSession(); session.setAttribute("fruitList" ,fruitList); session.setAttribute("uusername" ,"root" ); super .processTemplate("index" ,req,resp); } }
5、根据逻辑视图名称得到物理视图名称
此处的视图名称是 index
那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图名称 上去
逻辑视图名称: index
物理视图名称: view-preffix + 逻辑视图名称 + view-suffix
真实视图名称: /index.html
super.processTemplate(“index”,req,resp);
常用标签:< th:if > < th:unless > < th:each > <th:text >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org" > <head > <meta charset ="UTF-8" > <title > Title</title > <link rel ="stylesheet" href ="css/index.css" > </head > <body > <div id ="div_container" > <div id ="div_fruit_list" > <p class ="center f30" > 欢迎使用水果库存后台管理系统</p > <table id ="tbl_fruit" > <tr > <th class ="w20" > 名称</th > <th class ="w20" > 单价</th > <th class ="w20" > 库存</th > <th class ="w20" > 操作</th > </tr > <tr th:if ="${#lists.isEmpty(session.fruitList)}" > <td colspan ="4" > 对不起,库存为空!</td > </tr > <tr th:unless ="${#lists.isEmpty(session.fruitList)}" th:each ="fruit : ${session.fruitList}" > <td th:text ="${fruit.fname}" > 西瓜</td > <td th:text ="${fruit.price}" > 3</td > <td th:text ="${fruit.fcount}" > 20</td > <td > <img src ="imgs/img.png" class ="delImg" > </tr > </table > </div > </div > </body > </html >
异常处理 记录一次异常处理的问题分析过程
情景:tomcat集成thymeleaf的水果仓库项目,但是在页面上总是获取不到session的值
可能原因:
1、数据库连接出错或druid连接池jar包与mysql版本不兼容
通过查看Maven的依赖关系排除版本兼容问题。
检查了druid连接池的properties文件,然后通过debug发现可以从数据库中取出数据,因此排除。
2、session跨域时的sessionid发生改变
通过Edge浏览器的网络检查得到请求的sessionid,然后再在所有途经的servlet类中打印当前请求的sessionid,发现一致,因此排除。
3、网页请求URL错误
通过配置比较,IndexSevlet在web.xml中注册的url-patter与tomcat启动时的URL一致
因此排除
4、thymeleaf本身代码出错
经过检查发现,表达式本身没有问题,但仍然取不到session里的值,于是我试着修改了一下命名空间,从thy改成了th。
居然就成功了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org" > <head > <meta charset ="UTF-8" > <title > Title</title > <link rel ="stylesheet" href ="css/index.css" > </head > <body > <div id ="div_container" > <div id ="div_fruit_list" > <p class ="center f30" > 欢迎使用水果库存后台管理系统</p > <table id ="tbl_fruit" > <tr > <th class ="w20" > 名称</th > <th class ="w20" > 单价</th > <th class ="w20" > 库存</th > <th class ="w20" > 操作</th > </tr > <tr th:if ="${#lists.isEmpty(session.fruitList)}" > <td colspan ="4" > 对不起,库存为空!</td > </tr > <tr th:unless ="${#lists.isEmpty(session.fruitList)}" th:each ="fruit : ${session.fruitList}" > <td th:text ="${fruit.fname}" > 西瓜</td > <td th:text ="${fruit.price}" > 3</td > <td th:text ="${fruit.fcount}" > 20</td > <td > <img src ="imgs/img.png" class ="delImg" > </tr > </table > </div > </div > </body > </html >
经过网上搜索发现,thymeleaf的命名空间是固定的。
1 2 3 4 <html lang ="en" xmlns:th ="http://www.thymeleaf.org" xmlns:sec ="http://www.thymeleaf.org/extras/spring-security" xmlns:shiro ="http://www.pollix.at/thymeleaf/shiro" >
其中对应关系如下
1、thymeleaf约束 xmlns:th=“http://www.thymeleaf.org” 2、security约束 xmlns:sec=“http://www.thymeleaf.org/extras/spring-security” 3、shiro约束 xmlns:shiro=“http://www.pollix.at/thymeleaf/shiro”
从此得出结论:这个困扰我半天的session获取不到值的异常是因为thymeleaf命名空间是固定的,不能乱写。
保存作用域 原始情况下,保存作用域我们可以认为有四个:
page 页面级别(现在几乎不用)
request 一次请求响应范围
服务器内部转发(一次请求),第二个Servlet组件可以获取在第一个组件中设置的值
客户端重定向(两次请求),第二个Servlet组件不能获取在第一个组件中设置的值
session 一次会话范围
session1范围内的组件二可以获取session1范围内组件以设置的值。
session2范围内的组件二无法获取session1范围内组件一设置的值,即在session1范围内设置的值只对客户端1可见。
application 整个应用程序范围
客户端二可通过组件二访问客户端一在组件一设置的值
路径问题 假设当前有如下路径
如果当前在login.html想访问login.css
1 <link rel ="stylesheet" href =" ../css/login.css" > </head >
1 <link rel ="stylesheet" href =" http://localhost:8080/WebJava/css/login.css " > </head >
base标签 当前页面上所有的路径都以此为基础
1 2 3 <base href ="http://localhost:8080/WebJava/" /> <link rel ="stylesheet" href ="css/index.css" > </head >
水果库存管理系统项目1.0 项目结构 一个请求对应一个servlet,缺点是servlet太多。
DAO BaseDAO抽象类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 package com.yumo.fruit.dao.base;import org.apache.commons.dbutils.QueryRunner;import org.apache.commons.dbutils.handlers.BeanHandler;import org.apache.commons.dbutils.handlers.BeanListHandler;import org.apache.commons.dbutils.handlers.ScalarHandler;import com.yumo.myssm.util.JDBCUtilByDruid;import java.sql.Connection;import java.sql.SQLException;import java.util.List;public abstract class BaseDAO <T> { private QueryRunner qr=new QueryRunner (); protected int update (String sql,Object... parameters) { Connection connection=null ; try { connection= JDBCUtilByDruid.getConnection(); int update = qr.update(connection, sql, parameters); return update; } catch (SQLException e) { throw new RuntimeException (e); }finally { JDBCUtilByDruid.close(null ,null ,connection); } } protected List<T> queryMulti (String sql, Class<T> clazz, Object... parameters) { Connection connection=null ; try { connection=JDBCUtilByDruid.getConnection(); return qr.query(connection,sql,new BeanListHandler <T>(clazz),parameters); } catch (SQLException e) { throw new RuntimeException (e); }finally { JDBCUtilByDruid.close(null ,null ,connection); } } protected T querySingle (String sql,Class<T> clazz,Object... parameters) { Connection connection=null ; try { connection=JDBCUtilByDruid.getConnection(); return qr.query(connection,sql,new BeanHandler <T>(clazz),parameters); } catch (SQLException e) { throw new RuntimeException (e); }finally { JDBCUtilByDruid.close(null ,null ,connection); } } protected Object queryScalar (String sql,Object... parameters) { Connection connection=null ; try { connection=JDBCUtilByDruid.getConnection(); return qr.query(connection,sql,new ScalarHandler <>(),parameters); } catch (SQLException e) { throw new RuntimeException (e); }finally { JDBCUtilByDruid.close(null ,null ,connection); } } }
pojo对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.yumo.fruit.pojo;import lombok.Data;@Data public class Fruit { private Integer fid; private String fname; private Integer price; private Integer fcount; private String remark; public Fruit (Integer fid, String fname, Integer price, Integer fcount, String remark) { this .fid = fid; this .fname = fname; this .price = price; this .fcount = fcount; this .remark = remark; } public Fruit () { } }
FruitDAO接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.yumo.fruit.dao;import com.yumo.fruit.pojo.Fruit;import java.util.List;public interface FruitDAO { boolean addFruit (Fruit fruit) ; List<Fruit> getFruitList (String keyword,Integer pageNum) ; Fruit getFruitByFid (Integer fid) ; boolean updateFruit (Fruit fruit) ; boolean deleteFruit (Integer fid) ; int getFruitCount (String keyword) ; }
FruitDAOImpl实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.yumo.fruit.dao.Impl;import com.yumo.fruit.dao.FruitDAO;import com.yumo.fruit.dao.base.BaseDAO;import com.yumo.fruit.pojo.Fruit;import org.apache.commons.dbutils.QueryRunner;import java.util.List;public class FruitDAOImpl extends BaseDAO <Fruit> implements FruitDAO { private QueryRunner qr=new QueryRunner (); @Override public boolean addFruit (Fruit fruit) { String sql="INSERT INTO fruit VALUES(null,?,?,?,?)" ; return super .update(sql ,fruit.getFname(), fruit.getPrice(), fruit.getFcount(), fruit.getRemark()) > 0 ; } @Override public List<Fruit> getFruitList (String keyword,Integer pageNum) { String sql = "SELECT * FROM fruit WHERE fname LIKE ? OR remark LIKE ? LIMIT ? , 5" ; List<Fruit> fruitList = super .queryMulti(sql, Fruit.class,"%" +keyword+"%" ,"%" +keyword+"%" ,(pageNum-1 )*5 ); return fruitList; } @Override public Fruit getFruitByFid (Integer fid) { String sql="SELECT * FROM fruit WHERE fid = ?" ; return super .querySingle(sql,Fruit.class,fid); } @Override public boolean updateFruit (Fruit fruit) { String sql="UPDATE fruit SET fname = ? , price = ? , fcount = ? , remark = ? WHERE fid = ?" ; return super .update(sql,fruit.getFname(),fruit.getPrice(),fruit.getFcount(),fruit.getRemark(),fruit.getFid())>0 ; } @Override public boolean deleteFruit (Integer fid) { String sql="DELETE FROM fruit WHERE fid = ?" ; return super .update(sql,fid)>0 ; } @Override public int getFruitCount (String keyword) { String sql="SELECT * FROM fruit WHERE fname LIKE ? OR remark LIKE ?" ; return super .queryMulti(sql,Fruit.class,"%" +keyword+"%" ,"%" +keyword+"%" ).size(); } }
Util工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.yumo.myssm.util;import com.alibaba.druid.pool.DruidDataSourceFactory;import javax.sql.DataSource;import java.io.FileInputStream;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import java.util.Properties;public class JDBCUtilByDruid { private static DataSource ds; static { Properties properties = new Properties (); try { properties.load(new FileInputStream ("E:\\WebJava\\src\\druid.properties" )); ds= DruidDataSourceFactory.createDataSource(properties); }catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection () throws SQLException { return ds.getConnection(); } public static void close (ResultSet resultSet, Statement statement,Connection connection) { try { if (resultSet!=null ){ resultSet.close(); } if (statement!=null ){ statement.close(); } if (connection!=null ){ connection.close(); } }catch (SQLException e) { throw new RuntimeException (e); } } }
1 2 3 4 5 6 7 8 9 10 11 12 package com.yumo.myssm.util;public class StringUtil { public static boolean isEmpty (String str) { return str==null ||"" .equals(str); } public static boolean isNotEmpty (String str) { return !isEmpty(str); } }
核心Servlet thymeleaf渲染 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.yumo.fruit.servlets;import org.thymeleaf.TemplateEngine;import org.thymeleaf.context.WebContext;import org.thymeleaf.templatemode.TemplateMode;import org.thymeleaf.templateresolver.ServletContextTemplateResolver;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class ViewBaseServlet extends HttpServlet { private TemplateEngine templateEngine; @Override public void init () throws ServletException { ServletContext servletContext = this .getServletContext(); ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver (servletContext); templateResolver.setTemplateMode(TemplateMode.HTML); String viewPrefix = servletContext.getInitParameter("view-prefix" ); templateResolver.setPrefix(viewPrefix); String viewSuffix = servletContext.getInitParameter("view-suffix" ); templateResolver.setSuffix(viewSuffix); templateResolver.setCacheTTLMs(60000L ); templateResolver.setCacheable(true ); templateResolver.setCharacterEncoding("utf-8" ); templateEngine = new TemplateEngine (); templateEngine.setTemplateResolver(templateResolver); } protected void processTemplate (String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html;charset=UTF-8" ); WebContext webContext = new WebContext (req, resp, getServletContext()); templateEngine.process(templateName, webContext, resp.getWriter()); } }
Index主页 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 package com.yumo.servlets;import com.yumo.fruit.dao.FruitDAO;import com.yumo.fruit.dao.Impl.FruitDAOImpl;import com.yumo.fruit.pojo.Fruit;import com.yumo.myssm.util.StringUtil;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.IOException;import java.util.List;@WebServlet("/index") public class IndexServlet extends ViewBaseServlet { private FruitDAO fruitDAO=new FruitDAOImpl (); @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8" ); HttpSession session = req.getSession(); Integer pageNum=1 ; String keyword=null ; String oper = req.getParameter("oper" ); if (StringUtil.isNotEmpty(oper) && "search" .equals(oper)) { pageNum=1 ; keyword=req.getParameter("keyword" ); if (StringUtil.isEmpty(keyword)){ keyword="" ; } session.setAttribute("keyword" ,keyword); }else { String pageNumStr = req.getParameter("pageNum" ); if (StringUtil.isNotEmpty(pageNumStr)) { pageNum=Integer.parseInt(pageNumStr); } Object keywordObj = session.getAttribute("keyword" ); if (keywordObj!=null ){ keyword=(String) keywordObj; }else { keyword="" ; } } session.setAttribute("pageNum" ,pageNum); List<Fruit> fruitList = fruitDAO.getFruitList(keyword,pageNum); ServletContext application = req.getServletContext(); session.setAttribute("fruitList" ,fruitList); int fruitCount = fruitDAO.getFruitCount(keyword); int pageCount=(fruitCount+5 -1 )/5 ; session.setAttribute("pageCount" ,pageCount); super .processTemplate("index" ,req,resp); } }
增加记录及其界面渲染 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.yumo.fruit.servlets;import com.yumo.fruit.dao.FruitDAO;import com.yumo.fruit.dao.Impl.FruitDAOImpl;import com.yumo.fruit.pojo.Fruit;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@WebServlet("/add.do") public class AddServlet extends ViewBaseServlet { FruitDAO fruitDAO=new FruitDAOImpl (); @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super .processTemplate("add" ,req,resp); } @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8" ); String fname = req.getParameter("fname" ); String priceStr = req.getParameter("price" ); Integer price = Integer.parseInt(priceStr); String fcountStr = req.getParameter("fcount" ); Integer fcount=Integer.parseInt(fcountStr); String remark = req.getParameter("remark" ); fruitDAO.addFruit(new Fruit (0 ,fname,price,fcount,remark)); resp.sendRedirect("index" ); } }
删除记录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.yumo.servlets;import com.yumo.fruit.dao.FruitDAO;import com.yumo.fruit.dao.Impl.FruitDAOImpl;import com.yumo.myssm.util.StringUtil;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@WebServlet("/del.do") public class DeleteServlet extends ViewBaseServlet { private FruitDAO fruitDAO=new FruitDAOImpl (); @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String fidStr = req.getParameter("fid" ); if (StringUtil.isNotEmpty(fidStr)) { Integer fid = Integer.parseInt(fidStr); fruitDAO.deleteFruit(fid); resp.sendRedirect("index" ); } } }
编辑界面渲染 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.yumo.servlets;import com.yumo.fruit.dao.FruitDAO;import com.yumo.fruit.dao.Impl.FruitDAOImpl;import com.yumo.fruit.pojo.Fruit;import com.yumo.myssm.util.StringUtil;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@WebServlet("/edit.do") public class EditServlet extends ViewBaseServlet { private FruitDAO fruitDAO=new FruitDAOImpl (); @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String fidStr = req.getParameter("fid" ); if (StringUtil.isNotEmpty(fidStr)) { int fid = Integer.parseInt(fidStr); Fruit fruit = fruitDAO.getFruitByFid(fid); req.setAttribute("fruit" ,fruit); super .processTemplate("edit" ,req,resp); } } }
修改记录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.yumo.fruit.servlets;import com.yumo.fruit.dao.FruitDAO;import com.yumo.fruit.dao.Impl.FruitDAOImpl;import com.yumo.fruit.pojo.Fruit;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@WebServlet("/update.do") public class UpdateServlet extends ViewBaseServlet { FruitDAO fruitDAO=new FruitDAOImpl (); @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8" ); String fidStr = req.getParameter("fid" ); Integer fid = Integer.parseInt(fidStr); String fname = req.getParameter("fname" ); String priceStr = req.getParameter("price" ); Integer price = Integer.parseInt(priceStr); String fcountStr = req.getParameter("fcount" ); Integer fcount=Integer.parseInt(fcountStr); String remark = req.getParameter("remark" ); fruitDAO.updateFruit(new Fruit (fid,fname,price,fcount,remark)); resp.sendRedirect("index" ); } }
MVC Servlet优化 水果库存管理系统项目1.1 项目结构 把一系列的请求都对应一个servlet,通过一个operate的值来决定调用FruitServlet中的哪一个方法,使用switch-case.
FruitServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 package com.yumo.fruit.servlets;import com.yumo.fruit.dao.FruitDAO;import com.yumo.fruit.dao.Impl.FruitDAOImpl;import com.yumo.fruit.pojo.Fruit;import com.yumo.myssm.util.StringUtil;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.IOException;import java.util.List;@WebServlet("/fruit.do") public class FruitServlet extends ViewBaseServlet { FruitDAO fruitDAO=new FruitDAOImpl (); @Override protected void service (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8" ); String operate = request.getParameter("operate" ); if (StringUtil.isEmpty(operate)){ operate="index" ; } switch (operate) { case "index" : index(request,response); break ; case "add" : add(request,response); break ; case "delete" : delete(request,response); break ; case "edit" : edit(request,response); break ; case "update" : update(request,response); break ; default : throw new RuntimeException ("operate值非法!" ); } } private void index (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8" ); HttpSession session = req.getSession(); Integer pageNum=1 ; String keyword=null ; String oper = req.getParameter("oper" ); if (StringUtil.isNotEmpty(oper) && "search" .equals(oper)) { pageNum=1 ; keyword=req.getParameter("keyword" ); if (StringUtil.isEmpty(keyword)){ keyword="" ; } session.setAttribute("keyword" ,keyword); }else { String pageNumStr = req.getParameter("pageNum" ); if (StringUtil.isNotEmpty(pageNumStr)) { pageNum=Integer.parseInt(pageNumStr); } Object keywordObj = session.getAttribute("keyword" ); if (keywordObj!=null ){ keyword=(String) keywordObj; }else { keyword="" ; } } session.setAttribute("pageNum" ,pageNum); List<Fruit> fruitList = fruitDAO.getFruitList(keyword,pageNum); session.setAttribute("fruitList" ,fruitList); ServletContext application = req.getServletContext(); int fruitCount = fruitDAO.getFruitCount(keyword); int pageCount=(fruitCount+5 -1 )/5 ; session.setAttribute("pageCount" ,pageCount); super .processTemplate("index" ,req,resp); } private void add (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if ("POST" .equals(method)) { req.setCharacterEncoding("UTF-8" ); String fname = req.getParameter("fname" ); String priceStr = req.getParameter("price" ); Integer price = Integer.parseInt(priceStr); String fcountStr = req.getParameter("fcount" ); Integer fcount = Integer.parseInt(fcountStr); String remark = req.getParameter("remark" ); fruitDAO.addFruit(new Fruit (0 , fname, price, fcount, remark)); resp.sendRedirect("fruit.do" ); }else { super .processTemplate("add" ,req,resp); } } private void delete (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String fidStr = req.getParameter("fid" ); if (StringUtil.isNotEmpty(fidStr)) { Integer fid = Integer.parseInt(fidStr); fruitDAO.deleteFruit(fid); resp.sendRedirect("fruit.do" ); } } private void edit (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String fidStr = req.getParameter("fid" ); if (StringUtil.isNotEmpty(fidStr)) { int fid = Integer.parseInt(fidStr); Fruit fruit = fruitDAO.getFruitByFid(fid); req.setAttribute("fruit" ,fruit); super .processTemplate("edit" ,req,resp); } } private void update (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8" ); String fidStr = req.getParameter("fid" ); Integer fid = Integer.parseInt(fidStr); String fname = req.getParameter("fname" ); String priceStr = req.getParameter("price" ); Integer price = Integer.parseInt(priceStr); String fcountStr = req.getParameter("fcount" ); Integer fcount=Integer.parseInt(fcountStr); String remark = req.getParameter("remark" ); fruitDAO.updateFruit(new Fruit (fid,fname,price,fcount,remark)); resp.sendRedirect("fruit.do" ); } }
优化switch-case->reflect 在上面的代码中,Servlet中充斥着大量的switch-case,随着项目的业务规模扩大,那么会有很多的Servlet,从而造成代码冗余
因此,在Servlet中使用反射技术。规定operate的值和方法名一致,那么接收到operate的值是什么就表明需要调用对应的方法进行响应。若找不到对应的方法,则抛出异常。
1 2 3 4 5 6 7 8 9 10 try { Method method = getClass().getDeclaredMethod(operate,HttpServletRequest request,HttpServletResponse response); if (method!=null ){ method.invoke(this ,request,response); return ; } }catch (Exception e){ e.printStackTrace(); } throw new RuntimeException ("没有找到" +operate+"的方法" );
向上提取 上面的代码使用了反射技术,但仍然存在问题:
每一个servlet中都有类似的反射技术的代码。因此继续抽取,设计了中央控制器类:DispatcherServlet。
该类的工作分为两部分:
1、根据url定位到能够处理这个请求的controller组件:
(1)从url中提取servletPath : /fruit.do -> fruit
1 2 3 4 5 6 String servletPath = request.getServletPath();servletPath=servletPath.substring(1 ,servletPath.lastIndexOf(".do" )); Object controllerBeanObj = beanMap.get(servletPath);
(2)根据fruit找到对应的组件:FruitController:
1 2 Object controllerBeanObj = beanMap.get(servletPath);
这个对应的依据存储在applicationContext.xml:
1 2 3 4 5 6 7 <?xml version="1.0" encoding="UTF-8" ?> <beans > <bean id ="fruit" class ="com.yumo.fruit.controllers.FruitController" /> </beans >
通过DOM技术去解析XML文件,在中央控制器形成一个beanMap容器,用来存放所有的Controller组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 try { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml" ); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse(inputStream); NodeList beanNodeList = document.getElementsByTagName("bean" ); for (int i=0 ;i<beanNodeList.getLength();i++) { Node beanNode = beanNodeList.item(i); if (beanNode.getNodeType()==Node.ELEMENT_NODE){ Element beanElement = (Element) beanNode; String beanId = beanElement.getAttribute("id" ); String className = beanElement.getAttribute("class" ); Object beanObj = null ; Constructor<?> declaredConstructor = Class.forName(className).getDeclaredConstructor(); declaredConstructor.setAccessible(true ); beanObj =declaredConstructor.newInstance(); beanMap.put(beanId,beanObj); } } } catch (ParserConfigurationException | IOException | SAXException | ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); }
(3)根据获取到的operate的值定位到FruitController中需要调用的方法
1 2 3 4 5 String operate = request.getParameter("operate" );if (StringUtil.isEmpty(operate)) { operate = "index" ; }
2、调用Controller组件中的方法
(1)获取参数
获取即将要调用的方法的参数签名信息:Parameter[] parameters = method.getParameters();
通过parameter.getNmae()获取参数的名称;
· 准备了Object[] parameterValues = new Object[parameters.length];这个数组来存放对应参数的参数值
另外还需要考虑参数的类型问题,需要做类型转化的工作。通过parameter.getType()获取参数的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 for (int i=0 ;i<parameters.length;i++){ Parameter parameter=parameters[i]; String parameterName = parameter.getName(); if ("request" .equals(parameterName)){ parameterValues[i]=request; }else if ("response" .equals(parameterName)){ parameterValues[i]=response; }else if ("session" .equals(parameterName)){ parameterValues[i]=request.getSession(); }else { String parameterValue = request.getParameter(parameterName); String typeName = parameter.getType().getName(); Object parameterObj=parameterValue; if (parameterObj!=null ){ if ("java.lang.Integer" .equals(typeName)) { parameterObj = Integer.parseInt(parameterValue); } } parameterValues[i]=parameterObj; } }
(2)执行方法
1 2 method.setAccessible(true ); Object returnObj = method.invoke(controllerBeanObj,parameterValues);
(3)视图处理
1 2 3 4 5 6 7 8 String methodReturnStr = (String) returnObj;if (methodReturnStr.startsWith("redirect:" )) { String redirectStr = methodReturnStr.substring("redirect:" .length()); response.sendRedirect(redirectStr); } else { super .processTemplate(methodReturnStr, request, response); }
水果库存管理系统项目1.2 项目结构
中央处理器Servlet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 package com.yumo.myssm.myspringmvc;import com.yumo.myssm.util.StringUtil;import org.w3c.dom.Document;import org.w3c.dom.Element;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import org.xml.sax.SAXException;import javax.servlet.ServletConfig;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.parsers.ParserConfigurationException;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.*;import java.util.HashMap;import java.util.Map;@WebServlet("*.do") public class DispacherServlet extends ViewBaseServlet { private Map<String,Object> beanMap= new HashMap <>(); @Override public void init () throws ServletException { super .init(); try { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml" ); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse(inputStream); NodeList beanNodeList = document.getElementsByTagName("bean" ); for (int i=0 ;i<beanNodeList.getLength();i++) { Node beanNode = beanNodeList.item(i); if (beanNode.getNodeType()==Node.ELEMENT_NODE){ Element beanElement = (Element) beanNode; String beanId = beanElement.getAttribute("id" ); String className = beanElement.getAttribute("class" ); Object beanObj = null ; Constructor<?> declaredConstructor = Class.forName(className).getDeclaredConstructor(); declaredConstructor.setAccessible(true ); beanObj =declaredConstructor.newInstance(); beanMap.put(beanId,beanObj); } } } catch (ParserConfigurationException | IOException | SAXException | ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } } @Override protected void service (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8" ); String servletPath = request.getServletPath(); servletPath=servletPath.substring(1 ,servletPath.lastIndexOf(".do" )); Object controllerBeanObj = beanMap.get(servletPath); String operate = request.getParameter("operate" ); if (StringUtil.isEmpty(operate)) { operate = "index" ; } try { for (Method method : controllerBeanObj.getClass().getDeclaredMethods()) { if (operate.equals(method.getName())) { Parameter[] parameters = method.getParameters(); Object[] parameterValues = new Object [parameters.length]; for (int i=0 ;i<parameters.length;i++){ Parameter parameter=parameters[i]; String parameterName = parameter.getName(); if ("request" .equals(parameterName)){ parameterValues[i]=request; }else if ("response" .equals(parameterName)){ parameterValues[i]=response; }else if ("session" .equals(parameterName)){ parameterValues[i]=request.getSession(); }else { String parameterValue = request.getParameter(parameterName); String typeName = parameter.getType().getName(); Object parameterObj=parameterValue; if (parameterObj!=null ){ if ("java.lang.Integer" .equals(typeName)) { parameterObj = Integer.parseInt(parameterValue); } } parameterValues[i]=parameterObj; } } method.setAccessible(true ); Object returnObj = method.invoke(controllerBeanObj,parameterValues); String methodReturnStr = (String) returnObj; if (methodReturnStr.startsWith("redirect:" )) { String redirectStr = methodReturnStr.substring("redirect:" .length()); response.sendRedirect(redirectStr); } else { super .processTemplate(methodReturnStr, request, response); } } } } catch (InvocationTargetException | IllegalAccessException e) { e.printStackTrace(); } } }
Controller层 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 package com.yumo.fruit.controllers;import com.yumo.fruit.dao.FruitDAO;import com.yumo.fruit.dao.Impl.FruitDAOImpl;import com.yumo.fruit.pojo.Fruit;import com.yumo.myssm.myspringmvc.ViewBaseServlet;import com.yumo.myssm.util.StringUtil;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.List;public class FruitController { FruitDAO fruitDAO=new FruitDAOImpl (); public FruitController () { } private String index (HttpServletRequest request,String oper,String keyword,Integer pageNum) throws IOException{ HttpSession session = request.getSession(); if (pageNum==null ){ pageNum=1 ; } if (StringUtil.isNotEmpty(oper) && "search" .equals(oper)) { pageNum=1 ; if (StringUtil.isEmpty(keyword)){ keyword="" ; } session.setAttribute("keyword" ,keyword); }else { Object keywordObj = session.getAttribute("keyword" ); if (keywordObj!=null ){ keyword=(String) keywordObj; }else { keyword="" ; } } session.setAttribute("pageNum" ,pageNum); List<Fruit> fruitList = fruitDAO.getFruitList(keyword,pageNum); session.setAttribute("fruitList" ,fruitList); ServletContext application = request.getServletContext(); int fruitCount = fruitDAO.getFruitCount(keyword); int pageCount=(fruitCount+5 -1 )/5 ; session.setAttribute("pageCount" ,pageCount); return "index" ; } private String add (HttpServletRequest request,Integer fid,String fname,Integer price,Integer fcount,String remark) throws IOException { String method = request.getMethod(); if ("POST" .equals(method)) { fruitDAO.addFruit(new Fruit (0 , fname, price, fcount, remark)); return "redirect:fruit.do" ; }else if ("GET" .equals(method)){ return "add" ; }else { return "error" ; } } private String delete (Integer fid) throws IOException { if (fid!=null ) { fruitDAO.deleteFruit(fid); return "redirect:fruit.do" ; }else { return "error" ; } } private String edit (HttpServletRequest request,Integer fid) throws IOException { if (fid!=null ) { Fruit fruit = fruitDAO.getFruitByFid(fid); request.setAttribute("fruit" ,fruit); return "edit" ; } return "error" ; } private String update (Integer fid,String fname,Integer price,Integer fcount,String remark) throws IOException { fruitDAO.updateFruit(new Fruit (fid,fname,price,fcount,remark)); return "redirect:fruit.do" ; } }
ServletAPI 初始化 1 2 3 4 5 6 7 public void init (ServletConfig config) throws ServletException { this .config = config; this .init(); } public void init () throws ServletException {}
如果想要在Servlet初始化时做一些准备工作,那么可以重写init()方法。
ServletConfig 可以通过如下步骤去获取初始化设置的数据:
获取config对象:
1 ServletConfig config = getServletConfig();
获取初始化参数值:
1 config.getInitParameter(key);
配置初始化参数:
1、通过XML文件的< init-param >标签配置
1 2 3 4 5 6 7 8 9 10 11 12 <servlet > <servlet-name > DispacherServlet</servlet-name > <servlet-class > com.yumo.myssm.myspringmvc.DispacherServlet</servlet-class > <init-param > <param-name > key</param-name > <param-value > value</param-value > </init-param > </servlet > <servlet-mapping > <servlet-name > DispacherServlet</servlet-name > <url-pattern > *.do</url-pattern > </servlet-mapping >
2、通过注解配置
1 2 3 4 5 6 @WebServlet(urlPatterns = {"*.do"}, initParams = { @WebInitParam(name = "key1",value = "value2"), @WebInitParam(name = "key2",value = "value2") } )
ServletContext ServletContext对象表示Servlet应用程序 。每个Web应用程序都只有一个ServletContext对象。在将一个应用程序同时部署到多个容器的分布式环境中,每台Java虚拟机上的Web应用都会有一个ServletContext对象。
通过在ServletConfig中调用getServletContext方法,也可以获得ServletContext对象。
那么为什么要存在一个ServletContext对象呢?存在肯定是有它的道理,因为有了ServletContext对象,就可以共享从应用程序中的所有资料处访问到的信息,并且可以动态注册Web对象。前者将对象保存在ServletContext中的一个内部Map中。保存在ServletContext中的对象被称作属性。
1、获取servletContext对象
1 ServletContext servletContext = getServletContext();
在服务方法中( service() ):可以通过request对象获取,也可以通过session对象获取
1 2 ServletContext requestServletContext = request.getServletContext();ServletContext servletContext = session.getServletContext();
2、获取context参数
1 servletContext.getInitParameter("key" )
1 2 3 4 <context-param > <param-name > key</param-name > <param-value > value</param-value > </context-param >
业务层 MVC MVC :Model (模型)、View (视图)、Controller (控制器)
视图层 :用于做数据展示以及和用户交互的一个界面。
控制层 :能够接受客户端的请求,具体的业务功能需要借助模型组件来完成。
模型层 :模型分为很多种,有比较简单的pojo/vo(value object)、业务模型组件、数据访问层对象
pojo/vo :值对象
DAO :数据访问对象
BO :业务对象
区分业务对象和数据访问对象:
DAO中的方法都是单精度 (细粒度)方法。即一个方法只考虑一个操作。
BO中的方法属于业务方法,实际的业务时比较复杂的,因此业务方法是粗粒度 方法。
例子:注册这个功能属于业务功能,也就是说注册这个方法属于业务方法:
那么这个业务方法中包含了多个DAO方法。也就是说注册这个业务功能需要通过多个DAO方法的组合调用,从而完成注册功能的实现。
注册:
检查用户名是否已经被注册 - DAO中的select方法
向用户表新增一条新用户记录 - DAO中的insert方法
向系统日志表新增一条记录 - DAO中的insert方法
. . . . . .
在库存系统中添加业务层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.yumo.fruit.service;import com.yumo.fruit.pojo.Fruit;import java.util.List;public interface FruitService { List<Fruit> getFruitList (String keyword, Integer pageNum) ; boolean addFruit (Fruit fruit) ; Fruit getFruitByFid (Integer fid) ; boolean updateFruit (Fruit fruit) ; boolean deleteFruit (Integer fid) ; int getPageCount (String keyword) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.yumo.fruit.service.impl;import com.yumo.fruit.service.FruitService;import com.yumo.fruit.dao.FruitDAO;import com.yumo.fruit.dao.Impl.FruitDAOImpl;import com.yumo.fruit.pojo.Fruit;import java.util.List;public class FruitServiceImpl implements FruitService { private FruitDAO fruitDAO=new FruitDAOImpl (); @Override public List<Fruit> getFruitList (String keyword, Integer pageNum) { return fruitDAO.getFruitList(keyword, pageNum); } @Override public boolean addFruit (Fruit fruit) { return fruitDAO.addFruit(fruit); } @Override public Fruit getFruitByFid (Integer fid) { return fruitDAO.getFruitByFid(fid); } @Override public boolean updateFruit (Fruit fruit) { return fruitDAO.updateFruit(fruit); } @Override public boolean deleteFruit (Integer fid) { return fruitDAO.deleteFruit(fid); } @Override public int getPageCount (String keyword) { int count = fruitDAO.getFruitCount(keyword); int pageCount=(count+5 -1 )/5 ; return pageCount; } }
然后把Controller中的dao对象改成service对象。
IOC
IOC-控制反转 :
1、之前在controller中,创建service对象,FruitService fruitService=new FruitServiceImpl();
这句话如果出现在controller的某个方法内部,那么这个fruitService的作用域(生命周期)应该是这个方法级别;
如果这句话出现在controller的类中,也就是说fruitService是一个成员变量,那么这个fruitService的生命周期就是这个controller的类级别。
2、之后再applicationContext.xml中定义了这个fruitService,然后通过解析XML,产生fruitService实例,存放在beanMap中,这个beanMap称之为IOC容器,是存放在BeanFactory类中。
因此,我们转移(改变)了之前的service实例、dao实例等等它们的生命周期。控制权从程序员转移到BeanFactory。这个现象我们称之为控制反转。
DI-依赖注入 :
1、之前在controller层出现代码 FruitService fruitService=new FruitServiceImpl();
此依赖关系是我们程序员主动绑定的。那么,controller层就和service层存在耦合 。
2、之后,将代码改为 FruitService fruitService=null;
然后再配置文件中配置
1 2 3 <bean id ="fruit" class ="com.yumo.fruit.controllers.FruitController" > <property name ="fruitService" ref ="fruitService" /> </bean >
现在是通过反射技术,IOC容器寻找到ref属性对应的service实例,并注入到对应的Controller实例中
BeanFactory
1 2 3 4 5 6 7 package com.yumo.myssm.ioc;public interface BeanFactory { Object getBean (String id) ; }
BeanFactory的实现类
将DispacherServlet类的init()方法内解析XML文件获取所有的Controller组件,并存放到IOC容器的相关代码提取到BeanFactory的实现类中。各个JavaBean组件(Controller、Service、DAO组件)由BeanFactory进行同一控制和管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 package com.yumo.myssm.ioc;import org.w3c.dom.Document;import org.w3c.dom.Element;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import org.xml.sax.SAXException;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.parsers.ParserConfigurationException;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Map;public class ClassPathXmlApplicationContext implements BeanFactory { private Map<String,Object> beanMap= new HashMap <>(); public ClassPathXmlApplicationContext () { try { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml" ); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse(inputStream); NodeList beanNodeList = document.getElementsByTagName("bean" ); for (int i=0 ;i<beanNodeList.getLength();i++) { Node beanNode = beanNodeList.item(i); if (beanNode.getNodeType()==Node.ELEMENT_NODE){ Element beanElement = (Element) beanNode; String beanId = beanElement.getAttribute("id" ); String className = beanElement.getAttribute("class" ); Object beanObj = null ; Class<?> beanClass = Class.forName(className); beanObj=beanClass.newInstance(); beanMap.put(beanId,beanObj); } } for (int i=0 ;i<beanNodeList.getLength();i++) { Node beanNode = beanNodeList.item(i); if (beanNode.getNodeType()==Node.ELEMENT_NODE){ Element beanElement = (Element) beanNode; String beanId = beanElement.getAttribute("id" ); NodeList beanChildNodeList = beanElement.getChildNodes(); for (int j = 0 ; j < beanChildNodeList.getLength(); j++) { Node beanChildNode = beanChildNodeList.item(j); if (beanChildNode.getNodeType()==Node.ELEMENT_NODE&&"property" .equals(beanChildNode.getNodeName())){ Element propertyElement = (Element) beanChildNode; String propertyName = propertyElement.getAttribute("name" ); String propertyRef = propertyElement.getAttribute("ref" ); Object refObj = beanMap.get(propertyRef); Object beanObj = beanMap.get(beanId); Class<?> beanClazz = beanObj.getClass(); Field propertyField = beanClazz.getDeclaredField(propertyRef); propertyField.setAccessible(true ); propertyField.set(beanObj,refObj); } } } } } catch (ParserConfigurationException | IOException | SAXException | ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchFieldException e) { e.printStackTrace(); } } @Override public Object getBean (String id) { return beanMap.get(id); } }
然后再在DispacherServlet中定义BeanFactory实例。之前获取组件是直接从beanMap中得到:
beanMap.get();
而现在由BeanFactory进行统一管理:
beanFactory.getBean();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 package com.yumo.myssm.myspringmvc;import com.alibaba.druid.util.StringUtils;import com.yumo.myssm.ioc.BeanFactory;import com.yumo.myssm.ioc.ClassPathXmlApplicationContext;import com.yumo.myssm.util.StringUtil;import org.w3c.dom.Document;import org.w3c.dom.Element;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import org.xml.sax.SAXException;import javax.servlet.ServletConfig;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.annotation.WebInitParam;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.parsers.ParserConfigurationException;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.*;import java.util.HashMap;import java.util.Map;@WebServlet(urlPatterns = {"*.do"}) public class DispacherServlet extends ViewBaseServlet { private BeanFactory beanFactory; @Override public void init () throws ServletException { super .init(); beanFactory=new ClassPathXmlApplicationContext (); } @Override protected void service (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8" ); String servletPath = request.getServletPath(); servletPath=servletPath.substring(1 ,servletPath.lastIndexOf(".do" )); Object controllerBeanObj = beanFactory.getBean(servletPath); String operate = request.getParameter("operate" ); if (StringUtil.isEmpty(operate)) { operate = "index" ; } try { for (Method method : controllerBeanObj.getClass().getDeclaredMethods()) { if (operate.equals(method.getName())) { Parameter[] parameters = method.getParameters(); Object[] parameterValues = new Object [parameters.length]; for (int i=0 ;i<parameters.length;i++){ Parameter parameter=parameters[i]; String parameterName = parameter.getName(); if ("request" .equals(parameterName)){ parameterValues[i]=request; }else if ("response" .equals(parameterName)){ parameterValues[i]=response; }else if ("session" .equals(parameterName)){ parameterValues[i]=request.getSession(); }else { String parameterValue = request.getParameter(parameterName); String typeName = parameter.getType().getName(); Object parameterObj=parameterValue; if (parameterObj!=null ){ if ("java.lang.Integer" .equals(typeName)) { parameterObj = Integer.parseInt(parameterValue); } } parameterValues[i]=parameterObj; } } method.setAccessible(true ); Object returnObj = method.invoke(controllerBeanObj,parameterValues); String methodReturnStr = (String) returnObj; if (methodReturnStr.startsWith("redirect:" )) { String redirectStr = methodReturnStr.substring("redirect:" .length()); response.sendRedirect(redirectStr); } else { super .processTemplate(methodReturnStr, request, response); } } } } catch (InvocationTargetException | IllegalAccessException e) { e.printStackTrace(); } } }
XML配置文件
配置各个组件之间的依赖关系,以让BeanFactory进行依赖注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="UTF-8" ?> <!--一个JavaBean称为一个Java对象--> <beans> <bean id="fruitDAO" class="com.yumo.fruit.dao.Impl.FruitDAOImpl" /> <bean id="fruitService" class="com.yumo.fruit.service.impl.FruitServiceImpl" > <!--property标签用来表示属性:name表示属性名;ref表示引用其他bean的id值--> <property name="fruitDAO" ref="fruitDAO" /> </bean> <!-- 这个bean标签的作用是 将来servletPath中涉及的名字对应的是fruit,那么就要FruitController这个类来处理--> <bean id="fruit" class="com.yumo.fruit.controllers.FruitController" > <property name="fruitService" ref="fruitService" /> </bean> </beans>
过滤器Filter 过滤器的三要素:
拦截
过滤器之所以能够对请求进行预处理,关键是对请求进行拦截,把请求拦截下来才能做后续的操作。而且对于一个具体的过滤器,它必须明确它要拦截的请求,而不是所有请求都拦截。
过滤
根据业务功能实际的需求,看把请求拦截之后需要做上面检查或操作,写对应的代码。
放行
过滤器完成自己的任务或者是检测当前请求符合过滤规则,那么可以将请求放行。所谓放行,就是让请求继续去访问它原本要访问的资源。
Filter也属于Servlet规范
开发步骤:
新建类实现Filter接口
实现其中的三个方法:init、doFilter、destroy
配置Filter
过滤器链FilterChain
如果采取注解方式进行配置,那么过滤器链的拦截顺序是按照全类名的先后顺序排序的。
如果采取XML的方式进行配置,那么按照配置的先后顺序进行排序。
示例程序 拦截所有以.do结尾的请求,并设置其字符编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.yumo.myssm.filter;import com.yumo.myssm.util.StringUtil;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.annotation.WebInitParam;import javax.servlet.http.HttpServletRequest;import java.io.IOException;@WebFilter(urlPatterns = "*.do",initParams = {@WebInitParam(name = "encoding",value = "UTF-8")}) public class CharacterEncodingFilter implements Filter { private String encoding="UTF-8" ; @Override public void init (FilterConfig filterConfig) throws ServletException { String encodingStr = filterConfig.getInitParameter("encoding" ); if (StringUtil.isNotEmpty(encodingStr)){ encoding=encodingStr; } } @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ((HttpServletRequest)servletRequest).setCharacterEncoding("UTF-8" ); filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy () { Filter.super .destroy(); } }
事务管理 在上面的代码中,Service层包含多个DAO操作。假如在DAO层执行事务处理,则可能出现一个事务成功提交,而另一个事务回滚,从而造成数据异常。
service的操作应该是一个整体,不能部分成功部分失败。
因此:事务管理不能以DAO层的单精度方法为单位,而应该以业务层的方法为单位。
应该从最底层的DAO开始,抛出RuntimeException异常,中间层捕获后继续抛出,直至最上层的Filter捕获后执行事务回滚。
可以定义一个TranscationManager来执行事务管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package com.yumo.fruit.transcation;import com.yumo.myssm.util.JDBCUtilByDruid;import java.sql.SQLException;public class TranscationManager { public static void beginTrans () { try { JDBCUtilByDruid.getConnection().setAutoCommit(false ); } catch (Exception e) { throw new RuntimeException (e); } } public static void commit () { try { JDBCUtilByDruid.getConnection().commit(); JDBCUtilByDruid.close(); } catch (Exception e) { throw new RuntimeException (e); } } public static void rollback () { try { JDBCUtilByDruid.getConnection().rollback(); JDBCUtilByDruid.close(); } catch (Exception e) { throw new RuntimeException (e); } } }
在最顶层的Filter中捕获异常并回滚
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.yumo.myssm.filter;import com.yumo.fruit.transcation.TranscationManager;import com.yumo.myssm.util.JDBCUtilByDruid;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import java.io.IOException;@WebFilter("*.do") public class OpenSessionInViewFilter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { try { Filter.super .init(filterConfig); }catch (Exception e){ throw new RuntimeException (e); } } @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) { try { TranscationManager.beginTrans(); System.out.println("开启事务......" ); filterChain.doFilter(servletRequest,servletResponse); TranscationManager.commit(); System.out.println("提交事务......" ); } catch (Exception e) { TranscationManager.rollback(); System.out.println("回滚事务......" ); throw new RuntimeException (e); } } @Override public void destroy () { try { Filter.super .destroy(); }catch (Exception e){ throw new RuntimeException (e); } } }
同时记得逐层修改为捕获后抛出运行时异常。
ThreadLocal ThreadLocal称之为本地线程。可以通过set方法在当前线程上存储数据,通过get方法在当前线程上获取数据。
源码分析 1 2 3 4 5 6 7 8 9 public void set (T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) { map.set(this , value); } else { createMap(t, value); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 public T get () { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) { ThreadLocalMap.Entry e = map.getEntry(this ); if (e != null ) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
监听器Listener
ServletContextListener 监听ServletContext对象的创建和销毁的过程
HttpSessionListener 监听HttpSession对象的创建和销毁的过程
ServletRequestListener 监听ServletRequest对象的创建和销毁的过程
ServletContextAttributeListener 监听ServletContext的保存作用域的改动(add,remove,replace)
HttpSessionAttributeListener 监听HttpSession的保存作用域的改动(add,remove,replace)
ServletRequestAttributeListener 监听ServletRequest的保存作用域的改动(add,remove,replace)
HttpSessionBindingListener 监听某个对象在session作用域的绑定和移除
HttpSessionActivationListener 监听某个对象在session作用域的序列化和反序列化
配置Listener
注解 @WebListener
XML
1 2 3 <listener > <listener-class > com.yumo.myssm.listener.MyServletContextListener</listener-class > </listener >
优化 在DispatcherServlet类的初始化方法中,我们会实例化BeanFactory对象,从而创建IOC容器。
1 2 3 4 5 6 7 8 9 10 11 12 @WebServlet(urlPatterns = {"*.do"}) public class DispatcherServlet extends ViewBaseServlet { private BeanFactory beanFactory; @Override public void init () throws ServletException { super .init(); beanFactory=new ClassPathXmlApplicationContext (); } ...... }
但我们希望在tomcat启动的同时就创建好IOC容器。因此创建一个ServletContextListener的实现类
监听上下文启动,在上下文启动的时候创建IOC容器,然后将其保存到servletContext作用域中
后面中央控制器再从servletContext作用域中去获取IOC容器
先在web.xml创建servletContext的初始化参数以设置IOC容器的配置文件
1 2 3 4 <context-param > <param-name > contextConfigLocation</param-name > <param-value > applicationContext.xml</param-value > </context-param >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.yumo.myssm.listener;import com.yumo.myssm.ioc.BeanFactory;import com.yumo.myssm.ioc.ClassPathXmlApplicationContext;import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;import javax.servlet.annotation.WebListener;@WebListener public class ContextLoaderListener implements ServletContextListener { @Override public void contextInitialized (ServletContextEvent sce) { ServletContext servletContext = sce.getServletContext(); String path = servletContext.getInitParameter("contextConfigLocation" ); BeanFactory beanFactory = new ClassPathXmlApplicationContext (path); servletContext.setAttribute("beanFactory" ,beanFactory); } @Override public void contextDestroyed (ServletContextEvent sce) { ServletContextListener.super .contextDestroyed(sce); } }
中央控制器DispatcherServlet再从servletContext作用域中去获取IOC容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @WebServlet(urlPatterns = {"*.do"}) public class DispatcherServlet extends ViewBaseServlet { private BeanFactory beanFactory=null ; @Override public void init () throws ServletException { super .init(); ServletContext servletContext = getServletContext(); Object beanFactoryObj = servletContext.getAttribute("beanFactory" ); if (beanFactoryObj!=null ){ beanFactory=(BeanFactory) beanFactoryObj; }else { throw new RuntimeException ("IOC容器获取失败" ); } } ...... }
然后再BeanFactory实现类中增加无参构造
1 2 3 4 5 6 7 8 9 10 11 public class ClassPathXmlApplicationContext implements BeanFactory { private Map<String,Object> beanMap= new HashMap <>(); private String path="applicationContext.xml" ; public ClassPathXmlApplicationContext () { this ("applicationContext.xml" ); } ... }
至此,水果库存管理系统项目已经初步完善并优化。