CS与BS
CS:客户端服务器架构模式 充分利用客户端机器资源,减轻服务器负荷 一部分安全要求不高的计算任务放在客户端执行,不需要把计算和存储都在服务端执行,从而减轻服务端压力,也能够减轻网络负荷 需要安装;升级维护成本高 BS:浏览器服务器架构模式 客户端不需要安装;维护成本低 所有计算和存储都在服务端,服务器负荷较重;在服务端计算之后把结果给客户端,会进行频繁的数据通信,网络负荷较重
Tomcat
server服务端:tomcat是一个webContainer Tomcat目录: bin 可执行文件 conf 配置文件 lib 依赖 logs webapps项目部署目录 work工作目录 temp
Servlet
Server服务端 + let小应用程序 作用: 获取客户端发送的数据 调用DAO 步骤: 继承HttpServlet重写doPost/doGet 1. 用户发请求,action=add 2. 项目中,web.xml中找到url-pattern = /add 3. 找servlet-name = addFruit 4. 找和servlet-mapping中servlet-name一致的servlet 5. servlet-class -> com.example.controller.FruitController 6. 用户发送的是post请求(method=post) , 因此 tomcat会执行AddServlet中的doPost方法 web.xml
<servlet>
<servlet-name>addFruit</servlet-name>
<servlet-class>com.example.controller.FruitController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>addFruit</servlet-name>
<url-pattern>/add</url-pattern>
</servlet-mapping>
java.lang.ClassNotFoundException: org.apache.ibatis.io.Resources 原因:没有在设置中将项目设置为输出目录。 Artifact中: web Application:Exploded web程序部署包,解压后的war包 web Application:Archive web程序的压缩包,在tomcat webapp目录下会自动解压 如果网址是:http://localhost:8080/,那么默认访问index.html接着index.htm index.jsp,在tomcat web.xml文件的welcome-file-list中设置,如果这几个文件都没有404。也可以在自己的web.xml中设置欢迎页
编码问题
Post方式下,设置编码,防止中文乱码 req.setCharacterEncoding("utf-8");必须放在获取参数之前 Get方式不需要设置编码(基于tomcat8)
servlet的继承关系
1.继承关系 Servlet接口 GenericServlet抽象类 HttpServlet抽象子类 2.相关方法 javax.servlet.Servlet接口 init() service(ServletRequest,ServletResponse) 当请求过来时,service方法自动响应(tomcat容器调用的) destroy() javax.servlet.GenericServlet service(ServletRequest,ServletResponse)任然是抽象的 javax.servlet.HttpServlet service(ServletRequest,ServletResponse) 1.req.getMethod() 获取请求的方式“GET/POST等” 2.各种if判断,根据请求方式不同决定调用不同的do方法(doGet之类的) 3.do方法默认是405的实现风格-要求子类实现对应do方法,不然会报405错误
Servlet生命周期
生命周期:从出生到死亡的过程,对应servlet的三个方法:init() service() destroy() 默认情况下: 第一次接收请求时,servlet进行实例化(调用构造方法),初始化(init()),然后服务(调用service()) 提高了启动速度,但是第一次请求时间长(如果要提高响应速度,应该设置servlet初始化时机) 从第二次请求开始,每一次都是服务 容器关闭,servlet实例被销毁,调用destroy() servlet初始化请求: 默认第一次接收请求时servlet实例化 <load-on-startup>1</load-on-startup>设置servlet实例化时机,数字越小,启动越靠前 servlet在容器中是 单例的、线程不安全的 单例:所有请求都是同一个实例去响应 线程不安全:一个线程要根据这个实例中某个成员变量的值去做逻辑判断。但在中间某个时机,另一个线程改变了该成员变量的值,改变了这个成员变量的执行路径 启示:尽量不要在servlet中定义成员变量。如果不得不定义,尽量不要修改逻辑变量的值,不要根据成员变量的值做逻辑判断
HTTP
超文本传输协议 http是无状态的 http请求响应: 请求: 请求行:包含 请求方式 请求url 请求的协议(一般是http1.1) 请求头:对本次请求进行详细说明,客户端对服务器的自我介绍 cookie在里面 请求体: get方式:没有请求体,但是有一个queryString,在地址后面 post方式:有请求体,form data json方式:有请求体,request payload(json: raw 生的,未处理的数据) 响应: 响应行:包含 协议 状态码 响应状态 响应头:包含了服务器的信息,服务器发送给浏览器的描述信息(内容的媒体类型、编码、内容长度等) 响应体:响应的实际内容
Http协议解析
http :Hyper Text Transfer Protocol 超文本传输协议,规定浏览器与服务器之间数据传输的规则 特点: Tcp 请求响应模型:一次请求一次响应 http协议是无状态的协议:对于事务没有记忆能力,每次请求响应独立 缺点:多次请求之间不能共享数据 优点:速度快 http请求协议(请求数据的格式) 请求行:请求数据第一行(请求方式get 资源路径 协议http1.1) 请求头:第二行开始,格式key,value 请求体:Post请求有,存放请求参数 get请求:请求参数在请求行资源路径中,没有请求体,get请求大小是有限制的 post请求,请求参数在请求体中 http响应格式(响应数据的格式) 响应行:第一行(协议HTTP/1.1 状态码200 描述OK) 1xx:响应中,临时状态码,表示请求已经接收,告诉客户端应该继续请求或者如果已经完成则忽略 2xx:成功 3xx:让客户端重定向 4xx:客户端错误:请求不存在的资源,客户端未被授权,禁止访问等 5xx:服务端错误:程序抛出异常等 响应头:格式key,value 响应体:响应数据 Apache Tomcat web服务器(web容器,servlet容器) 软件程序,对http协议的操作进行封装,提供网上信息浏览服务 Tomcat识别Servlet,Tomcat是Servlet容器 前端控制器:DispatcherServlet,springMVC提供,实现Servlet接口 请求数据对象:HttpServletRequest 响应数据对象:HttpServletResponse http-DispatcherServlet(封装HttpServletRequest)-xxxController html文件放于static中,public或resource
会话
http是无状态的:每次请求服务器无法区分是否来源于同一个客户端 通过会话跟踪技术解决无状态的问题 常用API: request.getSession() 获取当前会话,没有创建一个新的会话 request.getSession(false) 没有返回null,不会创建新的 session.getMaxInactiveInterval() session非激活间隔时长 session的有效时间,默认30分钟 session.invalidate() 强制会话失效 session保存作用域: session.setAttribute(key,value)向当前session保存作用域保存一个数据 服务器内部转发与客户端重定向: 服务器内部转发:request.getRequestDispatcher(...).forward(request,response) 一次请求响应的过程 客户端重定向: response.sendRedirect(...) 多次请求响应
Thymeleaf
视图模板技术 步骤: 添加jar包 新建一个Servlet类ViewBaseServlet 在web.xml配置 配置前缀 view-prefix 配置后缀 view-suffix 使得自定义Servlet继承ViewBaseServlet 将逻辑视图名称拼接成物理视图名称
<!--配置Thymeleaf上下文参数-->
<context-param>
<param-name>view-prefix</param-name>
<param-value>/</param-value>
<!-- /代表webapp根目录 -->
</context-param>
<context-param>
<param-name>view-suffix</param-name>
<param-value>.html</param-value>
</context-param>
Servlet从3.0开始支持注解方式的注册,不需要在web.xml注册 @WebServlet()
<!--表示不让@webServlet拦截静态资源-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
保存作用域
原始情况下,四个保存作用域:page,request,session,application page:页面级别,几乎不用 request:一次请求响应范围 注意:客户端重定向与服务器端转发 session:一次会话范围 application:应用级别req.getServletContext() 打印两遍是因为tomcat中我们勾选了after launch,勾选后这个操作相当于自动提前发了一个请求,你可验证一下,你打印两次sessionid,这两个sessionid不同,说明是两个会话 路径问题: 相对路径: 绝对路径:eg:http://localhost:8080/context-root/index.html <base href="http://localhost:8080/context-root/* />作用:当前页面所有路径以其为基础 thymeleaf中:th:href=@{}就相当于base标签
@WebServlet("/index")
public class FruitController extends ViewBaseServlet {
private FruitService fruitService = new FruitServiceImpl();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
System.out.println("doPost");
fruitService.add(req, resp);
super.processTemplate("index", req, resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet");
List<Fruit> fruitList = fruitService.getFruitList();
req.getSession().setAttribute("fruitList", fruitList);
//此处的视图名称是 index
//那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上去
//逻辑视图名称 : index
//物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
//所以真实的视图名称是: / index .html
super.processTemplate("index", req, resp);
}
}
优化:
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
String operator = req.getParameter("operate");
if (operator == null || operator.equals("")) {
operator = "index";
}
Method[] methods = this.getClass().getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
if(methods[i].getName().equals(operator)) {
try {
methods[i].invoke(this, req, resp);
return;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
if(i==methods.length-1)throw new RuntimeException("operate非法");
}
/* switch (operator) {
case "index":
index(req, resp);
break;
case "edit":
edit(req,resp);
break;
case "update":
update(req,resp);
break;
case "add":
add(req,resp);
break;
case "delete":
delete(req,resp);
break;
default:
throw new RuntimeException("operator非法");
}
*/
}
xml
概念: html:超文本标记语言 xml:可扩展标记语言 html是xml的一个子类 xml组成: xml声明 <?xml version="1.0" encoding="UTF-8" ?> DTD文档类型定义 xml正文 节点分类:Node接口 元素节点:Element子接口 标签<bean/> 单标签没有子节点 文本节点:Text子接口 双标签之间的文本 空白 注释 等
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id="fruit" class="com.example.controller.FruitController"/>
</beans>
-parameters java虚拟机编译的时候附带方法参数名称
DispatcherServlet
DispatcherServlet这个类的工作分为两大部分: 1.根据url定位到能够处理这个请求的controller组件: 1)从url中提取servletPath : /fruit.do -> fruit 2)根据fruit找到对应的组件:FruitController , 这个对应的依据我们存储在applicationContext.xml中 <bean id="fruit" class="com.atguigu.fruit.controllers.FruitController/> 通过DOM技术我们去解析XML文件,在中央控制器中形成一个beanMap容器,用来存放所有的Controller组件 3)根据获取到的operate的值定位到我们FruitController中需要调用的方法 2.调用Controller组件中的方法: 1) 获取参数 获取即将要调用的方法的参数签名信息: Parameter[] parameters = method.getParameters(); 通过parameter.getName()获取参数的名称; 准备了Object[] parameterValues 这个数组用来存放对应参数的参数值 另外,我们需要考虑参数的类型问题,需要做类型转化的工作。通过parameter.getType()获取参数的类型 2) 执行方法 Object returnObj = method.invoke(controllerBean , parameterValues); 3) 视图处理 String returnStr = (String)returnObj; if(returnStr.startWith("redirect:")){ .... }else if.....
package com.example.ssm.springmvc;
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.ServletException;
import javax.servlet.annotation.WebServlet;
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.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
@WebServlet("/")
public class DispatcherServlet extends ViewBaseServlet {
private Map<String, Object> beanMap = new HashMap<>();
public DispatcherServlet() {
try {
//1.解析xml获取bean的id和class
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
//1.1创建documentBuilderFactory
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//1.2创建documentBuilder
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
//1.3创建document
Document document = documentBuilder.parse(inputStream);
//1.4获取所有的bean节点
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;
//获取id和class属性
String beanId = beanElement.getAttribute("id");
String className = beanElement.getAttribute("class");
//得到他的实例
Object beanObject = Class.forName(className).newInstance();
beanMap.put(beanId, beanObject);
}
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
/*
得到相对请求路径
第一步:/fruit 切割为 fruit(servletPath)
第二步:通过applicationContext.xml配置,将fruit与FruitController对应起来(放在map中)
第三步:通过servletPath获取Map的value,value就是对应的Controller对象,然后根据operate通过反射调用不同方法
*/
String servletPath = req.getServletPath();
servletPath = servletPath.substring(1);
Object controllerBeanObject = beanMap.get(servletPath);
String operator = req.getParameter("operate");
if (operator == null || operator.equals("")) {
operator = "index";
}
try {
Method[] methods = controllerBeanObject.getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(operator)) {
//1.统一获取请求参数
//获取当前方法参数,返回参数数组
Parameter[] parameters = method.getParameters();
//parameterValues用来承载参数的值
Object[] parameterValues= new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
String parameterName = parameter.getName();
Class<?> parameterType = parameter.getType();
if ("req".equals(parameterName)) {
parameterValues[i] = req;
} else if ("resp".equals(parameterName)) {
parameterValues[i] = resp;
} else if ("session".equals(parameterName)) {
parameterValues[i] = req.getSession();
} else if ("java.lang.Integer".equals(parameterType.getName())) {
String parameterString = req.getParameter(parameterName);
if (parameterString != null)
parameterValues[i] = Integer.parseInt(parameterString);
} else if ("java.lang.String".equals(parameterType.getName())) {
parameterValues[i] = req.getParameter(parameterName);
} else {
Object instance = parameterType.newInstance();
Field[] fields = instance.getClass().getDeclaredFields();
for(Field field:fields){
field.setAccessible(true);
String fieldValueString = req.getParameter(field.getName());
if(fieldValueString!=null){
if(field.getType().getName().equals("java.lang.Integer")){
field.set(instance,Integer.parseInt(fieldValueString));
}else{
field.set(instance,fieldValueString);
}
}
}
parameterValues[i]=instance;
}
}
//2.controller组件中的方法调用
method.setAccessible(true);
Object methodReturnObject = method.invoke(controllerBeanObject,parameterValues);
//3.视图处理
if (methodReturnObject != null) {
String methodReturnValue = (String) methodReturnObject;
if (methodReturnValue.startsWith("redirect:")) {
resp.sendRedirect(methodReturnValue.substring("redirect:".length()));
} else {
super.processTemplate(methodReturnValue, req, resp);
}
}
}
}
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
Servlet生命周期:实例化 初始化 服务 销毁
Servlet初始化有两个重载方法: public void init(){ } public void init(ServletConfig config){ this.config=config; init(); } 如果我们需要在Servlet初始化时做一些准备工作,考虑重写init(),可以通过以下步骤获取初始话数据: 1.配置xml文件或者使用注解 <servlet> <servlet-name></servlet-name> <servlet-class></servlet-class> <init-param> <param-name></param-name> <param-value></param-value> </init-param> </servlet> <servlet-mapping> <servlet-name></servlet-name> <url-pattern></url-pattern> </servlet-mapping> 注解方式配置:@WebServlet( urlPatterns={}, initParams={ @WebInitParam(name=key,value=value) } ) 2.获取config对象:ServletConfig config=getServletConfig(); 3.获取初始化参数值:config.getInitParameter(key) ServletContext Servlet上下文: 获取ServletContext的方式: 1.可以在初始化方法中获取 ServletContext servletContext=getServletContext(); 2.可以在服务方法中获取 request.getServletContext(); session.getServletContext(); 获取Servlet上下文值: 1.配置xml文件 <context-param> <param-name></param-name> <param-value></param-value> </context-param> 2.获取初始化值 servletContext.getInitParameter();
MVC
model:数据访问模型DAO;业务逻辑模型BO;值对象模型POJO/VO;数据传输对象DTO view:数据展示、用户交互的一个界面 controller: 接收客户端的请求,业务功能借助于模型组件
IOC
耦合/依赖 高内聚低耦合:层内部的组成应该是高度耦合的,而层与层之间关系应该是低耦合的 IOC控制反转/DI依赖注入 控制反转: 1) 之前在Servlet中,我们创建service对象 , FruitService fruitService = new FruitServiceImpl(); 这句话如果出现在servlet中的某个方法内部,那么这个fruitService的作用域(生命周期)应该就是这个方法级别; 如果这句话出现在servlet的类中,也就是说fruitService是一个成员变量,那么这个fruitService的作用域(生命周期)应该就是这个servlet实例级别 2) 之后我们在applicationContext.xml中定义了这个fruitService。然后通过解析XML,产生fruitService实例,存放在beanMap中,这个beanMap在一个BeanFactory中 因此,我们转移(改变)了之前的service实例、dao实例等等他们的生命周期。控制权从程序员转移到BeanFactory。这个现象我们称之为控制反转 依赖注入: 1) 之前我们在控制层出现代码:FruitService fruitService = new FruitServiceImpl(); 那么,控制层和service层存在耦合。 2) 之后,我们将代码修改成FruitService fruitService = null ; 然后,在配置文件中配置: <bean id="fruit" class="FruitController"> <property name="fruitService" ref="fruitService"/> </bean>
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id="fruitService" class="com.example.service.Impl.FruitServiceImpl"/>
<!-- id表示将来servletPath中涉及的名字是fruit,那就用class值的类FruitController来处理-->
<bean id="fruit" class="com.example.controller.FruitController">
<!-- property标签用来表示FruitController中的属性,name表示属性名,ref表示引用其他bean的id -->
<property name="fruitService" ref="fruitService"/>
</bean>
</beans>
public interface BeanFactory {
Object getBean(String id);
}
package com.example.ssm.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.Field;
import java.util.HashMap;
import java.util.Map;
public class ClassPathXmlApplicationContext implements BeanFactory {
private Map<String, Object> beanMap = new HashMap<>();
public ClassPathXmlApplicationContext() {
try {
//1.解析xml获取bean的id和class
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
//1.1创建documentBuilderFactory
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//1.2创建documentBuilder
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
//1.3创建document
Document document = documentBuilder.parse(inputStream);
//1.4获取所有的bean节点
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;
//获取applicationContext.xml标签的id和class属性
String beanId = beanElement.getAttribute("id");
String className = beanElement.getAttribute("class");
//得到他的实例
Object beanClass = Class.forName(className).newInstance();
//将bean实例对象保存到map容器中
beanMap.put(beanId, beanClass);
//由fruit获取controller并进行映射在DispatchersServlet中完成,下一步将组装bean和bean之间的依赖关系
}
}
//组装bean之间的依赖关系
for (int i = 0; i < beanNodeList.getLength(); i++) {
Node beanNode = beanNodeList.item(i);
if (beanNode.getNodeType() == Node.ELEMENT_NODE) {
//要得到元素节点bean
Element beanElement = (Element) beanNode;
String beanId = beanElement.getAttribute("id");
//得到的他子节点List
NodeList beanChildNodes = beanElement.getChildNodes();
for (int j = 0; j < beanChildNodes.getLength(); j++) {
//去除单个子节点
Node beanChildNode = beanChildNodes.item(j);
//得到元素节点并且名称是property
if(beanChildNode.getNodeType()==Node.ELEMENT_NODE&&"property".equals(beanChildNode.getNodeName())){
Element propertyElement = (Element) beanChildNode;
String propertyName = propertyElement.getAttribute("name");
String propertyRef = propertyElement.getAttribute("ref");
//下一步根据ref找到对象,给属性名为propertyName的赋值
//1.找到ref对应的实例 eg:根据fruitService找到new FruitServiceImpl
Object refObject = beanMap.get(propertyRef);
//2.将refObj设置到当前bean对应的实例property属性上去 eg:找到FruitController
Object beanObj = beanMap.get(beanId);
//3.根据propertyName获取同名属性
Field propertyField = beanObj.getClass().getDeclaredField(propertyName);
propertyField.setAccessible(true);
propertyField.set(beanObj,refObject);
}
}
}
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
@Override
public Object getBean(String id) {
return beanMap.get(id);
}
}
@WebListener
//监听上下文启动,在启动的时候创建IOC容器,然后将其保存到application作用域中,后面中央控制器从application作用域中获取IOC容器
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
BeanFactory beanFactory = new ClassPathXmlApplicationContext();
ServletContext application = servletContextEvent.getServletContext();
application.setAttribute("beanFactory",beanFactory);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
package com.example.ssm.springmvc;
import com.example.ssm.ioc.BeanFactory;
import com.example.ssm.ioc.ClassPathXmlApplicationContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
@WebServlet("/")
public class DispatcherServlet extends ViewBaseServlet {
private BeanFactory beanFactory;
@Override
public void init() throws ServletException {
super.init();
//之前是在此处创建IOC容器,也就是IOC随着中央控制器初始化而创建,但是IOC应该在上下文被创建的时侯就准备好,所以优化在监听器中进行
//beanFactory = new ClassPathXmlApplicationContext();
ServletContext application = getServletContext();
Object beanFactoryObject = application.getAttribute("beanFactory");
if (beanFactoryObject != null) {
beanFactory = (BeanFactory) beanFactoryObject;
} else {
throw new RuntimeException("IOC容器获取失败");
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// req.setCharacterEncoding("utf-8");
/*
得到相对请求路径
第一步:/fruit 切割为 fruit(servletPath)
第二步:通过applicationContext.xml配置,将fruit与FruitController对应起来(放在map中)
第三步:通过servletPath获取Map的value,value就是对应的Controller对象,然后根据operate通过反射调用不同方法
*/
String servletPath = req.getServletPath();
servletPath = servletPath.substring(1);
Object controllerBeanObject = beanFactory.getBean(servletPath);
String operator = req.getParameter("operate");
if (operator == null || operator.equals("")) {
operator = "index";
}
try {
Method[] methods = controllerBeanObject.getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(operator)) {
//1.统一获取请求参数
//获取当前方法参数,返回参数数组
Parameter[] parameters = method.getParameters();
//parameterValues用来承载参数的值
Object[] parameterValues = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
String parameterName = parameter.getName();
Class<?> parameterType = parameter.getType();
if ("req".equals(parameterName)) {
parameterValues[i] = req;
} else if ("resp".equals(parameterName)) {
parameterValues[i] = resp;
} else if ("session".equals(parameterName)) {
parameterValues[i] = req.getSession();
} else if ("java.lang.Integer".equals(parameterType.getName())) {
String parameterString = req.getParameter(parameterName);
if (parameterString != null)
parameterValues[i] = Integer.parseInt(parameterString);
} else if ("java.lang.String".equals(parameterType.getName())) {
parameterValues[i] = req.getParameter(parameterName);
} else {
Object instance = parameterType.newInstance();
Field[] fields = instance.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
String fieldValueString = req.getParameter(field.getName());
if (fieldValueString != null) {
if (field.getType().getName().equals("java.lang.Integer")) {
field.set(instance, Integer.parseInt(fieldValueString));
} else {
field.set(instance, fieldValueString);
}
}
}
parameterValues[i] = instance;
}
}
//2.controller组件中的方法调用
method.setAccessible(true);
Object methodReturnObject = method.invoke(controllerBeanObject, parameterValues);
//3.视图处理
if (methodReturnObject != null) {
String methodReturnValue = (String) methodReturnObject;
if (methodReturnValue.startsWith("redirect:")) {
resp.sendRedirect(methodReturnValue.substring("redirect:".length()));
} else {
super.processTemplate(methodReturnValue, req, resp);
}
}
return;
}
}
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
1. 过滤器Filter 2. 事务管理(TransactionManager、ThreadLocal、OpenSessionInViewFilter) 3. 监听器(Listener , ContextLoaderListener)
过滤器Filter
1) Filter也属于Servlet规范 2) Filter开发步骤:新建类实现Filter接口,然后实现其中的三个方法:init、doFilter、destroy 配置Filter,可以用注解@WebFilter,也可以使用xml文件 <filter> <filter-mapping> 3) Filter在配置时,和servlet一样,也可以配置通配符,例如 @WebFilter("*.do")表示拦截所有以.do结尾的请求 4) 过滤器链 1)执行的顺序依次是: A B C demo03 C2 B2 A2 2)如果采取的是注解的方式进行配置,那么过滤器链的拦截顺序是按照全类名的先后顺序排序的 3)如果采取的是xml的方式进行配置,那么按照配置的先后顺序进行排序
事务管理
1) 涉及到的组件: - OpenSessionInViewFilter - TransactionManager - ThreadLocal - ConnUtil - BaseDAO 2) ThreadLocal - get() , set(obj) - ThreadLocal称之为本地线程 。 我们可以通过set方法在当前线程上存储数据、通过get方法在当前线程上获取数据 - set方法源码分析: public void set(T value) { Thread t = Thread.currentThread(); //获取当前的线程 ThreadLocalMap map = getMap(t); //每一个线程都维护各自的一个容器(ThreadLocalMap) if (map != null) map.set(this, value); //这里的key对应的是ThreadLocal,因为我们的组件中需要传输(共享)的对象可能会有多个(不止Connection) else createMap(t, value); //默认情况下map是没有初始化的,那么第一次往其中添加数据时,会去初始化 } -get方法源码分析: public T get() { Thread t = Thread.currentThread(); //获取当前的线程 ThreadLocalMap map = getMap(t); //获取和这个线程(企业)相关的ThreadLocalMap(也就是工作纽带的集合) if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); //this指的是ThreadLocal对象,通过它才能知道是哪一个工作纽带 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; //entry.value就可以获取到工具箱了 return result; } } return setInitialValue(); }
监听器WebListener
接口: 1) ServletContextListener - 监听ServletContext对象的创建和销毁的过程 观察者模式 2) HttpSessionListener - 监听HttpSession对象的创建和销毁的过程 3) ServletRequestListener - 监听ServletRequest对象的创建和销毁的过程 4) ServletContextAttributeListener - 监听ServletContext的保存作用域的改动(add,remove,replace) 5) HttpSessionAttributeListener - 监听HttpSession的保存作用域的改动(add,remove,replace) 6) ServletRequestAttributeListener - 监听ServletRequest的保存作用域的改动(add,remove,replace) 7) HttpSessionBindingListener - 监听某个对象在Session域中的创建与移除 8) HttpSessionActivationListener - 监听某个对象在Session域中的序列化和反序列化 序列化-钝化 内存->磁盘; 反序列化-活化 4. ServletContextListener的应用 - ContextLoaderListener
数据库的范式
1) 第一范式:列不可再分 2) 第二范式:一张表只表达一层含义(只描述一件事情) 3) 第三范式:表中的每一列和主键都是直接依赖关系,而不是间接依赖 数据库设计的范式和数据库的查询性能很多时候是相悖的,我们需要根据实际的业务情况做一个选择: - 查询频次不高的情况下,我们更倾向于提高数据库的设计范式,从而提高存储效率 - 查询频次较高的情形,我们更倾向于牺牲数据库的规范度,降低数据库设计的范式,允许特定的冗余,从而提高查询的性能