0%

JavaWeb一览

1. IDEA中tomcat部署与运行

采用maven形式

  1. 右键,new->Module,选择Maven Archetype,Archetype选择:org.apache.maven.archetypes:maven-archetype-webapp,自定义名称等,其它默认,如图:
image.png
image.png
  1. 点击create,创建完成后,进入pom.xml,添加tomcat插件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <plugin>
    <groupId>org.apache.tomcat.maven</groupId>
    <artifactId>tomcat7-maven-plugin</artifactId>
    <version>2.2</version>
    <configuration>
    <!-- 端口 -->
    <port>8888</port>
    <!-- 路径 -->
    <path>/</path>
    </configuration>
    </plugin>

  2. 打开右侧工具栏中的Maven,点击M(Excute Maven Goal),输入tomcat7:run即可运行项目

image.png
image.png

2、Servlet

1、导包

在正式使用前,需要在Maven中导入Servlet包

1
2
3
4
5
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>

2、使用Servlet

  1. 编写一个类实现Servlet接口
  2. 实现Service方法,处理请求,并响应数据
  3. 在web.xml中配置Servlet程序的访问地址

但在实际使用中,通常不用Servlet接口,而是使用它的实现类的子类HttpServlet,即:

  1. 编写Servlet,继承自HttpServlet,重写doPost()、doGet()方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class AddServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String fname = req.getParameter("fname");
    Integer price = Integer.valueOf(req.getParameter("price"));
    Integer fcount = Integer.valueOf(req.getParameter("fcount"));
    String remark = req.getParameter("remark");
    System.out.println(fname+"--"+price+"--"+fcount+"--"+remark);
    }
    }

  2. 配置文件web.xml或者使用注解配置(Servlet从3.0版本开始支持使用注解)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd" >

    <web-app>
    <!-- 给Tomcat配置Servlet程序 -->
    <servlet>
    <!-- Servlet名称(一般是类名) -->
    <servlet-name>AddServlet</servlet-name>
    <!-- Servlet全限定类名 -->
    <servlet-class>com.zephon.AddServlet</servlet-class>
    </servlet>
    <!-- 给Servlet程序配置访问地址 -->
    <servlet-mapping>
    <!-- Servlet名 -->
    <servlet-name>AddServlet</servlet-name>
    <!-- Servlet对应处理的请求url,以/开头,/表示http://ip:port/工程路径 -->
    <url-pattern>/add</url-pattern>
    </servlet-mapping>
    </web-app>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // @WebServlet("/add") // 表示对应的请求url
    @WebServlet(urlPatterns={"/add"},
    initParams = {
    @WebInitParam(name="user", value="root"),
    @WebInitParam(name="password", value="123456")
    }
    )
    public class AddServlet extends HttpServlet {
    ...
    }

  3. 处理中文乱码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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");
    Integer price = Integer.valueOf(req.getParameter("price"));
    Integer fcount = Integer.valueOf(req.getParameter("fcount"));
    String remark = req.getParameter("remark");
    System.out.println(fname+"--"+price+"--"+fcount+"--"+remark);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println(req.getParameter("fname"));
    // req.setCharacterEncoding("utf-8");
    // tomcat7及之前的版本中get请求处理乱码的方式
    String fname = req.getParameter("fname");
    byte[] bytes = fname.getBytes(StandardCharsets.ISO_8859_1);
    fname = new String(bytes, StandardCharsets.UTF_8);

    System.out.println(fname);
    }
    }

3、Servlet

继承关系:

  • javax.servlet.Servlet接口
    • javax.servlet.GenericServlet抽象类
      • javax.servlet.http.HttpServlet抽象子类

生命周期:

  1. 出生:init

  2. 运行:service

  3. 死亡:destroy

  4. 默认情况下,第一次接受请求时,Servlet会进行实例化、初始化,调用init()方法,然后服务

  5. 之后的每次请求,都会调用service()方法

  6. 当容器关闭时,其中所有Servlet实例都会被销毁,会调用destroy()方法

javax.servlet.Servlet主要方法

  • void init(ServletConfig config);初始化方法
  • void service(ServletRequest req, ServletResponse res);服务方法,前端有请求时调用
  • void destroy();销毁方法

javax.servlet.GenericServlet主要方法

实现了Servlet接口,做了很多空的实现,并持有一个ServletConfig类的引用

常用方法
  • void service(ServletRequest req, ServletResponse res);服务方法,仍然是抽象的
涉及到的ServletConfig接口

ServletConfig接口:Servlet程序的配置信息接口,也是由Tomcat服务器负责创建实例化,每个Servlet创建时就创建一个对应的ServletConfig实例 ServletConfig类的作用:

  1. 获取Servlet程序的别名,即web.xml中配置的的值,对应的方法:config.getServletName();
  2. 获取初始化参数,即web.xml中配置的,对应的方法:config.getInitParameter(String s);其中s表示对应参数名

例:

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>AddServlet</servlet-name>
<servlet-class>com.zephon.AddServlet</servlet-class>
<init-param>
<param-name>jdbcClass</param-name>
<param-value>com.mysql.cj.jdbc.Driver</param-value>
</init-param>
</servlet>

  1. 获取ServletContext对象,对应的方法:ServletContext servletContext = config.getServletContext(); > 注: > 1. 即使在子类中调用父类的getServletConfig()获取ServletConfig对象,其获取的各种属性、参数等仍是子类对应的,即如果在web.xml中的参数定义的是给父类的,则子类获得的对应的值就是null > 1. 当继承了HttpServlet时,重写了init方法,则必须在init方法调用super.init(config),否则其它方法如果调用getServletConfig()时获取的ServletConfig会是空的对象
涉及到的ServletContext接口
  1. ServletContext是一个接口,表示Servlet上下上文对象
  2. 一个Web工程只有一个ServletContext对象实例
  3. ServletContext对象是一个域对象 > 什么是域对象? > 域对象就是可以像Map一样存取数据的对象。这个域指的是存取数据的操作范围。 > 域对象统一存、取、删方法: > 存:setAttribute(String key, Object value) > 取:getAttribute(String key) > 删:removeAttribute(String key)

作用:

  1. 获取web.xml中配置的上下文参数,即:context.getInitParameter(String name);

注:不能获取中的内容 例:

1
2
3
4
5
6
7
8
<web-app>
<!-- 上下文参数,属于整个web工程 -->
<context-param>
<param-name>username</param-name>
<param-value>root</param-value>
</context-param>
...
</web-app>

  1. 获取当前的工程路径,即:context.getContextPath(); // 如/hello
  2. 获取工程部署后在服务器磁盘上绝对路径,即:context.getRealPath("/"); // "/"表示http://ip:端口/工程名,返回值例:D:,最终映射到IDEA中是代码中对应的webapp目录
  3. 像Map一样存取数据

javax.servlet.http.HttpServlet主要方法

  • void service(ServletRequest req, ServletResponse res);服务方法,根据请求类型执行对应的方法,如"POST"类型则执行doPost();
  • doGet()、doHead()、doPut()、doDelete()、doOptions()、doTrace()等,分别对不同请求类型进行默认处理,一般就是返回405错误

小结:

上述主要方法中的服务方法,当有请求时,会自动响应,实际上是tomcat容器调用(反射调用)的,然后在HttpServlet重写的服务方法中会对请求类型进行划分处理,执行对应的方法。 Servlet第一次请求时都会实例化、初始化,然后再服务,这样会提高系统的启动速度,但会造成第一次请求耗时会较长;因此,如果需要提高响应速度,可以设置Servlet的初始化时机(在web.xml中设置,设置Servlet启动的先后顺序,数字越小,启动越靠前),牺牲系统的启动速度来提高响应速度。 > 注:Servlet在容器中是单例的、线程不安全的 > 线程不安全-->尽量不要在Servlet中定义成员变量,如果不得不定义成员变量,则不应该: > 1. 修改成员变量值 > 1. 根据成员变量值做逻辑判断

Service的请求参数HttpServletRequest

每次只要有请求进入Tomcat服务器,Tomcat服务器就会将请求过来的HTTP协议信息解析封装到Request对象中,然后传递到service方法中,可以通过HttpServletRequest对象获取所有请求的信息 继承关系:

  • javax.servlet.ServletRequest:接口
    • javax.servlet.http.HttpServletRequest:子接口
常用方法
  • String uri = req.getRequestURI(); // 获取请求的资源路径,如:/hello/MyServlet
  • StringBuffer url = req.getRequestURL(); // 获取请求的统一资源定位符,如:http://localhost:80/hello/MyServlet
  • String remoteHost = req.getRemoteHost(); // 获取客户端IP地址,如127.0.0.1
  • String header = req.getHeader(String name); // 获取请求头,如:name="User-Agent"
  • String parameter = req.getParameter(String s); // 获取请求的参数
  • String[] parameterValues = req.getParameterValues(String s); // 获取请求的参数(多个值时使用,例如前端数据是一个多选框)
  • String method = req.getMethod(); // 获取请求的方式,如:"GET"、"POST"、"PUT等
  • req.setAttribute(String key, Object value); // 设置域数据
  • Object attribute = req.getAttribute(String key); // 获取域数据
  • req.setCharacterEncoding("utf-8"); // 设置请求体的字符集,用于解决请求的中文乱码问题
  • req.getServletPath():返回请求URI,即如果请求地址是:http://ip:端口/project/abc,则返回/abc
请求转发相关

请求转发指的是服务器收到请求后,从一个资源跳转到另一个资源的操作。

  • RequestDispatcher requestDispatcher = req.getRequestDispatcher(String path); // 获取请求转发对象,path表示新的请求路径,必须以/开始,"/"表示http://ip:port/工程名,映射到IDEA的webapp目录,例:"/servlet2"
  • 通常后面需要跟一个转发的方法:requestDispatcher.forward(req, resp);

请求转换的特点:

  1. 浏览器地址栏没有变化
  2. 几次请求是同一次请求
  3. 共享Request域中的数据
  4. 可以转发到WEB-INF目录下(直接访问则不行),path设置为"/WEB-INF/..."
  5. 不能访问工程以外的资源,如:path设置为"www.baidu.com"就不行

Service的响应参数HttpServletResponse

HttpServletResponse和HttpServletRequest一样,每次请求进来时,Tomcat服务器会创建一个HttpServletResponse对象传递给Servlet程序使用。HttpServletRequest表示请求过来的信息,HttpServletResponse表示响应的信息

常用方法
  • resp.setCharacterEncoding(String s); // 设置服务器字符集编码,配合resp.setHeader("Content-Type", "text/html; charset=UTF-8");(或resp.setContentType("text/html; charset=UTF-8");,使用setContentType时,不用setCharacterEncoding,它会同时设置服务器和客户端字符集,)可解决中文乱码问题,注:必须在获取流对象之前调用
  • resp.setContentType(String s);//设置响应体内容类型等
两个响应流
  • resp.getOutputStream();获取字节流,常用于下载,传递二进制数据
  • resp.getWriter();获取字符流,常用于回传字符串

两个流同时只能使用一个 往客户端回传字符串数据:

1
2
3
4
5
resp.setCharacterEncoding("UTF-8");
// 通过响应头设置浏览器使用UTF-8字符集
resp.setHeader("Content-Type", "text/html; charset=UTF-8");
PrintWriter writer = resp.getWriter();
writer.write("响应数据");

请求重定向相关

请求重定向指客户端向服务器发请求,服务器给定新地址,客户端用新地址去访问。

  • resp.setStatus(int i); // 设置响应状态码,请求重定向时通常需要设置为302,表示重定向
  • resp.setHeader(String name, String value); // 设置响应头,name:响应头名,value:响应头值,例如请求重定向时通常需要设置:resp.setHeader("Location", "http://www.baidu.com");指明新地址
  • resp.sendRedirect(String url); // 直接进行请求重定向

请求重定向的两种实现: 方式一:

1
2
resp.setStatus(302);
resp.setHeader("Location", "https://www.baidu.com");
方式二(推荐使用):
1
resp.sendRedirect("https://www.baidu.com");
请求重定向特点:

  1. 浏览器地址栏会发生变化
  2. 两次请求
  3. 不共享Request域中的资源
  4. 不能访问WEB-INF下的资源
  5. 可以访问工程外的资源

Filter过滤器

  • Filter过滤器是JavaWeb的三大组件之一
  • Filter过滤器的作用:拦截请求,过滤响应

应用场景:

  • 权限检查
  • 日记操作
  • 事务管理
  • ......

使用Filter过滤器

  1. 编写一个类去实现javax.servlet.Filter接口

  2. 重写接口对应的方法,主要是doFilter方法

    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
    public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    // 过滤登录的案例
    HttpServletRequest req = (HttpServletRequest) servletRequest;
    HttpSession session = req.getSession();
    Object user = session.getAttribute("user");
    if(user==null){
    req.getRequestDispatcher("/login").forward(servletRequest, servletResponse);
    return;
    }else{
    // 继续经过其它有可能的Filter或正常访问
    filterChain.doFilter(servletRequest, servletResponse);
    }
    }

    @Override
    public void destroy() {

    }
    }

  3. 在web.xml中配置,也可使得注解@WebFilter()代替

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- 配置过滤器 -->
    <filter>
    <filter-name>MyFilter</filter-name>
    <filter-class>com.zephon.MyFilter</filter-class>
    </filter>
    <!-- 配置过滤路径 -->
    <filter-mapping>
    <filter-name>MyFilter</filter-name>
    <url-pattern>/admin/*</url-pattern>
    </filter-mapping>

Filter的生命周期

  1. 构造器方法
  2. init()初始化方法
  3. doFilter()过滤方法
  4. destroy()销毁方法

其中,1、2在Web工程启动时执行;3是每次拦截到请求时执行;4是停止Web工程时执行并真正销毁Filter

FilterConfig接口

  • FilterConfig是Filter过滤器的配置相关接口
  • Tomcat每次创建Filter时,也会同时创建一个FilterConfig实现类,包含了Filter配置文件的配置信息
  • 作用:获取Filter过滤器的配置内容
    1. 获取Filter名称:config.getFilterName();
    2. 获取Filter的初始化参数:config.getInitParameter(String name);
    3. 获取ServletContext对象;config.getServletContext();

FilterChain接口

Filter中的doFilter()中传递的参数的最后一个参数就是一个实现了FilterChain接口的实例,表示过滤器链 FilterChain中doFilter()方法的作用:

  1. 如果后面还有Filter,则执行下一个Filter
  2. 如果没有Filter了,则执行访问目标资源

多个Filter的执行过程:

  1. 客户端发起请求
  2. Filter1执行doFilter()中的chain1.doFilter()之前的代码
  3. 执行chain1.doFilter(),通过chain1.doFilter()调用Filter2的doFilter()
  4. 执行Filter2中doFilter()的chain2.doFilter()之前的代码
  5. 执行chain2.doFilter(),并通过chain2.doFilter()调用Servlet中对应的doXxx()方法请求资源
  6. Filter2中执行chain2.doFilter()之后的代码
  7. Filter1中执行chain1.doFilter()之后的代码
  8. 返回结果给客户端 > 多个Filter过滤器执行时,执行的优先顺序是由web.xml中从上到下的配置顺序决定

多个Filter过滤器执行的特点:

  1. 所有Filter和目标资源默认都在同一个线程中执行
  2. 多个Filter都使用同一个Request对象

Filter的拦截路径配置

  • 精确匹配

    1
    2
    <!-- 表示请求地址必须是:http://ip:port/工程路径/target.jsp才会被拦截 -->
    <url-pattern>/target.jsp</url-pattern>

  • 目录匹配

    1
    2
    <!-- 表示请求地址必须是:http://ip:port/工程路径/admin/*才会被拦截 -->
    <url-pattern>/admin/*</url-pattern>

  • 后缀名匹配(不能以/开头)

    1
    2
    <!-- 表示请求地址必须以.html结尾才会被拦截 -->
    <url-pattern>*.html</url-pattern>
    > 注:过滤器只关心请求地址是否匹配,不关心请求资源是否存在

Listener监听器

  1. Listener监听器是JavaWeb的三大组件之一
  2. 监听器的作用:监听某种事物的变化,通过回调函数反馈给用户(或程序)做一些相应的处理

继承关系:

  • java.util.EventListener接口:是一个标识接口,表明是一个事件监听器
    • javax.servlet.ServletContextListener接口,监听ServletContext对象的创建和销毁,监听到之后会分别调用内部对应的方法
    • HttpSessionListener接口:监听HttpSession对象的创建和销毁的过程
    • ServletRequestListener接口:监听ServletRequest对象的创建和销毁的过程
    • ServletContextAttributeListener接口:监听ServletContext中属性的创建(add)、修改(replaced)和销毁(removed)
    • HttpSessionAttributeListener接口:监听HttpSession中属性的创建、修改和销毁
    • ServletRequestAttributeListener接口:监听ServletRequest中属性的创建、修改和销毁
    • HttpSessionBindingListener接口:监听某个对象在Session域中的创建与移除
    • HttpSessionActivationListener接口:监听某个对象在Session域中的序列化与反序列化
      1
      2
      3
      4
      public interface ServletContextListener extends EventListener {
      void contextInitialized(ServletContextEvent var1); // 在ServletContext对象创建之后调用
      void contextDestroyed(ServletContextEvent var1); // 在ServletContext对象销毁之后调用
      }

使用ServletContextListener监听器

  1. 编写一个类去实现ServletContextListener接口

  2. 实现两个回调方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
    System.out.println("ServletContext被创建");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
    System.out.println("ServletContext被销毁");
    }
    }

  3. 在web.xml中配置监听器或者用注解@WebListener

    1
    2
    3
    4
    <!-- 配置监听器 -->
    <listener>
    <listener-class>com.zephon.MyListener</listener-class>
    </listener>

  • Cookie是服务器通知客户端保存键值对的一种技术
  • 客户端有了Cookie后,每次请求都发送给服务器
  • 每个Cookie的大小不能超过4KB

Cookie的创建

  • Cookie cookie = new Cookie(String key, String value); // 实例化Cookie
  • resp.addCookie(Cookie cookie); // 通知浏览器保存Cookie
    1
    2
    3
    4
    // 1.创建Cookie对象
    Cookie cookie = new Cookie("key1", "value1");
    // 2.通知客户端保存Cookie
    resp.addCookie(cookie);
    > 注:创建Cookie可创建多个,将上述代码重复多次即可

Cookie的获取

  • Cookie[] cookies = req.getCookies(); // 返回所有Cookie
  • cookie.getName(); // 返回Cookie的key
  • cookie.getValue(); // 返回Cookie的值

Cookie的修改

方案一:

  1. 创建一个同名的Cookie对象

  2. 在构造器同时赋予新的Cookie值

  3. 调用resp.addCookie(cookie);

    1
    2
    Cookie cookie = new Cookie("key1", "newvalue");
    resp.addCookie(cookie);
    方案二:

  4. 找到需要修改的Cookie对象

  5. 调用setValue()方法赋予新的Cookie值

  6. 调用resp.addCookie(cookie);

    1
    2
    3
    4
    5
    6
    7
    Cookie[] cookies = req.getCookies();
    for (Cookie cookie : cookies) {
    if("key1".equals(cookie.getName())){
    cookie.setValue("newvalue");
    resp.addCookie(cookie);
    }
    }

Cookie的生命控制

  • cookie.setMaxAge(int maxAge); // 设置cookie的最大生存时间,以秒为单位,默认值为-1
    • maxAge为正数表示在指定秒数后过期
    • maxAge为负数表示浏览器关闭则Cookie被删除
    • maxAge为0时表示马上删除Cookie

Cookie的path属性

  • Cookie的path属性可以有效过滤哪些Cookie可以发送给服务器,哪些不发
  • path属性是通过请求的地址来进行有效地过滤
  • 如CookieA的path为path=/工程路径,CookieB的path为path=/工程路径/abc,则当请求地址为htttp://ip:port/工程路径/index.html时,CookieA发送,CookieB不发送,当请求地址为http://ip:port/工程路径/abc/index.html时,两个Cookie都发送
  • cookie.setPath(String uri); // 设置path
    • 默认是当前请求路径
    • 例:cookie.setPath(req.getContextPath() + "/abc");

Session

  1. HttpSession session = req.getSession();获取当前会话,没有则创建一个新的会话
  2. HttpSession session = req.getSession(boolean b);如果b为true,则和无参构造一样;如果b为false则返回null,不会创建新的会话
  • HttpSession
    • session.getId();获取sessionID,每个会话的ID是唯一的
    • session.isNew();判断当前session是否是新创建的
    • session.getMaxInactiveInterval();获得session的超时时间,超过指定的时长后Session就会被销毁,
    • session.setMaxInactiveInterval(int i);设置session的非激活间隔时长,单位是秒,默认1800秒(30分钟),tomcat的配置文件web.xml中有中有设置为30
      • 正数表示超时时长
      • 负数表示永不超时(极少使用) > 注:Session中的超时,指的是客户端两次请求的最大间隔时长
    • session.invalidate();使得会话立即(超时)失效(退出用户时使用)
    • session.setAttribute(String key, Object value);向当前Session保存作用域保存数据,key:数据关键字,value:数据值
    • session.getAttribute(String key);根据数据关键字从当前Session保存作用域中获取数据值
    • session.removeAttribute(String key);根据数据关键字从当前Session保存作用域中删除对应的数据

Session的技术与Cookie的联系

  • 服务器每次创建Session会话时,都会创建一个Cookie对象,这个Cookie对象的key永远是"JSESSIONID",value是新创建的Session的id值,然后通过响应将新创建的Cookie返回给客户端
  • 浏览器收到数据后,创建Cookie
  • 之后的每次请求,浏览器就会将Cookie携带上,即后面每次请求都会把Session的id以Cookie的形式发送服务器
  • 然后服务器getSession()时就可以通过客户端发送的session的id找到对应的Session对象并返回
  • 因此,如果浏览器将Cookie删除了,或者没有携带上,则服务器就会重新创建一个新的Session

保存作用域

原始情况下,保存作用域可以认为有4个:page(页面级别,现在几乎不用),request、session、application(ServletContext)

  • request:一次请求响应的范围
  • session:一次会话的范围
  • application:整个应用程序范围有效

Thymeleaf

Thymeleaf:视图模板技术

  1. 导入对应的包:

    1
    2
    3
    4
    5
    6
    <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf -->
    <dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.0.11.RELEASE</version>
    </dependency>

  2. web.xml中配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- 配置上下文 -->
    <context-param>
    <!-- 配置前缀 -->
    <param-name>view-prefix</param-name>
    <param-value>/WEB-INF/view/</param-value>
    </context-param>
    <context-param>
    <!-- 配置后缀 -->
    <param-name>view-suffix</param-name>
    <param-value>.html</param-value>
    </context-param>

  3. 新建一个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
    70
    71
    72
    73
    74
    75
    76
    77
    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 {

    // 1.获取ServletContext对象
    ServletContext servletContext = this.getServletContext();

    // 2.创建Thymeleaf解析器对象
    ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);

    // 3.给解析器对象设置参数
    // ①HTML是默认模式,明确设置是为了代码更容易理解
    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");

    // 4.创建模板引擎对象
    templateEngine = new TemplateEngine();

    // 5.给模板引擎对象设置模板解析器
    templateEngine.setTemplateResolver(templateResolver);

    }

    /**
    * 处理模板
    * thymeleaf会将这个逻辑视图名称对应到物理视图名称
    * 逻辑视图名称: index
    * 物理视图名称:view-prefix+逻辑视图名称+view-suffix
    * @param templateName 视图名称
    * @param req 请求
    * @param resp 响应
    * @throws IOException
    */
    protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
    // 1.设置响应体内容类型和字符集
    resp.setContentType("text/html;charset=UTF-8");

    // 2.创建WebContext对象
    WebContext webContext = new WebContext(req, resp, getServletContext());

    // 3.处理模板数据
    templateEngine.process(templateName, webContext, resp.getWriter());
    }
    }

  4. 自定义类继承自ViewBaseServlet

基本语法

  • ${变量名}:表示变量名对应的变量值
    • ${}表达式可以从以下元素开始:
      • 访问属性域的起点
        • 请求域属性名
        • session
        • application
      • param
      • 内置对象
        • #request
        • #session
        • #lists
        • $strings
  • @{字符串}:在字符串前附加上下文路径,对于带参数的如:/order/process?id=123,type=a,可用@{order/process(id=\({id}, type=\){type})}表示

常用标签

th:表示名称空间,是在html标签中引入的:

](http://www.thymeleaf.org">)

  • th:text:设置标签文本值
  • th:value:设置指定属性值
  • th:if:分支判断
  • th:unless:否则
  • th:switch:分支
  • th:each:循环,如:
  • th:href:设置链接,以项目路径作为根路径
  • th:insert:将目标的代码片段整个插入到当前标签内部
  • th:replace:用目标的代码替换当前标签
  • th:include:把目标的代码片段去除最外层标签,然后再插入到当前标签内部

内置对象

基本内置对象

  • #ctx
  • #vars
  • #locale
  • #request:HttpRequest对象
  • #response:HttpResponse对象
  • #session:HttpSession对象
  • #servletContext:ServletContext对象

公共内置对象

  • #conversion
  • #dates
  • #calendars
  • #numbers
  • #strings
  • #objects
  • #bools
  • #arrays
  • #lists:List相关方法
  • #sets:Set相关方法
  • #maps:Map相关方法
  • #aggregates
  • #ids

从Servlet到Spring过渡

Bilibili:手写SpringMVC和IOC IOC-控制反转:

  1. Spring前,定义:UserService userService = new UserServiceImpl();这如果出现在控制器某个方法内部则userService的作用域(生命周期)就是方法级别;如果出现在控制器类中,则userService是一个成员变量,其作用域(生命周期)与控制器一样
  2. Spring后,在applicationContext.xml中定义了userService,然后通过解析XML,产生的UserServiceImpl()实例,存放在beanMap中,而这个beanMap则在一个BeanFactory中。因此,转移(改变)了之前的Service实例、DAO实例等它们的生命周期,其生命周期控制权从程序员转移到了BeanFactory,这个现象就是所谓的控制反转

DI-依赖注入:

  1. Spring前,控制层定义:UserService userService = new UserServiceImpl();则控制层和Service层存在耦合,同理,Service层和DAO层也存在耦合
  2. Spring后,控制层定义:UserService userService = null; 然后在配置文件中配置bean,如:
    1
    2
    3
    <bean id="user" class="com.zephon.controller.FruitController">
    <property name="userService" ref="userService"/>
    </bean>

自己简单手写SpringMVC(DispatcherServlet类)

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
@WebServlet("*.do")
public class DispatcherServlet extends ViewBaseServlet {
// 定义beanMap用来存放Bean中各个对象,其中key对应name,value对应实例化的对象
private Map<String, Object> beanMap = new HashMap<>();
/**
* 在init方法中解析applicationContext.xml文件
*/
public DispatcherServlet() {
try {
// 1. 获取配置文件路径
// ServletContext context = getServletContext();
// String contextConfigLocation = context.getInitParameter("contextConfigLocation");
// System.out.println(contextConfigLocation);
String contextConfigLocation = "applicationContext.xml";
// 2. 获取路径对应的流
InputStream is = getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
// 3. 创建DocumentBuilderFactory
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
// 4. 创建DocumentBuilder对象
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
// 5. 创建Document对象
Document document = documentBuilder.parse(is);
// 获取所有bean标签对应的节点
NodeList beanNodeList = document.getElementsByTagName("bean");
for (int i = 0; i < beanNodeList.getLength(); i++) {
// 获取第i个节点
Node beanNode = beanNodeList.item(i);
if(beanNode.getNodeType() == Node.ELEMENT_NODE){
// 如果是Element节点则强转
Element element = (Element) beanNode;
String beanId = element.getAttribute("id");
String beanClass = element.getAttribute("class");
// 反射创建对象
Object beanObj = Class.forName(beanClass).newInstance();
beanMap.put(beanId, beanObj);
}
}
} catch (ParserConfigurationException | ClassNotFoundException | IllegalAccessException | IOException |
SAXException | InstantiationException e) {
throw new RuntimeException(e);
}
}

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");

// 获取请求路径,作为beanMap的key
String path = req.getServletPath();
String controllerName = path.substring(1, path.lastIndexOf(".do"));
System.out.println(controllerName);
Object beanObj = beanMap.get(controllerName);
// operate表示需要调用什么方法 add/update/delete......
String operate = req.getParameter("operate");
Method[] methods = beanObj.getClass().getDeclaredMethods();

try {
for (Method method : methods) {
System.out.println(method.getName());
if(operate.equals(method.getName())){
// 获取统一请求参数
Parameter[] parameters = method.getParameters();
Object[] paramValues = new Object[parameters.length];
method.setAccessible(true);
for (int i = 0; i < parameters.length; i++) {
if(parameters[i].getType() == HttpServletRequest.class){
paramValues[i] = req;
}else if(parameters[i].getType() == HttpServletResponse.class){
paramValues[i] = resp;
}else{
if(parameters[i].getType() == Integer.class){
paramValues[i] = Integer.valueOf(req.getParameter(parameters[i].getName()));
}else {
paramValues[i] = req.getParameter(parameters[i].getName());
}
}
}
System.out.println(Arrays.toString(paramValues));
// 控制器层方法调用
Object returnObj = method.invoke(beanObj, paramValues);
String returnStr = (String) returnObj;
// 视图处理
if(returnStr!=null && returnStr.startsWith("redirect:")){
String s = returnStr.substring("redirect:".length());
resp.sendRedirect(s);
}else{
super.processTemplate(returnStr, req, resp);
}
return;
}
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}

}
}

自己简单完成依赖注入功能

BeanFactory和ClassPathXmlApplicationContext

1
2
3
4
public interface BeanFactory {
Object getBean(String name);
}

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
public class ClassPathXmlApplicationContext implements BeanFactory{
// 定义beanMap用来存放Bean中各个对象,其中key对应name,value对应实例化的对象
private Map<String, Object> beanMap;
public ClassPathXmlApplicationContext(){
beanMap = new HashMap<>();
try {
String contextConfigLocation = "applicationContext.xml";
// 2. 获取路径对应的流
InputStream is = getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
// 3. 创建DocumentBuilderFactory
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
// 4. 创建DocumentBuilder对象
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
// 5. 创建Document对象
Document document = documentBuilder.parse(is);
// 获取所有bean标签对应的节点
NodeList beanNodeList = document.getElementsByTagName("bean");
for (int i = 0; i < beanNodeList.getLength(); i++) {
// 获取第i个节点
Node beanNode = beanNodeList.item(i);
if(beanNode.getNodeType() == Node.ELEMENT_NODE){
// 如果是Element节点则强转
Element element = (Element) beanNode;
String beanId = element.getAttribute("id");
String beanClass = element.getAttribute("class");
// 反射创建对象
Object beanObj = Class.forName(beanClass).newInstance();
beanMap.put(beanId, beanObj);
}
}
// 组装bean之间的依赖关系
for (int i = 0; i < beanNodeList.getLength(); i++) {
Node beanNode = beanNodeList.item(i);
if(beanNode.getNodeType() == Node.ELEMENT_NODE){
Element element = (Element) beanNode;
String beanId = element.getAttribute("id");
NodeList childNodeList = beanNode.getChildNodes();
for (int j = 0; j < childNodeList.getLength(); j++) {
Node node = childNodeList.item(j);
if(node.getNodeType() == Node.ELEMENT_NODE && "property".equals(node.getNodeName())){
Element e = (Element) node;
String name = e.getAttribute("name");
String ref = e.getAttribute("ref");
Object obj = beanMap.get(beanId);
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, beanMap.get(ref));
}
}
}
}

} catch (ParserConfigurationException | ClassNotFoundException | IllegalAccessException | IOException |
SAXException | InstantiationException | NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
@Override
public Object getBean(String id) {
return beanMap.get(id);
}
}

DispatcherServlet

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
@WebServlet("*.do")
public class DispatcherServlet extends ViewBaseServlet {
private BeanFactory beanFactory;
public DispatcherServlet() {

}

@Override
public void init() throws ServletException {
super.init();
beanFactory = new ClassPathXmlApplicationContext();
}

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");

// 获取请求路径,作为beanMap的key
String path = req.getServletPath();
String controllerName = path.substring(1, path.lastIndexOf(".do"));
System.out.println(controllerName);
Object beanObj = beanFactory.getBean(controllerName);
// operate表示需要调用什么方法 add/update/delete......
String operate = req.getParameter("operate");
Method[] methods = beanObj.getClass().getDeclaredMethods();

try {
for (Method method : methods) {
System.out.println(method.getName());
if(operate.equals(method.getName())){
// 获取统一请求参数
Parameter[] parameters = method.getParameters();
Object[] paramValues = new Object[parameters.length];
method.setAccessible(true);
for (int i = 0; i < parameters.length; i++) {
if(parameters[i].getType() == HttpServletRequest.class){
paramValues[i] = req;
}else if(parameters[i].getType() == HttpServletResponse.class){
paramValues[i] = resp;
}else{
if(parameters[i].getType() == Integer.class){
paramValues[i] = Integer.valueOf(req.getParameter(parameters[i].getName()));
}else {
paramValues[i] = req.getParameter(parameters[i].getName());
}
}
}
System.out.println(Arrays.toString(paramValues));
// 控制器层方法调用
Object returnObj = method.invoke(beanObj, paramValues);
String returnStr = (String) returnObj;
// 视图处理
if(returnStr!=null && returnStr.startsWith("redirect:")){
String s = returnStr.substring("redirect:".length());
resp.sendRedirect(s);
}else{
super.processTemplate(returnStr, req, resp);
}
return;
}
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}

}
}

使用监听器编写ContextLoaderListener进一步改进

  1. 将ClassPathXmlApplicationContext中的配置文件地址提取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public ClassPathXmlApplicationContext(){
    this("applicationContext.xml");
    }
    public ClassPathXmlApplicationContext(String path){
    if(path==null || "".equals(path)){
    throw new RuntimeException("配置文件有错误");
    }
    beanMap = new HashMap<>();
    try {
    // 2. 获取路径对应的流
    InputStream is = getClass().getClassLoader().getResourceAsStream(path);
    ......

  2. 将beanFactory实例过过程放到监听器中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @WebListener
    public class ContextLoaderListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
    ServletContext servletContext = servletContextEvent.getServletContext(
    String path = servletContext.getInitParameter("contextConfigLocation")
    BeanFactory beanFactory = new ClassPathXmlApplicationContext(path);
    servletContext.setAttribute("beanFactory", beanFactory);
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
    }

  3. 修改DispatcherServlet中的init方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Override
    public void init() throws ServletException {
    super.init();
    beanFactory = new ClassPathXmlApplicationContext();
    ServletContext servletContext = getServletContext();
    Object obj = servletContext.getAttribute("beanFactory");
    if(obj!=null){
    beanFactory = (BeanFactory) obj;
    }else{
    throw new RuntimeException("IOC容器获取失败");
    }
    }

使用监听器完成自定义的编程式事务管理

ConnectionUtils:

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
public class ConnectionUtils {
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
public static Connection createConnection() throws SQLException, IOException, ClassNotFoundException {
InputStream is = ConnectionUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
Class.forName(properties.getProperty("driverClass"));
return DriverManager.getConnection(properties.getProperty("url"),
properties.getProperty("user"), properties.getProperty("password"));
}
public static Connection getConnection() throws SQLException, IOException, ClassNotFoundException {
Connection connection = threadLocal.get();
if(connection==null){
connection = createConnection();
threadLocal.set(connection);
}
return threadLocal.get();
}

public static void closeConnection() throws SQLException {
Connection connection = threadLocal.get();
if(connection!=null){
if(!connection.isClosed()) {
connection.close();
threadLocal.set(null);
}
}
}
}
TransactionManager:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 未考虑事务嵌套等负责情况
public class TransactionManager {
public static void beginTransaction() throws SQLException, IOException, ClassNotFoundException {
ConnectionUtils.getConnection().setAutoCommit(false);
}
public static void commit() throws SQLException, IOException, ClassNotFoundException {
ConnectionUtils.getConnection().commit();
ConnectionUtils.closeConnection();
}
public static void rollback() throws SQLException, IOException, ClassNotFoundException {
ConnectionUtils.getConnection().rollback();
ConnectionUtils.closeConnection();
}
}
OpenSessionInViewFilter:
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
@WebServlet("*.do")
public class OpenSessionInViewFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain){
try {
TransactionManager.beginTransaction();
TransactionManager.commit();
} catch (SQLException | IOException | ClassNotFoundException e) {
e.printStackTrace();
try {
TransactionManager.rollback();
} catch (SQLException | IOException | ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
}

@Override
public void destroy() {

}
}