Java Web项目1:水果管理系统
核心参考资料:
项目架构体系
单一架构技术体系
- 视图(V):用户的操作界面+数据的动态显示
- 前端技术:HTML/CSS/JavaScript
- 服务器端页面模板技术:Thymeleaf
- 控制层(C):处理请求+跳转页面
- 服务器:Tomcat
- 控制器:Servlet
- 域对象:request、session、servletContext
- 过滤器:Filter
- 监听器:Listener
- 业务逻辑层(M):业务逻辑计算
- 持久化层:操作数据库(DAO语句)
视图设计
- 结构:由HTML实现,负责管理网页的内容。将来网页上不管是静态还是动态的数据都是填写到HTML的标签里。
- 表现:由CSS实现,负责管理网页内容的表现形式。比如:颜色、尺寸、位置、层级等等。也就是给数据穿上一身漂亮的衣服。
- 行为:由JavaScript实现,负责实现网页的动态交互效果。比如:轮播图、表单验证、鼠标滑过显示下拉菜单、鼠标滑过改变背景颜色等等。
HTML各标签的具体用法可参照:HTML标签
这里强调一下服务器访问地址(绝对路径)的概念:
CSS语法:由选择器和声明组成,CSS样式由选择器和声明组成,而声明又由属性和值组成。
Java Script的具体语法可参考:JSP基础语法
控制器
Tomcat
配置文件
XML文件(eXtensible Markup Language)——可拓展标记语言。
properties文件(web.xml)
Tomcat的作用
对外是连接客户端与服务端的Web服务器,对内则是能够处理请求的Servlet容器。
Servlet类
Servlet类的功能
-
获取来自客户端的数据
请求-响应执行顺序
1. 用户发送请求,action = add; 1. 在web.xml找到url-pattern中对应的映射 1. 找到对应的servlet类,由对应的该类获取请求
- 调用DAO完成对数据库的操作
使用对应的dopost()或者doget方法来调用DAO方法完成对于数据库的操作
Servlet处理乱码问题
处理http request中的中文乱码
method中默认为get方法,另一种为post方法(表单),这个请求根据http协议采取不同的内容。
tomcat8:
1)get请求不需要设置编码
2)Post请求需要设置编码
request.setCharacterEncoding("UTF-8");
Servlet中的Service方法
Service方法:
当服务器收到http request的时候,自动调用service方法
相关方法
javax.servlet.Servlet接口:
void init(config) - 初始化方法
void service(request,response) - 服务方法
void destory() - 销毁方法
javax.servlet.GenericServlet抽象类:
void service(request,response) - 仍然是抽象的
javax.servlet.http.HttpServlet 抽象子类:
void service(request,response) - 不是抽象的
1. String method = req.getMethod(); 获取请求的方式
2. 各种if判断,根据请求方式不同,决定去调用不同的do方法
if (method.equals("GET")) {
this.doGet(req,resp);
} else if (method.equals("HEAD")) {
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
3. 在HttpServlet这个抽象类中,do方法都差不多:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
service()方法执行流程:
当有请求过来时,service方法会自动响应(其实是tomcat容器调用的) 在HttpServlet中我们会去分析请求的方式:到底是get、post、head还是delete等等 然后再决定调用的是哪个do开头的方法
Servlet的生命周期:
1) 生命周期:从出生到死亡的过程就是生命周期。
对应Servlet中的三个方法:init(),service(),destroy()
2) 默认情况下:
第一次接收请求时,这个Servlet会进行实例化(调用构造方法)、初始化(调用init())、然后服务(调用service())
从第二次请求开始,每一次都是服务
当容器关闭时,其中的所有的servlet实例会被销毁,调用销毁方法
3) 通过案例我们发现:
- Servlet实例tomcat只会创建一个,所有的请求都是这个实例去响应。
- 默认情况下,第一次请求时,tomcat才会去实例化,初始化,然后再服务.这样的好处是什么? 提高系统的启动速度 。 这样的缺点是什么? 第一次请求时,耗时较长。
- 因此得出结论: 如果需要提高系统的启动速度,当前默认情况就是这样。如果需要提高响应速度,我们应该设置Servlet的初始化时机。
Servlet的线程安全性:
Servlet在容器中是:单例的、线程不安全的
- 单例:所有的请求都是同一个实例去响应
- 线程不安全:一个线程需要根据这个实例中的某个成员变量值去做逻辑判断。但是在中间某个时机,另一个线程改变了这个成员变量的值,从而导致第一个线程的执行路径发生了变化
- 我们已经知道了servlet是线程不安全的,给我们的启发是: 尽量的不要在servlet中定义成员变量。如果不得不定义成员变量,那么不要去:①不要去修改成员变量的值 ②不要去根据成员变量的值做一些逻辑判断。
HTTP协议
Http 称之为 超文本传输协议
1) Http是无状态的
2) Http请求响应包含两个部分:请求和响应
- 请求:
请求包含三个部分: 1.请求行 ; 2.请求消息头 ; 3.请求主体
1)请求行包含是三个信息: 1. 请求的方式 ; 2.请求的URL ; 3.请求的协议(一般都是HTTP1.1)
2)请求消息头中包含了很多客户端需要告诉服务器的信息,比如:我的浏览器型号、版本、我能接收的内容的类型、我给你发的内容的类型、内容的长度等等
3)请求体,三种情况
get方式,没有请求体,但是有一个queryString,比如?nation=USA&name=jim(就是URL后面那一串字符串)
post方式,有请求体,form data
json格式,有请求体,request payload - 响应:
响应也包含三本: 1. 响应行 ; 2.响应头 ; 3.响应体
1)响应行包含三个信息:1.协议 2.响应状态码(200) 3.响应状态(ok)
2)响应头:包含了服务器的信息;服务器发送给浏览器的信息(内容的媒体类型、编码、内容长度等)
3)响应体:响应的实际内容(比如请求add.html页面时,响应的内容就是<form....)
具体可参考:HTTPS协议详解
由于HTTP是无状态的,因此没有办法判断两次请求是一个客户端发送过来的,还是不同客户端发送过来的,因此需要通过会话跟踪技术来解决无状态的问题。
会话跟踪技术
客户端请求,服务器响应,这就像谈话一样,这种场景被定义为一个会话。
- 客户端第一次发请求给服务器,服务器获取session,获取不到,则创建新的,然后响应给客户端
- 下次客户端给服务器发请求时,会把sessionID带给服务器,那么服务器就能获取到了,那么服务器就判断这一次请求和上次某次请求是同一个客户端,从而能够区分开客户端
- 常用的API:
request.getSession() -> 获取当前的会话,没有则创建一个新的会话
request.getSession(true) -> 效果和不带参数相同
request.getSession(false) -> 获取当前会话,没有则返回null,不会创建新的
session.getId() -> 获取sessionID
session.isNew() -> 判断当前session是否是新的
session.getMaxInactiveInterval() -> session的非激活间隔时长,默认1800秒
session.setMaxInactiveInterval()
session.invalidate() -> 强制性让会话立即失效
Session保存作用域
每个客户端实例化了一个HttpSession类,同时呢,也实例化了一个xxxServlet类
- session保存作用域是和具体的某一个session对应的
- 常用的API:
void session.setAttribute(k,v)
Object session.getAttribute(k)
void removeAttribute(k)
服务器端转发和客户端重定向
服务器端转发
服务器内部转发:request.getRequestDispatcher("...").forward(request,response);
-一次请求响应的过程,对于客户端而言,内部经过了多少次转发,客户端是不知道的
- 地址栏没有变化
客户端重定向
客户端重定向: response.sendRedirect("....");
- 两次请求响应的过程。客户端肯定知道请求URL有变化
- 地址栏有变化
超链接即为重定向,从URL和request的变化中即可看出
Web服务器中四种容器
四种容器的作用域
pageContext:当前页面有效 (页面跳转后无效)
request:同一次请求有效(请求转发后有效;重定向后无效)
session:同一次会话有效(关闭/切换浏览器后无效 ; 默认30分钟有效期)
appliation:全局有效 (切换浏览器 仍然有效) 服务器开着就有效,切换客户端
ServletContext接口
ServletContext作为Servlet的整体配置接口
-
- 获取某个资源的真实路径:getRealPath()
- 获取整个Web应用级别的初始化参数:getInitParameter()
- 作为Web应用范围的域对象
- 存入数据:setAttribute()
- 取出数据:getAttribute()
操作
[1]配置Web应用级别的初始化参数
<!-- 配置Web应用的初始化参数 -->
<context-param>
<param-name>handsomeMan</param-name>
<param-value>alsoMe</param-value>
</context-param>
[2]获取参数
String handsomeMan = servletContext.getInitParameter("handsomeMan");
System.out.println("handsomeMan = " + handsomeMan);
动态获取上下文路径
上下文路径(context path)= /Web应用名称
使用request对象来动态获取上下文路径,这样就不用担心上下文路径变化带来的影响。
request.getContextPath()
在Thymeleaf中则用@{}表示在字符串前附加上下文路径。
${pageContext.request.contextPath} 的效果就是"/应用名" ,这里等于 /test
补充一下相对路径与绝对路径的概念
https://blog.csdn.net/GoOnDrift/article/details/106664904
Thymeleaf基础语法
Thymeleaf精准获取请求参数中的值:
<p th:text="${param.team[0]}">这里替换为请求参数的值</p>
<p th:text="${param.team[1]}">这里替换为请求参数的值</p>
持久化层(DAO)
POJO类
POJO是Plain OrdinaryJava Object的缩写,实质上可以理解为简单的实体类,顾名思义POJO类的作用是方便程序员使用数据库中的数据表,对于广大的程序员,可以很方便的将POJO类当做对象来进行使用,当然也是可以方便的调用其get,set方法。
JavaBean
(1)通常情况下,由于 Java Bean 是被容器所创建(如 Tomcat) 的,所以 Java Bean 应具有一个无参的构造器,同时方便进行反射来赋值。
(2)实现 Serializable 接口用于实现 Bean 的持久性
(3)不能被跨进程访问
BaseDAO类:通用的增删改查操作
xxxDAO接口:针对某一张数据表的原子操作,使用继承与BaseDAO的方法实现
xxxDAOImpl类:重写xxxDAO接口中的所有方法
Fruit项目功能迭代过程
1.0 增删改查功能
使用HTML的事件请求设置对应的Servlet,或者通过JSP再调用Servlet。然后Servlet中调用DAO方法来操作数据库。
2.0 实现分页功能
-
厘清总记录条数与总页数的数学关系:
总记录条数:fruitCount 总页数:pageCount
pageCount = (frUItCount+4)/5
-
控制页面显示数量:
@Override public List<Fruit> getFruitList(Integer pageNo) { return super.executeQuery("select * from t_fruit limit ?,5",(pageNo-1)*5); }
-
确定记录数据:
@Override public int getFruitCount() { return ((Long)super.executeComplexQuery("select count(*) from t_fruit")[0]).intValue(); }
4.在界面上实现:
<div style="width:60%;margin-left:20%;" class="center"> <input type="button" value="首 页" class="btn" th:onclick="|page(1)|" th:disabled="${session.pageNo==1}"/> <input type="button" value="上一页" class="btn" th:onclick="|page(${session.pageNo-1})|" th:disabled="${session.pageNo==1}"/> <input type="button" value="下一页" class="btn" th:onclick="|page(${session.pageNo+1})|" th:disabled="${session.pageNo==session.pageCount}"/> <input type="button" value="尾页" class="btn" th:onclick="|page(${session.pageCount})|" th:disabled="${session.pageNo==session.pageCount}"/></div>
5.判断
通过一个判断变量,既可以区分请求是由查询而来还是从点击下一页等而来。
if(StringUtil.isNotEmpty(oper) && "search".equals(oper)){ //说明此时是点击 查询来进入index组件的,keyword可以从查询的表单中获取 pageNo=1 ; //从表单的请求中获取keyword keyword = req.getParameter("keyword"); if(StringUtil.isEmpty(keyword)){ keyword=""; } session.setAttribute("keyword",keyword); }else { //说明此处不是通过点击查询来发送请求的,当点击下面的 页数的时候 //从会话中得到页数 String pageNoStr = req.getParameter("pageNo"); if(StringUtil.isNotEmpty(pageNoStr)){ pageNo = Integer.parseInt(pageNoStr); } //从会话中获取关键词 Object keywordObj = session.getAttribute("keyword"); if(keywordObj!=null){ keyword = (String)keywordObj; }else{ keyword=""; } }
3.0 Servlet优化
由于以前其实也是调用XXXServlet中的service方法,然后service()方法根据method的值判断应该调用XXXServlet中的哪一个方法,因此现在只需要重写service方法即可。
把之前的XxxServlet类都写成方法,让他们都变成FruitServlet类里边的方法,然后在FruitServlet类中重写的service方法调用
现在设计一个FruitServlet类,标签为fruit.do,那么所有的.html文件的相关位置都要改为fruit.do,并且都要回传一个隐藏域变量operate
@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 "del":
del(request,response);
break;
case "edit":
edit(request,response);
break;
case "update":
update(request,response);
break;
default:
throw new RuntimeException("operate值非法");
}
}
4.0 反射的引入
由于switch case不适合实际业务的开发,因此引入反射机制来替代switch case这一结构。一句话总结,利用反射机制来调用和operate同名的方法。
//使用反射机制来调用和operate同名的方法
//获取当前类中的所有方法
Method[] methods = this.getClass().getDeclaredMethods();
for(Method m : methods){
//获取方法名称
String methodName = m.getName();
if(operate.equals(methodName)){
try {
//找到和operate同名的方法,通过反射技术调用该方法
m.invoke(this,request,response);
return;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
throw new RuntimeException("operate值非法!");
5.0 dispatcherServlet的引入
由于实际业务中需要不同的业务,如水果管理系统中有水果和用户等业务,因此进一步抽象,引入核心控制器的概念。
- 解析URL
//解析字符串url
//假设url是: http://localhost:8080/pro15/hello.do
//那么servletPath是: /hello.do
// 我的思路是:
// 第1步: /hello.do -> hello 或者 /fruit.do -> fruit
// 第2步: hello -> HelloController 或者 fruit -> FruitController
String servletPath = request.getServletPath();//得到 /hello.do
servletPath = servletPath.substring(1);//处理掉斜杠
int lastDotIndex = servletPath.lastIndexOf(".do") ;//处理掉最后的.do
servletPath = servletPath.substring(0,lastDotIndex);
- XML配置
在XML中建立字符串fruit与FruitController的映射关系
<beans>
<!-- 这个bean标签的作用是 将来servletpath中涉及的名字对应的是fruit,那么就要FruitController这个类来处理 -->
<bean id="fruit" class="com.atguigu.fruit.controllers.FruitController"/>
</beans>
- XML文档的解析
解析XML配置文件,并最终创建一个map容器,beanMap,的技术就叫做DOM技术
DOM:Document Object Mode,文档对象模型
private Map<String,Object> beanMap = new HashMap<>();
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
//1.创建DocumentBuilderFactory
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//2.创建DocumentBuilder对象
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ;
//3.创建Document对象
Document document = documentBuilder.parse(inputStream);
//4.获取所有的bean节点,拿到每一组<bean id="fruit" class="com.atguigu.fruit.controllers.FruitController"/>
NodeList beanNodeList = document.getElementsByTagName("bean");
for(int i = 0 ; i<beanNodeList.getLength() ; i++){
//拿到一个bean标签
Node beanNode = beanNodeList.item(i);
if(beanNode.getNodeType() == Node.ELEMENT_NODE){
//Element有更好的API支持
Element beanElement = (Element)beanNode ;
String beanId = beanElement.getAttribute("id");
String className = beanElement.getAttribute("class");
//最终是希望在map容器中放入字符串id以及对应的实例
Class controllerBeanClass = Class.forName(className);
Object beanObj = controllerBeanClass.newInstance() ;
beanMap.put(beanId , beanObj) ;
}
}
比如说我们想要调用FruitController,那么我们就希望从xml中解析出另个东西存到map容器中,一个是字符串id=fruit,一个是FruitController的实例.
- 从map容器中获取对应的Controller实例
Object controllerBeanObj = beanMap.get(servletPath);
6.0 提取视图资源处理通用代码
新的客户端重定向方式:直接根据返回值来重定向,节约了使用switch-case的时间。
//2.controller组件中的方法调用
method.setAccessible(true);
//得到controller组件的返回对象
Object returnObj = method.invoke(controllerBeanObj,parameterValues);
//3.视图处理
String methodReturnStr = (String)returnObj ;
if(methodReturnStr.startsWith("redirect:")){
//比如: redirect:fruit.do
String redirectStr = methodReturnStr.substring("redirect:".length());
response.sendRedirect(redirectStr);
}
}
新的Thymeleaf跳转方式:
//2.controller组件中的方法调用
method.setAccessible(true);
Object returnObj = method.invoke(controllerBeanObj,parameterValues);
//3.视图处理
String methodReturnStr = (String)returnObj ;
if(methodReturnStr.startsWith("redirect:")){ //比如: redirect:fruit.do
String redirectStr = methodReturnStr.substring("redirect:".length());
response.sendRedirect(redirectStr);
}else{
super.processTemplate(methodReturnStr,request,response);
// 比如: "edit"
}
因为客户端重定向以及跳转的实现由核心控制器负责了,那么Fruit控制器中各个方法的形参也就不再需要HTTPServletResponse响应,核心控制器继承的不再是HttpServlet,而是ViewBaseServlet。
解决参数不匹配问题
我们从HttpServletRequest实例request中取到的数据都是String类型,然后将他们作为实参传给了控制器的某个方法。但是这个方法需要的实参并不都是String
还是使用反射机制,获得每个形参的类型parameter.getType().getName(),依据类型进行强转。又一次说明反射机制的重要性
//从请求中获取参数值
String parameterValue = request.getParameter(parameterName);
String typeName = parameter.getType().getName();
Object parameterObj = parameterValue ;
//如果是Integer,则强制转换
if(parameterObj!=null) {
if ("java.lang.Integer".equals(typeName)) {
parameterObj = Integer.parseInt(parameterValue);
}
}
parameterValues[i] = parameterObj ;
PS:可以看出Servlet的核心就在于反射这一块。
7.0 使用ServletConfig实现类存储初始化配置参数
- 首先可以定义web.xml中的初始化参数
<servlet>
<servlet-name>Demo01Servlet</servlet-name>
<servlet-class>com.atguigu.servlet.Demo01Servlet</servlet-class>
<init-param>
<param-name>hello</param-name>
<param-value>world</param-value>
</init-param>
<init-param>
<param-name>uname</param-name>
<param-value>jim</param-value>
</init-param>
</servlet>
- 使用servletConfig管理类的接口
在我们重写的init方法中,通过ServletConfig接口的实现类的实例来获取初始化配置参数,换句或说ServletConfig实例是用来管理初始化配置参数的。
public class Demo01Servlet extends HttpServlet {
@Override
public void init() throws ServletException {
ServletConfig config = getServletConfig();
String initValue = config.getInitParameter("hello");
System.out.println("initValue = " + initValue);
}
}
- 在ServletContext中实现类存储参数(IOC容器的实现)
1.配置context-param参数(全局配置参数)
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
- 在重写的空参init方法中获取ServletContext实例中的参数
public void init() throws ServletException {
ServletConfig config = getServletConfig();
String initValue = config.getInitParameter("hello");
System.out.println("initValue = " + initValue);
ServletContext servletContext = getServletContext();
String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
System.out.println("contextConfigLocation = " + contextConfigLocation);
}
3.在重写的service方法中获取ServletContext实例中的参数,这就麻烦一点,必须通过HttpServletRequest实例来获取
@Override
public void service(HttpServletRequest request, HttpServletResponse response){
request.getServletContext();
request.getSession().getServletContext();
}
8.0 Service技术
由业务层负责与业务相关的特有操作(一个业务可能包含多个DAO方法),控制器依赖业务层,负责业务层调用的方法,业务层负责调用DAO组件来实现业务方法。
业务层分为FruitService接口和FruitServiceImpl实现类,由FruitServiceImpl实现类中的方法调用fruitDAO实现目标。
(引入业务层就是将以细粒度中的DAO方法转换为粗粒度的业务方法)——这样更符合我们的核心需求。
9.0 引入IOC技术
-
中央控制器的作用:
继承了ViewBaseServlet,用于thymeleaf渲染
- 重写init方法,调用父类ViewBaseServlet的init方法,实例化IOC层的BeanFactory用于提供各层需要的实例
- 重写service方法,解析url确定调用控制器的哪一个方法,统一获取参数,视图处理
-
IOC层的作用:
新的IOC层负责:解析applicationContext.xml配置文件,创建beanMap容器,组装bean之间的依赖关系(赋值)
IOC层的结构非常简单:(1)接口BeanFactory,用于定义一些方法(2)接口的实现类,一个作为属性的map容器,一个空参构造器,重写接口中的方法
所谓的组装,其实就是进行赋值。比如说,控制器层依赖下层的业务层,在有IOC之前,我们是直接在控制器层中实例化了业务层,将业务层实例作为控制器层的一个属性。但现在是由IOC层利用反射机制为这个属性进行赋值,这个值来自于第一步中的beanMap容器(就是由IOC容器统一管理,统一实例化)
组装实例之间的依赖关系
1.在xml中配置bean之间的依赖关系
<beans>
<bean id="fruitDAO" class="com.atguigu.fruit.dao.impl.FruitDAOImpl"/>
<bean id="fruitService" class="com.atguigu.fruit.service.impl.FruitServiceImpl">
<!-- property标签用来表示属性;name表示属性名;ref表示引用其他bean的id值-->
<property name="fruitDAO" ref="fruitDAO"/>
</bean>
<bean id="fruit" class="com.atguigu.fruit.controllers.FruitController">
<property name="fruitService" ref="fruitService"/>
</bean>
</beans>
2.组装依赖类之间的关系
//1) 找到propertyRef对应的实例
Object refObj = beanMap.get(propertyRef);
//2) 将refObj设置到当前bean对应的实例的property属性上去
Object beanObj = beanMap.get(beanId);
Class beanClazz = beanObj.getClass();
Field propertyField = beanClazz.getDeclaredField(propertyName);
propertyField.setAccessible(true);
propertyField.set(beanObj,refObj);
FruitController依赖FruitServiceImpl
- 根据FruitController中的ref(fruitService),取出被依赖的实例FruitServiceImpl
- 根据fruit(id)取出FruitController实例
- 根据FruitController中的name的值拿到fruitService属性
- 将FruitController对象中的fruitService顺序性设置为FruitServiceImpl
10.0 引入Filter技术
//@WebFilter("/demo01.do")
@WebFilter("*.do")
public class Demo01Filter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("helloA");
//放行
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("helloA2");
}
@Override
public void destroy() {
}
}
不只是请求,服务器端的响应也要经过过滤器,也就是说,请求先被拦截,打印helloA,然后放行,执行servlet的service方法,响应传回给客户端之前,响应被过滤器拦截,打印helloA2,最后,响应传到客户端。
创建过滤器的步骤
Filter也属于Servlet规范
Filter开发步骤:新建类实现Filter接口,然后实现其中的三个方法:init、doFilter、destroy
配置Filter,可以用注解@WebFilter,也可以使用xml文件
Filter在配置时,和servlet一样,也可以配置通配符,例如 @WebFilter(“*.do”)表示拦截所有以.do结尾的请求
过滤器链
1)执行的顺序依次是: A B C demo03 C2 B2 A2
2)如果采取的是注解的方式进行配置,那么过滤器链的拦截顺序是按照全类名的先后顺序排序的
3)如果采取的是xml的方式进行配置,那么按照配置的先后顺序进行排序
11.0 引入事务管理技术
(1)将通过DriverManager获取连接封装为一个方法
(2)从ThreadLocal实例获取连接封装为一个方法
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
public static final String DRIVER
public static final String URL
public static final String USER
public static final String PWD
private static Connection createConn(){
try {
//1.加载驱动
Class.forName(DRIVER);
//2.通过驱动管理器获取连接对象
return DriverManager.getConnection(URL, USER, PWD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null ;
}
public static Connection getConn(){
Connection conn = threadLocal.get();
if(conn==null){
conn =createConn();
threadLocal.set(conn);
}
return threadLocal.get() ;
}
于是TransactionManager中的结构变成:
public class TransactionManager {
//开启事务
public static void beginTrans() throws SQLException {
ConnUtil.getConn().setAutoCommit(false);
}
//提交事务
public static void commit() throws SQLException {
ConnUtil.getConn().commit();
}
//回滚事务
public static void rollback() throws SQLException {
ConnUtil.getConn().rollback();
}
}
12.0 异常体系构建
//执行更新,返回影响行数
protected int executeUpdate(String sql , Object... params) {
boolean insertFlag = false ;
insertFlag = sql.trim().toUpperCase().startsWith("INSERT");
conn = getConn();
try{
}catch (SQLException e){
e.printStackTrace();
throw new DAOException("BaseDAO executeUpdate出错了");
}
}
13.0 添加监听器
public interface EventListener {
}
public interface ServletContextListener extends EventListener {
void contextInitialized(ServletContextEvent var1);
void contextDestroyed(ServletContextEvent var1);
}
//@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("Servlet上下文对象初始化动作被我监听到了....");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("Servlet上下文对象销毁动作被我监听到了.....");
}
}
监听器的使用:在监听器中提前实例化IOC容器
//监听上下文启动,在上下文启动的时候去创建IOC容器,然后将其保存到application作用域
//后面中央控制器再从application作用域中去获取IOC容器
@WebListener
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//创建IOC容器
BeanFactory beanFactory = new ClassPathXmlApplicationContext();
//获取ServletContext对象
ServletContext application = servletContextEvent.getServletContext();
//将IOC容器保存到application作用域
application.setAttribute("beanFactory",beanFactory);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
然后中央控制器再从application作用域中去获取IOC容器
private BeanFactory beanFactory ;
public DispatcherServlet(){
}
public void init() throws ServletException {
super.init();
//之前是在此处主动创建IOC容器的
//现在优化为从application作用域去获取
//beanFactory = new ClassPathXmlApplicationContext();
ServletContext application = getServletContext();
Object beanFactoryObj = application.getAttribute("beanFactory");
if(beanFactoryObj!=null){
beanFactory = (BeanFactory)beanFactoryObj ;
}else{
throw new RuntimeException("IOC容器获取失败!");
}
}
ServletContext其实就是Application对象,启动时由tomcat创建。
https://blog.csdn.net/weixin_43362002/article/details/124659893
14.0 项目小结
项目的整体流程
- 首先用CharacterEncodingFilter拦截器来拦截请求,从而配置编码。
- 使用事务管理器开启事务,遇到错误则回退事务。
- 使用DispatchServlet来解析URL,并调用对应的控制器FruitController,接受到operate参数的方法,通过反射技术调用方法,同时进行重定向或视图处理。
- FruitController依赖FruitService中提供的方法,这里涉及到依赖注入,核心就是通过ioc容器在使用时将一个FruitService实例赋给FruitController,这样就起到解耦合的作用。
- 获取连接时只从ThreadLocal实例中获取,如果没有拿到,就造一个连接放到ThreadLocal实例中,然后再从ThreadLocal实例中取出数据库连接。
- 于是TransactionManager就可以调用ConnUtil中获取数据库连接的方法来开启事务。
- 事务开启后,使用FruitDAO调用BaseDAO方法实现数据库操作。