狂神说--阶段四--javaWeb

目录

1. 基本概念

1.1 前言

web开发:

  • web网页,从互联网中可以拿到一些资源
  • 静态web
    • 提供所有人看的数据始终不会发生变化,如html,css
  • 动态web
    • 每个人在不同的事件,不同的地点看到的信息不同,如淘宝主页
    • 技术栈 Servlet/JSP,ASP,PHP
      在java中动态web资源开发的计数称为javaWeb

1.2 web应用程序

web应用程序可以提供浏览器访问的程序

  • a.html,b.html...多个web资源整合在一起可以对外界提供服务
  • 我们所访问的任何一个页面或资源一定存在某地的计算机上
  • URL定位到该计算机
  • 这个同一的web资源会被放在同一个文件夹下,web应用程序--Tomcat:服务器
  • 一个web应用由多部分组成
    • html,css,js
    • jsp,servlet
    • java程序
    • jar包
    • 配置文件(properties,xml等等)
      web应用程序需要服务器统一管理,提供给外界访问

1.3 静态web

  • .htm,html都是网页后缀,服务器中存在这些东西可以直接读取
  • 静态web缺点
    • 不能动态更新,所有用户看到一个样子
      • 为了好看实现伪动态,使用轮播图,点击特效
      • javaScript(用的多)
      • VBScript
    • 无法和数据库交互

1.4 动态web

web页面展示因人而异

缺点:

  • 假如服务器动态web资源出现错误,我们需要重新编写后台程序,重新发布(意味着需要停机维护)
  • 可以动态更新,所有用户看到的不同
  • 可以与数据库交互(数据持久化:注册信息,商品信息可以保存到数据库)
    (动态的web实际上就是java程序,可以拥有更多逻辑判断,更多的数据库交互,实现每个用户页面不同,二静态页面html是固定的展示。)

2 web服务器

2.1 技术讲解

  • ASP:
    • 微软,国内最早流行的就是ASP
    • 在HTML页面中中嵌入VB脚本,ASP+COM
    • 基本上一个页面中都有上千行业务代码,维护成本高
  • PHP:
    • 开发速度快,功能强大,跨平台,代码简单(70%)
    • 无法承载大访问量的情况(局限性)
  • JSP/Servlet:
    • sun公司主推的B/S架构
    • 基于java语言(所有大公司,或者一些开源的组件,都是用java写的)
    • 可以承载三高问题带来的问题
    • 语法像ASP,加强市场强度

2.2 web 服务器

服务器是一种被动的操作,用来处理用户的一些请求,给用户一些响应信息
一些知名的服务器
IIS
微软的服务器:ASP 。。。windows中自带的
Tomcat
Tomcat是apache的核心项目,实际上运行的是JSP页面和Servlet。属于轻量级应用服务器,最新版本为9.0
下载Tomcat:

  1. 安装or解压
  2. 了解配置文件及目录结构
  3. 这个东西的作用

3. Tomcat

3.1 安装Tomcat

在官网下载64 bit安装包
https://tomcat.apache.org/download-90.cgi
解压后直接可以用了

3.2 Tomcat启动和使用

  • 文件夹作用
  • 启动,关闭Tomcat
    • windows下,双击startup.bat文件启动Tomcat
    • 访问测试 http://localhost:8080/
    • 双击shutdown.bat关闭或者叉掉启动的窗口
      可能遇到的问题:
    1. java 环境变量没有配置
    2. 闪退问题:需要配置兼容性
    3. 乱码问题:配置文件中设置

3.3 配置

conf目录下的server.xml是服务器核心配置文件

  • 可以配置启动的端口号
    tomcat默认端口是8080,mysql默认3306
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
  • 可以配置主机的名称
    默认的主机名为localhost->127.0.0.1
    默认网站应用存放的位置为:webapps
    webapps里每一个目录都是一个应用
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
  • 扩展面试题:
    请你谈谈网站是如何进行访问的?
    1.输入一个域名->回车
    2.检查本机的C:\Windows\System32\drivers\etc\hosts配置文件下有没有这个域名映射:
    • 有:直接返回对应的ip地址
      127.0.0.1 www.xiaoming.com
    • 没有:去DNS服务器上找,找到的话就返回,找不到就返回找不到

3.4 发布一个web网站

不会就先模仿

  • 将自己写的网站,放到服务器(Tomcat)中指定的web应用的文件夹(webapps)下,就可以访问了
    网站该有的结构

HTTP协议:面试
Maven:构建工具

  • Maven安装包
    Servlet入门
  • HelloWorld
  • Servlet配置
  • 原理

4. Http

4.1 什么是HTTP

超文本传输协议(Hypertext Transfer Protocol,HTTP)是一个简单的请求-响应协议,它通常运行在TCP之上。

  • 文本:html,字符串,...
  • 超文本:图片,音乐,视频定位,地图...

4.2 两个时代

  • http 1.0
    • HTTP/1.0 客户端和web服务器连接后,只能获得一个web资源,就断开连接了
  • http2.0
    • HTTP/1.1 客户端和web服务器连接后,可以获得多个web资源

4.3 HTTP请求

  • 客户端--发送请求(request)--服务器
Request URL: https://www.baidu.com/ 请求地址
Request Method: GET               get/post方法
Status Code: 200 OK              状态码:200
Remote Address: 220.181.38.149:443

请求头

Accept: text/html
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9  语言
Connection: keep-alive
  1. 请求行
  • 请求行中的请求方式:GET
  • 请求方式:GET/POST,HEAD,DELETE,PUT,TRACT...
    • get:请求能够携带的参数少,大小有限制,会在浏览器URL行中显示参数,不安全,但高效
    • post:请求携带的参数量及大小没有限制,不会在浏览器URL中显示参数,安全,但不高效
  1. 消息头
Accept: 告诉浏览器,它所支持的数据类型
Accept-Encoding: 支持那种编码格式,GBK,UTF-8 GB2312 ISO8859-1
Accept-Language: 告诉浏览器它的语言环境
Cache-Control:  缓存控制
Connection: 告诉浏览器,请求完成时断开还是保持连接
HOST:主机...

4.4 HTTP响应

响应头

Cache-Control: private    缓存控制
Connection: keep-alive    保持连接
Content-Encoding: gzip    编码
Content-Type: text/html;charset=utf-8 ;类型
  1. 响应体
Accept: 告诉浏览器,它所支持的数据类型
Accept-Encoding: 支持那种编码格式,GBK,UTF-8 GB2312 ISO8859-1
Accept-Language: 告诉浏览器它的语言环境
Cache-Control:  缓存控制
Connection: 告诉浏览器,请求完成时断开还是保持连接
HOST:主机...
Refresh:告诉浏览器多久刷新一次
Location:让网页重新定位
  1. 响应状态码
    200:请求响应成功
    3xx: 请求重定向
    4xx:代表找不到资源 404
    5xx: 服务器代码错误 500, 502网关错误
    常见面试题:
    当你的浏览器中地址栏输入地址并回车的一瞬间到页面能够展示回来,经历什么?

5. Maven

我为什么要学习这个技术?

  1. 在javaweb开发中,需要使用大量的jar包,我们需要手动导入
  2. 如何能够让一个东西自动帮我们导入和配置这个jar包
    由此,Maven诞生了

5.1 Maven项目架构管理工具

我们目前用来就是方便导入jar包的
Maven的核心思想:约定大于配置

  • 有约束,不要违反
    Maven会规定好你该如何取编写我们的Java代码,必须要按照这个规范来

5.2 下载安装Maven

  • 去官网https://maven.apache.org/ 下载zip版本

  • 解压(建议电脑上所有环境都放在一个文件夹下)

  • 配置环境变量
    在系统环境变量中做如下设置

    • 增加系统变量
      • M2_HOME: Maven安装目录下的bin目录(后面学习的springboot等都会自动引用M2_HOME)
      • MAVEN_HOME: Maven的目录
    • 在系统path中配置%MAVEN_HOME%\bin
    • 终端里输入mvn -version,可以输出正确的maven版本就可以了

5.3 阿里云镜像

maven解压包内conf/settings.xml为配置文件
其中镜像mirrors作用: 配置文件中mirrors 方便下载使用,Maven国外的国内有墙,访问会慢,VPN
我们国内建议使用阿里云镜像(百度搜索maven 阿里云镜像)

 <mirror>
      <id>nexus-aliyun</id>
      <mirrorOf>*,!jeecg,!jeecg-snapshots</mirrorOf>
      <name>Nexus aliyun</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public</url> 
 </mirror>

5.4 本地仓库

在本地的仓库,远程仓库
配置文件里配置建立的一个本地的仓库

<!--如果不手动配置本地仓库,则默认启动maven后存储在${user.home}/.m2/repository
<localRepository>D:\Environment\apache-maven-3.6.2\maven-repo</localRepository>

5.5 IDEA里使用Maven

方法一:使用maven模板
详情参考操作步骤可以参考
别人的kuangshen笔记

  1. 启动IDEA
  2. 创建一个Maven web项目
  • 配置java安装目录,选择maven模板maven-archetype-webapp

  • next后填写GAV,组ID(一般是公司域名),项目名,版本

  • next后填写安装的maven home,用户配置文件位置,本地仓库位置

  • next 项目名,项目存放位置
    3.等待项目初始化完毕
    下载各种jar包,控制台完成时输出BUILD SUCCESS,说明项目搭建成功

4.观察maven-repo多了很多文件夹jar包
5. 配置完后可以 在settings 里检查下Maven配置是否正确
其中maven home path是重点检查项,可能IDEA会出现错误。

Importting里自动下载源码项可以不勾选
到这里使用模板配置idea maven就完成了,其中有可能初夏一些jar下载不下来的情况,如junit

方法二 创建一个普通maven
new project时 不勾选模板create from archetype,直接next

此时创建一个干净的maven项目
项目目录如下:

5.6 在项目中标记目录功能

在刚刚创建的web项目里main下手动创建src和resource目录,然后右键->Mark directory as->分别设置为源代码目录和资源目录
使用webapp模板创建的目录如下

方法一 目录上右键标记功能

方法二 在项目结构配置中标记功能

5.7 在IDEA中配置Tomcat

  • 右上角edit configuration

  • 选中Tomcat
  • 注意Tomcat配置端口,jre版本和warning
  • 必须要配置:为什么会有这个问题,我们访问一个网站,需要指定一个文件夹的名字(相当于webapp)
    解决警告问题:手动建立artifact,不用点fix war包选项



    安装后启动tomcat

    启动完成后弹出helloworld界面

5.8 pom.xml文件

pom.xml时maven的核心配置文件

这个界面的内容和pom.xml里是一 一对应的

<?xml version="1.0" encoding="UTF-8"?>
<!--Maven版本与头文件-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
<!--这里就是刚才配置的GAV-->
  <groupId>com.kuang</groupId>
  <artifactId>javaweb-01-maven</artifactId>
  <version>1.0-SNAPSHOT</version>
  <!--这里就是Package:项目的打包方式
  jar:java应用
  war:javaweb应用
  -->
  <packaging>war</packaging>
  <name>javaweb-01-maven Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>
<!--项目的默认构建编码-->
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!--编码版本-->
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
<!--项目依赖-->
  <dependencies>
<!--具体依赖的jar包配置文件-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
<!--项目构建用的东西-->
  <build>
    <finalName>javaweb-01-maven</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

Maven的高级之处在于它不仅可以给你导入指定的jar包,还自动把指定的jar包依赖的jar包都导入了

  • Maven由于它的约定大于配置,我们之后可能遇到我们写的配置文件,无法被导出或者生效的问题。
    解决方案:在maven配置文件中build中配置Resource节点,来防止我们导出失败
    参考https://www.cnblogs.com/pixy/p/4798089.html
<build>
    .......
      <resources>
        <resource>
            <directory>src/main/resources</directory>
            <excludes>
                <exclude>**/*.properties</exclude>
                <exclude>**/*.xml</exclude>
             </excludes>
            <filtering>false</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <!--关闭过滤,让导出包含.properties和.xml文件-->
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
    ......
</build>

5.9 IDEA 操作

生成目录树

5.10 解决遇到的问题

  1. Maven 3.6.2 导不了东西,兼容性问题
    unable to import maven project:see logs for details
    解决方法,降低版本使用Maven 3.6.1
    注:IDEA查看日志 Help->Show Log in Explorer->Idea log

  2. Tomcat闪退 JAVA_HOME,JRE_HOME没配置
    Tomcat启动的时候要引用java的路径,如果没有事先配置好,就会出现问题
    可以查看startup.bat->catalina.bat(引用java目录)->在该批处理文件末尾加pause,查看报错

  3. IDEA中每次都要重复配置Maven
    在IDEA中全局默认配置中配置,启动IDEA时点击settings

    在default settings里设置maven选项

  4. maven默认web项目中web.xml头文件版本问题
    默认的文件如下

参考Tomcat 安装路径下ROOT文件夹里的web.xml来改该文件,替换webapp4.0版本和tomcat保持一致

5.11 Maven仓库的使用

  1. 在tomcat的安装路径下webapps/examples里有很多现成的例子,初学可以直接模仿学习
    浏览器访问http://localhost:8080/examples/
  2. 在写helloworld程序中发现httpServlet没有导入依赖,可以alt+回车 添加maven依赖,如果本地库里有该包就直接能查找添加了
    本地没有去访问Maven的远程仓库https://mvnrepository.com/,搜索对应的jar包,选择使用做的的,然后版本可以选用的多又相对较新的

    把依赖添进pom.xml里

web.xml中式配置我们web核心应用,注册servlet

6.Servlet

6.1 Servlet简介

  • Servlet就是sun公司开发动态web的一门技术
  • Sun在这些API中提供一个接口叫做Servlet,具体程序分成两个步骤
    • 编写一个类实现Servlet接口
    • 把开发好的java类部署到web服务器中
      把实现了Servlet接口的java程序叫做servlet

6.2 HelloServlet

HttpServlet是sun公司提供的Servlet的是实现类

  1. new project 构建一个普通的Maven项目,删除里面的src目录,以后我们的学习就在这个项目里建立Module,这个空的工程就是Maven主工程
  • 扩展.iml文件和pom.xml的区别
    • .iml是IDEA构建Maven项目自己创建的,里面有一些module的信息的(information of module),依赖信息模块路径等等
    • pom.xml是Maven自己的配置文件
  1. 关于Maven父子工程的理解
    父项目中会有module标签指向子项目
     <modules>
         <module>servlet-01</module>
     </modules>

子项目中会有partent标签:

    <parent>
        <artifactId>javaweb-02-servlet</artifactId>
        <groupId>com.kuang</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

父项目中的java 子项目都可以直接使用
3. 把web.xml换成最新的,子项目下建立java(标记为sources root)和resource目录(标记为resources root)
4. 编写servlet
HttpServlet继承GenericServlet(实现Servlet接口),在HttpServlet的service里最终根据不同的request类型来判断调用doGet还是doPost

public class HelloServlet extends HttpServlet {
    
    //由于get或者post只是请求实现的不同的方式,可以相互调用,业务逻辑都一样;
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //ServletOutputStream outputStream = resp.getOutputStream();
        PrintWriter writer = resp.getWriter(); //响应流
        writer.print("Hello,Serlvet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //相互调用,不用写两份同样代码
        doGet(req, resp);
    }
}
  1. 编写Servlet的映射
    为什么需要映射:我们写的是java程序,通过浏览器访问需要连接web服务器,所以需要在web服务器中注册我们写的Servlet,从而给浏览器一个能够访问的路径
<!--注册Servlet-->
<servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.kuang.servlet.HelloServlet</servlet-class>
</servlet>
<!--Servlet的请求路径-->
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>
  1. 配置Tomcat


    7.启动测试
    参考 https://blog.csdn.net/bell_love/article/details/105667638#1_4

6.3 Servlet原理

6.4 Mapping问题

1.一个servlet可以映射一个或多个路径,也可以使用通配符

<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
	<!--<url-pattern>/hello/hello1</url-pattern>-->
	<!--<url-pattern>/hello/*</url-pattern>-->
</servlet-mapping>

2.使用自定义路径(指定一些前缀,后缀)*前面不能加项目映射的路径,

  <!--可以自定义后缀实现请求映射
      注意点,*前面不能加项目映射的路径
      hello/sajdlkajda.qinjiang
      -->
  <servlet-mapping>
      <servlet-name>hello</servlet-name>
      <url-pattern>*.qinjiang</url-pattern>
  </servlet-mapping>
  1. 指定了固有的映射路径优先级最高,如果找不到就会走默认的处理请求,即由精细到模糊匹配
 <!--404-->
  <servlet>
      <servlet-name>error</servlet-name>
      <servlet-class>com.kuang.servlet.ErrorServlet</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>error</servlet-name>
      <url-pattern>/*</url-pattern>
  </servlet-mapping>

6.5 SevletContext

web容器在启动的时候,它会为每个web程序都创建一个对应的ServletContext对象(上下文语境,例携带用户登录信息),它代表了当前的web应用.
可以用来共享数据,获取初始化参数,请求转发,和加载资源文件(但是总体来说ServletContext不常用,后面session等会取代其功能)

  • 共享数据
    我在这个Servlet中保存的数据,可以在另一个servlet中读取(ServletContext唯一)
    例helloServlet设置ServletContext的attribute数据,另一个servlet取数据
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
        //this.getInitParameter()   初始化参数
        //this.getServletConfig()   Servlet配置
        //this.getServletContext()  Servlet上下文
        ServletContext context = this.getServletContext();

        String username = "秦疆"; //数据
        context.setAttribute("username",username); //将一个数据保存在了ServletContext中,名字为:username 。值 username

    }

}

取数据

public class GetServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        String username = (String) context.getAttribute("username");

        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");
        resp.getWriter().print("名字"+username);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

对应的web.xml配置

 <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>com.kuang.servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>getc</servlet-name>
        <servlet-class>com.kuang.servlet.GetServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>getc</servlet-name>
        <url-pattern>/getc</url-pattern>
    </servlet-mapping>

测试时先访问helloServlet

  • 也可以在配置文件中设置ServletContext对应的初始化参数
    web.xml里设置context-param
   <!--配置一些web应用初始化参数-->
    <context-param>
        <param-name>url</param-name>
        <param-value>jdbc:mysql://localhost:3306/mybatis</param-value>
    </context-param>

servlet里取

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    ServletContext context = this.getServletContext();
    String url = context.getInitParameter("url");
    resp.getWriter().print(url);
}
  • 请求转发
    请求转发url地址不变,重定向地址也变了
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    ServletContext context = this.getServletContext();
    System.out.println("进入了ServletDemo04");
    //RequestDispatcher requestDispatcher = context.getRequestDispatcher("/gp"); //转发的请求路径
    //requestDispatcher.forward(req,resp); //调用forward实现请求转发;
    context.getRequestDispatcher("/gp").forward(req,resp);
}
  • 读取资源文件
    web应用没办法保证绝对地址,resource路径下文件默认编译后装在target的classes路径
    Properties
    • 在java路径下新建properties文件
    • 在resources目录下新建properties
      发现都被打包到了同一个路径下:classes,我们俗称这个路径为classpath
username=root12312
password=zxczxczxc

加载properties文件/表示项目路径,接着写下面的WEB-INF等其他文件夹即可

public class ServletDemo05 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        InputStream is = this.getServletContext().getResourceAsStream("/WEB-INF/classes/com/kuang/servlet/aa.properties");

        Properties prop = new Properties();
        prop.load(is);
        String user = prop.getProperty("username");
        String pwd = prop.getProperty("password");

        resp.getWriter().print(user+":"+pwd);

    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

6.6 HttpServletResponse

web 服务器接收到客户端的http请求,创建HttpServletRequest请求对象,和HttpsServletResponse对象

  • 如果要获取客户端请求过来的参数,使用HttpServletRequest
  • 想要设置响应信息,使用HttpServletResponse

6.6.1 简单分类

  • 负责向浏览器发送数据的方法
servletOutputstream getOutputstream() throws IOException;
Printwriter getwriter() throws IOException;
  • 负责向浏览器发送响应头的方法
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentLengthLong(long var1);
void setContentType(String var1);
void setDateHeader(String varl,long var2)
void addDateHeader(String var1,long var2)
void setHeader(String var1,String var2);
void addHeader(String var1,String var2);
void setIntHeader(String var1,int var2);
void addIntHeader(String varl,int var2);
  • 响应的状态码

6.6.2 常见应用

  • 向浏览器输出消息(略,getWritter)
  • 下载文件
    1. 要获取下载文件的路径
    2. 下载的文件名是啥
    3. 设置浏览器支持下载需要的东西
    4. 获取下载文件的输入流
    5. 创建缓冲区
    6. 获取OutputStream对象
    7. 将FileInputStream流写入到buffer缓冲区,再将缓冲区数据输出到客户端
      本质上还是IO流,只是取得输出流是响应里获得的,同时还要设置响应头,使其支持下载
      resp.setHeader("Content-disposition","attachment;filename"+ URLEncoder.encode(filename));
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 1. 要获取下载文件的路径,此处tomcat的安装路径和target路径不一致,会导致获取不到图片,后面学使用相对路径,
    String realPath = this.getServletContext().getRealPath("/1.png");
    System.out.println("下载文件的路径:"+realPath);
    // 2. 下载的文件名是啥?截取最后一个斜杠后面的名字
    String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);

    // 3. 设置想办法让浏览器能够支持(Content-Disposition)下载我们需要的东西,中文文件名URLEncoder.encode编码,否则有可能乱码
    resp.setHeader("Content-Disposition","attachment;filename="+URLEncoder.encode(fileName,"UTF-8"));

    // 4. 获取下载文件的输入流
    FileInputStream in = new FileInputStream(realPath);
    // 5. 创建缓冲区
    int len = 0;
    byte[] buffer = new byte[1024];
    // 6. 从响应里,获取OutputStream对象
    ServletOutputStream out = resp.getOutputStream();
    // 7. 将FileInputStream流写入到buffer缓冲区,使用OutputStream将缓冲区中的数据输出到客户端!
    while ((len=in.read(buffer))>0){
        out.write(buffer,0,len);
    }

    in.close();
    out.close();
}
  • 验证码功能
    验证怎么来的?
    • 前端实现
    • 后端实现,需要用到java的图片类,生成一个图片
package com.kuang.servlet;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

public class ImageServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //如何让浏览器3秒自动刷新一次;
        resp.setHeader("refresh","3");
        
        //在内存中创建一个图片
        BufferedImage image = new BufferedImage(80,20,BufferedImage.TYPE_INT_RGB);
        //得到图片
        Graphics2D g = (Graphics2D) image.getGraphics(); //笔
        //设置图片的背景颜色
        g.setColor(Color.white);
        g.fillRect(0,0,80,20);
        //给图片写数据
        g.setColor(Color.BLUE);
        g.setFont(new Font(null,Font.BOLD,20));
        g.drawString(makeNum(),0,20);

        //设置图片响应头,告诉浏览器,这个请求用图片的方式打开
        resp.setContentType("image/jpeg");
        //网站存在缓存,不让浏览器缓存
        resp.setDateHeader("expires",-1);
        resp.setHeader("Cache-Control","no-cache");
        resp.setHeader("Pragma","no-cache");

        //把图片写给浏览器
        ImageIO.write(image,"jpg", resp.getOutputStream());

    }

    //生成随机数
    private String makeNum(){
        Random random = new Random();
        String num = random.nextInt(9999999) + "";
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 7-num.length() ; i++) {
            sb.append("0");
        }
        num = sb.toString() + num;
        return num;
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
  • 重定向(重点)

一个web资源收到客户端请求,它会通知客户端去访问另一个web资源(所以是响应里的方法),这个过程就是重定向。例用户登录后重定向到主页

 void sendRedirect(String var1) throws IOException;

测试

@override
protected void doGet(HttpservletRequest req, HttpservletResponse resp) throws ServletException, IOException {

    resp. sendRedirect("/r/img");//重定向
    /*
    resp. setHeader("Location","/r/img");
    resp. setstatus (302);
    *
}

面试题:请求转发和重定向的区别
相同点:
页面都会实现跳转
不同点:
请求转发的时候url不会产生变化
重定向时,url地址栏会发生变化,(重定向写路径要/项目名,加上项目名)

6.7 HttpServletRequest

通过http协议访问服务器所有的信息会被封装到HttpServletRequest,通过该对象的方法我们可以获得客户端的所有信息
应用场景
获取前端传递的参数,请求转发
String getParameter(String s)
String[] getParameterValues(String s) 例多选框
请求转发是server端进行的/已经表示项目目录了,写路径时不需要写项目名

protected void doGet(HttpservletRequest req. HttpservletResponse resp) throws ServletException, IOException {
	req. setcharacterEncoding("utf-8");
	resp.setcharacterEncoding("utf-8");
	String username = req.getParameter("username");
	String password = req.getParameter("password");
	String[] hobbys = req.getParameterValues("hobbys");
	System.out.println("==========");
	//后台接收中文乱码问题
	System. out.println(username);
	System. out.println(password);
	System. out.println(Arrays.tostring(hobbys));
	System. out.println("============");
	system. out.println(req.getContextPath());
	//通过请求转发
	//这里的/代表当前的web应用
	req.getRequestDispatcher("success.jsp").forward(req,resp);

7. Cookie、Session

7.1 会话

会话:用户打开一个浏览器,中间各种操作,到关闭浏览器的过程称之为会话
有状态会话:保存用户信息,下次登录知道用户曾经来过
无状态会话:不保存用户信息
一个网站,怎么证明你来过?
客户端 服务端
1.服务端给客户端一个信件,客户端下次访问服务器带上信件cookie就可以了(类比学校给你发的学生证)
2. 服务器登记你来过了,下次你来的时候,我来匹配你session(类比学校持有的学生登记表)

7.2 保存会话的两种技术

cookie

  • 客户端技术(请求,响应)

session

  • 服务器技术,把信息和数据保存在Session中
    (没有IO做不了的事情)

1.从请求中拿到cookie信息
2.服务器响应给客户端的cookie

Cookie[] cookies = req.getCookies(); //获得Cookie
cookie.getName(); //获得cookie中的key
cookie.getValue(); //获得cookie中的vlaue
new Cookie("lastLoginTime", System.currentTimeMillis()+""); //新建一个cookie
cookie.setMaxAge(24*60*60); //设置cookie的有效期
resp.addCookie(cookie); //响应给客户端一个cookie
  • cookie:一般保存在本地的用户目录下appdata,细节
    • 一个Cookie只能保存一个信息
    • 一个web站点可以发送给浏览器多个cookie,最多存放20个
    • 每个cookie大小限制4K内
    • 浏览器最多存放上限为300个

删除Cookie:

  • 不设置有效期MaxAge,关闭浏览器自动失效
  • 设置有效期为0

编码解码:(如cookies里存中文乱码可以使用)

URLEncoder.encode("秦疆","utf-8")
URLDecoder.decode(cookie.getValue(),"UTF-8")

7.4(重点)

什么是Session:

  • 服务器会给每一个用户(浏览器)创建一个Session对象
  • 一个Session独占一个浏览器,只要浏览器没关,这个Session就存在
  • 用户登录之后,整个网站内所有servlet,它都可以访问-->Session 应用 保存的用户信息,使用session实现数据共享
    例 一个servlet保存session的属性,另一个servelet去取。
    用户请求时携带sesionID的cookie信息,所以从Request里获取session

ackage com.kuang.servlet;

import com.kuang.pojo.Person;

import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.IOException;

public class SessionDemo01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
        //解决乱码问题
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        
        //得到Session
        HttpSession session = req.getSession();
        //给Session中存东西
        session.setAttribute("name",new Person("秦疆",1));
        //获取Session的ID
        String sessionId = session.getId();

        //判断Session是不是新创建
        if (session.isNew()){
            resp.getWriter().write("session创建成功,ID:"+sessionId);
        }else {
            resp.getWriter().write("session以及在服务器中存在了,ID:"+sessionId);
        }

        //Session创建的时候做了什么事情;
//        Cookie cookie = new Cookie("JSESSIONID",sessionId);
//        resp.addCookie(cookie);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

另一个servlet取,略
注销session
1.手动注销

//得到Session
HttpSession session = req.getSession();

Person person = (Person) session.getAttribute("name");

System.out.println(person.toString());

HttpSession session = req.getSession();
session.removeAttribute("name");
//手动注销Session
session.invalidate();

  1. 设置定时注销
    web.xml
<!--设置Session默认的失效时间-->
<session-config>
    <!--15分钟后Session自动失效,以分钟为单位-->
    <session-timeout>15</session-timeout>
</session-config>

Session和cookie的区别:

  • cookie把用户的数据写给用户的浏览器,浏览器保存
  • Session把用户的数据写到用户独占Session中,服务器端保存(保存重要的信息,减少资源的浪费)
  • Session是服务器创建的

Session使用场景:

  • 保存一个登录用户的信息
  • 购物车信息
  • 在整个网站中经常会使用的数据,我们将它保存在Session中

图文解析Cookie,Session, SeveletContext原理

  • Cookie 图
  • Session图(服务器会把SessionID以cookie的形式返回并存储在浏览器中,Session里可以存储用户自己共享的数据)
  • 类似还要SevletContext,代表服务器的环境,可以多用户之间共享数据

8. JSP

8.1 什么是JSP

Java Server Pages: Java服务器端页面,也和Servlet一样,用于动态Web技术
最大特点:

  • 写JSP就像在写HTML
  • 区别:
    • HTML只能提供静态数据
    • JSP 页面中可以嵌入JAVA代码,为用户提供动态数据

8.2 JSP原理

思路: JSP到底怎么执行

  • 代码层面正常网页,没有什么问题
  • 服务器内部工作
    tomcat中有一个work目录:
    IDEA中使用Tomcat会在IDEA中生成一个work目录

    每个web项目在tomcat里都对应一个工作空间目录,里面会有work目录
    电脑中的地址:
    C:\Users\Administrator.IntelliJIdea2018.1\system\tomcat\Unnamed_javaweb-session-cookie\work\Catalina\localhost\ROOT\org\apache\jsp
    发现页面转变成了java程序

    浏览器向服务器发送请求,不管访问什么资源,其实都是在访问Servlet.JSP本质上就是一个servlet
//初始化
  public void _jspInit() {
      
  }
//销毁
  public void _jspDestroy() {
  }
//JSPService
  public void _jspService(.HttpServletRequest request,HttpServletResponse response)
  1. 判断请求 在jspService里
  2. 内置一些对象
final javax.servlet.jsp.PageContext pageContext;  //页面上下文
javax.servlet.http.HttpSession session = null;    //session
final javax.servlet.ServletContext application;   //applicationContext(本质上ServletContext,改了名字)
final javax.servlet.ServletConfig config;         //config
javax.servlet.jsp.JspWriter out = null;           //out 输出对象
final java.lang.Object page = this;               //page:当前页面
HttpServletRequest request                        //请求
HttpServletResponse response                      //响应

3.输出页面前增加的代码
jsp编译自动帮我们做了下面这些事情,这些对象直接可以在jsp页面里面使用

response.setContentType("text/html");       //设置响应的页面类型,jsp可以转换成html
pageContext = _jspxFactory.getPageContext(this, request, response,
       null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;

相对jsp是做了一个封装,方便使用。

java代码<% ****% >中写,<%= 变量表达式 %>表示输出变量或者表达式结果--转为为out.write()

8.3 JSP 基础语法

普通Maven项目->右键 add Frameworks Support-》 Web Application可以添加web支持,此时pom.xml头是新版的。
但仍旧推荐直接创建web项目,目录更标准

<dependencies>
	<!--Servlet依赖-->
	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>servlet-api</artifactId>
		<version>2.5</version>
	</dependency>
	<!--JSP 依赖-->
	<dependency>
		<groupId>javax.servlet.jsp</groupId>
		<artifactId>javax.servlet.jsp-api</artifactId>
		<version>2.3.3</version>
	</dependency>
	<!--JSTL表达式依赖-->
	<dependency>
		<groupId>javax.servlet.jsp.jstl</groupId>
		<artifactId>jstl-api</artifactId>
		<version>1.2</version>
	</dependency>
	<!--standard标签库-->
	<dependency>
		<groupId>taglibs</groupId>
		<artifactId>standard</artifactId>
		<version>1.1.2</version>
	</dependency>
</dependencies>

JSP作为Java技术的一种应用,java所有语法都支持,除此之外还有一些扩充的语法(了解知道即可)
JSTL--JSP标准标签库

1.JSP 表达式
输出:将程序结果输出客户端
<%= 变量表达式 %>表示输出变量或者表达式结果--转为为out.write()
shift+F10->选择update classes and resources,直接热部署更新资源,不需要重启服务器

<%= new java.util.Date()%>

2.脚本片段

<%
  int sum =0;
  for( int i =1; i<=100;i++){
    sum+=i;
  }
out.println("<h1> Sum="+sum+"</h1>");
%>

3. 脚本片段的再实现

<%
  int x 10;
  out.println(x);
%>
<p>这是一个JSP文档</p>
<%
  int y =2;
  out.println(x);//同一个方法里,一个x变量
%>

在代码中嵌入HTML元素,或者说java代码把html包起来,实现原来Html做不到的事情

<%
  for(int i =0;i<5;i++){
%>  
<h1>HELLO WORLD <%=i%> </h1>
<%
  }
%>

Jsp 声明,写在jsp_Service外部,类内的代码
除了声明其他写法,都是写在jsp_Service方法里面

<%!
  static {
  System.out.println("Loading Servlet!");
}
private int gloablVar = 0;
public void say(){
  System.out.println("进入了方法“);
}
%>

总结

<% 片段%>
<%=表达式%>
<%!声明%>
<%--jsp注释--%>  jsp的注释不会在页面查看源码中存在,HTML的会

8.4 JSP指令

8.4.1 定制错误页面

方法1,在jsp里指定错误页面

<%@ page errorPage="error/500.jsp"%>

方法2.在web.xml里统一设置各个error code对应的错误页面

<error-page>
  <error-code>404</error-code>
  <location>/error/404.jsp</location>
</error-page>
<error-page>
  <error-code>500</error-code>
  <location>/error/500.jsp</location>
</error-page>

语法

<%@page args...%>
<%@include file=""%> 把别的网页嵌入当前文件里,如网站页面公用的header,footer信息提取出来进行引用

8.4.2 引入页面的两种写法

 <%--@include,java代码中将两个页面合二为一%>
<%@include file ="common/header.jsp"%>
<h1>网页主体</h1>
<%@include file="coommon/footer.jsp"%>

<%--JSP标签
jsp:include 拼接页面,java代码中不会合并页面,本质还是三个页面,变量重名相互不影响
--%>
<jsp:include page="/common/header.jsp"/>
<h1>网页主体</h1>
<jsp:include page="/common/footer.jsp"%>

8.5 9大内置对象

  • PageContext 存东西
  • Request 存东西
  • Response
  • Session
  • Application [ServletContext] 用来存东西
  • config [ServletConfig]
  • out
  • page 存东西
  • exception
<%
//脚本片段里的内容会原封不动的转化成Java代码,语法遵守java规定,所有注释用//
pageContext.setAttribute("name1","value1");//保存的数据旨在一个页面中有效
request.setAttribute("name2","value2");//保存的数据只在一次请求中有效,请求转发携带这个数据
session.setAttribute("name3","value3");//保证数据在一次session中有效
application.setAttribute("name4","value4");//从打开服务器到关闭服务器期间都有效
%>

<%
//pageContext.getAttribute...略,使用findAtrribute寻找
String name1 = (String)pageContext.findAttribute("name1");
String name2 = (String)pageContext.findAttribute("name2");
String name3 = (String)pageContext.findAttribute("name3");
String name4 = (String)pageContext.findAttribute("name4");
String name5 = (String)pageContext.findAttribute("name5"); //不存在
%>

<%
  pageContext.forward("index.jsp");//转发
  //request.getREquestDispatcher("/index.jsp").forward(request,reponse);
%>

pageContxt.findAttribute寻找方式 page->request->session->application

应用场景
request:客户端向服务端发送请求,产生的数据,用户看就没用了,如新闻
session:客户端向服务端发送请求,产生的数据,用户用完一会还有用,比如 购物车
application:客户端向服务端发送请求,产生的数据,一个用户用完了,其他用户还可以用:比如聊天,统计用户数

8.6 JSP 标签,JSTL标签,EL表达式

导包

	<!--JSTL表达式依赖-->
	<dependency>
		<groupId>javax.servlet.jsp.jstl</groupId>
		<artifactId>jstl-api</artifactId>
		<version>1.2</version>
	</dependency>
	<!--standard标签库-->
	<dependency>
		<groupId>taglibs</groupId>
		<artifactId>standard</artifactId>
		<version>1.1.2</version>
	</dependency>

EL 表达式语言,用来获取表达式的值:${}

  • 获取数据 (从隐式对象page,request,session,application中查找指定name对应的值)
  • 执行运算
  • 获取web开发常用的对象

JSP标签

<%--jsp:include--%>
<%--
http://localhost:8080/jsptag.jsp?name=xiaoming&age=23
--%>
<jsp:forward page ="/jsptag2.jsp">
	<jsp:param name="name" value=”xiaoming“></jsp>
	<jsp:param name="age" value="23"></jsp>
	
</jsp:forward>

在jsptag2.jsp页面取出数据

<%--取出参数--%>
名字:<%=request.getParameter("name")%>
年龄:<%=request.getParameter("age")%>

JSTL表达式
JSTL标签库的使用就是为了弥补HTML标签的不足,它自定义了许多标签,标签功能和java代码一样,即用原有java代码也能实现
格式化标签
SQL标签
核心标签(掌握部分)


JSTL标签使用步骤
1.导入对应的taglib
2.使用其中的方法
3.在Tomcat也需要引入jstl的包,否则会报错:JSTL解析
c:if

<%--1.导入核心库JSTL核心标签库--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<h4>if 测试</h4>
<form action="coreif.jsp" method ="get">
	<input type ="text" name="username" value="${param.username}”>
	<input type=“submit" value="登录">
	
	<%--判断如果提交的用户是管理员,则登录成功--%>
	<c:if test=${param.username='admin'}" var="isAdmin">
		<c:out value="管理员欢迎您!"/>
	</c:if>
	<c:out value="${isAdmin}">
	
</form>

c:choose,c:when(自上而向相当于if-else)

<c:set var="score" value="85"/>
<c:choose>
	<c:when test="${score>=90}">
	你的成绩为优秀
	</c:when>
	<c:when test="${score>=80}">
	你的成绩为良好
	</c:when>
	<c:when test="${score>=70}">
	你的成绩为一般
	</c:when>	
	<c:when test="${score<=60}">
	你的成绩为不及格
	</c:when>	
</c:choose>

c:forEach

<%

    ArrayList<String> people = new ArrayList<>();
    people.add(0,"张三");
    people.add(1,"李四");
    people.add(2,"王五");
    people.add(3,"赵六");
    people.add(4,"田六");
    request.setAttribute("list",people);
%>

<%--
var , 每一次遍历出来的变量
items, 要遍历的对象
begin,   哪里开始
end,     到哪里
step,   步长
--%>
<c:forEach var="people" items="${list}">
    <c:out value="${people}"/> <br>
</c:forEach>

<hr>

<c:forEach var="people" items="${list}" begin="1" end="3" step="1" >
    <c:out value="${people}"/> <br>
</c:forEach>

9. JavaBean

实体类
JavaBean有遵循特定的写法

  • 必须有一个无参构造
  • 属性必须私有化
  • 必须有对应的get/set方法
    ** 一般用来和数据库的字段做映射 ORM(object relational mapping对象关系映射)
  • 表-->类
  • 字段-->属性
  • 行记录-->对象

10. MVC 三层架构

Model View Controller 模型 视图 控制器
Servlet和Jsp都可以写Java代码,为了易于维护和使用,Servlet专注于处理请求,以及控制视图跳转,JSP专注于显示数据
以前的架构

servlet--CRUD(Create Retrieve Update Delete增删改查)-->数据库
弊端:程序十分臃肿,不利于维护
servlet的代码中:处理请求,响应,视图跳转,处理JDBC,处理业务代码,处理逻辑代码
架构:没有什么是加一层解决不了的

现在的架构

  • Model
    • 业务处理:业务逻辑(Service)
    • 数据持久层:CRUD(Dao)
  • View
    • 展示数据
    • 提供链接发起Servlet请求(a, form,img..)
  • Controller
    • 接收用户的请求:(req:请求参数,Session信息...)
    • 交给业务层处理对应的代码
    • 控制视图的跳转
      登录-->接收用户的登录请求-->处理用户的请求(获取用户的登录参数,username,password)--->交给业务层处理登录业务(判断用户密码是否正确,事务)--Dao层查询用户名和密码是否正确

11. Filter(重点)

Filter:过滤器,用来过滤网站的数据

  • 处理中文乱码(不用在每个Servlet里单独对编码进行设置,尤其在大量servlet下优势明显)
  • 登录验证
  • 屏蔽骂人的关键字显示***

    Filter开发步骤
  1. 导包(pom.xml里写各个jar包的dependency:servlet,jsp,standard,mysql)
  2. 编写过滤器
    注意filter导包不要错

    Filter本质上也是servlet,包含三个方法Init,destroy, doFilter,重写对应的方法
      public class CharacterEncodingFilter implements Filter {
      
          //初始化:web服务器启动,就立即初始化了,随时等待过滤对象出现!
          public void init(FilterConfig filterConfig) throws ServletException {
              System.out.println("CharacterEncodingFilter初始化");
          }
      
          //Chain : 链
          /*
          1. 过滤中的所有代码,在过滤特定请求的时候都会执行
          2. 必须要让过滤器继续通行
              chain.doFilter(request,response);
           */
          public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
              request.setCharacterEncoding("utf-8");
              //setCharacterEncoding是让servlet用utf-8来转码,setContentType是告诉浏览器用什么来解析
              response.setCharacterEncoding("utf-8");
              response.setContentType("text/html;charset=UTF-8");
      
              System.out.println("CharacterEncodingFilter执行前....");
              chain.doFilter(request,response); //让我们的请求继续走,如果不写,程序到这里就被拦截停止!
              System.out.println("CharacterEncodingFilter执行后....");
          }
      
          //销毁:web服务器关闭的时候,过滤器会销毁
          public void destroy() {
              System.out.println("CharacterEncodingFilter销毁");
          }
      }

3.在web.xml中配置 Filter
Filter本质上是servlet,但是它的创建是在服务器启动时就先创建了,等待拦截对象出现

  <filter>
       <filter-name>CharacterEncodingFilter</filter-name>
       <filter-class>com.kuang.filter.CharacterEncodingFilter</filter-class>
   </filter>
   <filter-mapping>
       <filter-name>CharacterEncodingFilter</filter-name>
       <!--只要是 /servlet的任何请求,会经过这个过滤器-->
       <url-pattern>/servlet/*</url-pattern>
       <!--<url-pattern>/*</url-pattern>-->
       <!-- 别偷懒写个 /* 过滤所有 -->
   </filter-mapping>

12.监听器

jservlet三个技术点:servlet,filter,listener.监听器就是监听某个对象状态变化的组件。相关的概念有:

  • 监听对象(request,session,servletContext)
  • 监听器:监听事件源的对象,相关事件源状态变化都会触发监听器
  • 注册监听器:将事件源和监听对象进行绑定
  • 响应行为:监听器监测到事件源状态发生变化时,所执行的代码
    被监听的对象可划分为,servletRequest域,servletSession域,servletContext域。监听的变化包括域对象的创建和销毁,及属性变化

参考 https://www.cnblogs.com/ginb/p/7247955.html

例步骤:
1.编写一个监听器,实现一个监听器的接口(监听如session创建)
例:统计网站在线人数 : 统计session

public class OnlineCountListener implements HttpSessionListener {

    //创建session监听: 看你的一举一动
    //一旦创建Session就会触发一次这个事件!
    public void sessionCreated(HttpSessionEvent se) {
        ServletContext ctx = se.getSession().getServletContext();

        System.out.println(se.getSession().getId());

        Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");

        if (onlineCount==null){
            onlineCount = new Integer(1);
        }else {
            int count = onlineCount.intValue();
            onlineCount = new Integer(count+1);
        }

        ctx.setAttribute("OnlineCount",onlineCount);

    }

    //销毁session监听
    //一旦销毁Session就会触发一次这个事件!
    public void sessionDestroyed(HttpSessionEvent se) {
        ServletContext ctx = se.getSession().getServletContext();

        Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");

        if (onlineCount==null){
            onlineCount = new Integer(0);
        }else {
            int count = onlineCount.intValue();
            onlineCount = new Integer(count-1);
        }

        ctx.setAttribute("OnlineCount",onlineCount);

    }


    /*
    Session销毁:
    1. 手动销毁  getSession().invalidate();
    2. 自动销毁
     */
}

2.web.xml 注册监听器

<!--注册监听器-->
<listener>
    <listener-class>com.kuang.listener.OnlineCountListener</listener-class>
</listener>

3.看情况是否使用

13. 过滤器、监听器常见应用

监听器:GUI编程中经常使用
用户登录之后才能进入主页,用户注销后不能进入主页
1.用户登录之后,向Session中放入用户的数据
2.进入主页的时候要判断用户是否已经登录;要求:在过滤器中实现

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//通常这个属性key写在工具类定义成static 常量,方便以后修改
if (request.getSession().getAttribute(Constant.USER_SESSION)==null){
    response.sendRedirect("/error.jsp");
}

chain.doFilter(request,response);

14 JDBC回顾

java连接数据库的中间层,连接各厂商数据库驱动的接口
需要jar包的支持

  • java.sql
  • javax.sql
  • mysql-connector-java...连接驱动(必须要导入)
  1. 实验环境搭建,建表

CREATE TABLE users(
    id INT PRIMARY KEY,
    `name` VARCHAR(40),
    `password` VARCHAR(40),
    email VARCHAR(60),
    birthday DATE
);

INSERT INTO users(id,`name`,`password`,email,birthday)
VALUES(1,'张三','123456','zs@qq.com','2000-01-01');
INSERT INTO users(id,`name`,`password`,email,birthday)
VALUES(2,'李四','123456','ls@qq.com','2000-01-01');
INSERT INTO users(id,`name`,`password`,email,birthday)
VALUES(3,'王五','123456','ww@qq.com','2000-01-01');


SELECT	* FROM users;
  1. 导入数据库依赖
<!--mysql的驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
  1. IDEA 连接数据库

  2. 写java代码查询数据库
    JDBC固定步骤
    加载驱动
    连接数据库,connection代表数据库
    向数据库发送SQL的对象Statement : CRUD
    编写SQL (根据业务,不同的SQL)
    执行SQL
    关闭连接(先开的后关)

public class TestJdbc {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //配置信息
        //useUnicode=true&characterEncoding=utf-8 解决中文乱码
        String url="jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf-8";
        String username = "root";
        String password = "123456";

        //1.加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.连接数据库,代表数据库
        Connection connection = DriverManager.getConnection(url, username, password);

        //3.向数据库发送SQL的对象Statement,PreparedStatement : CRUD
        Statement statement = connection.createStatement();

        //4.编写SQL
        String sql = "select * from users";

        //5.执行查询SQL,返回一个 ResultSet  : 结果集
        ResultSet rs = statement.executeQuery(sql);

        while (rs.next()){
            System.out.println("id="+rs.getObject("id"));
            System.out.println("name="+rs.getObject("name"));
            System.out.println("password="+rs.getObject("password"));
            System.out.println("email="+rs.getObject("email"));
            System.out.println("birthday="+rs.getObject("birthday"));
        }

        //6.关闭连接,释放资源(一定要做) 先开后关
        rs.close();
        statement.close();
        connection.close();
    }
}

预编译SQL

public class TestJDBC2 {
    public static void main(String[] args) throws Exception {
        //配置信息
        //useUnicode=true&characterEncoding=utf-8 解决中文乱码
        String url="jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf-8";
        String username = "root";
        String password = "123456";

        //1.加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.连接数据库,代表数据库
        Connection connection = DriverManager.getConnection(url, username, password);

        //3.编写SQL
        String sql = "insert into  users(id, name, password, email, birthday) values (?,?,?,?,?);";

        //4.预编译
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setInt(1,2);//给第一个占位符? 的值赋值为1;
        preparedStatement.setString(2,"狂神说Java");//给第二个占位符? 的值赋值为狂神说Java;
        preparedStatement.setString(3,"123456");//给第三个占位符? 的值赋值为123456;
        preparedStatement.setString(4,"24736743@qq.com");//给第四个占位符? 的值赋值为1;
        preparedStatement.setDate(5,new Date(new java.util.Date().getTime()));//给第五个占位符? 的值赋值为new Date(new java.util.Date().getTime());

        //5.执行SQL
        int i = preparedStatement.executeUpdate();

        if (i>0){
            System.out.println("插入成功@");
        }

        //6.关闭连接,释放资源(一定要做) 先开后关
        preparedStatement.close();
        connection.close();
    }
}

事务
要么都成功,要么都失败。ACID原则,保证数据的安全
开启事务
事务提交 commit
事务回滚 rollback
关闭事务
Junit 单元测试
依赖

<!--单元测试-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

简单使用
@Test只能在方法上使用,所有有这个注解的方法都可以运行
建表

CREATE TABLE account(
   id INT PRIMARY KEY AUTO_INCREMENT,
   `name` VARCHAR(40),
   money FLOAT
);

INSERT INTO account(`name`,money) VALUES('A',1000);
INSERT INTO account(`name`,money) VALUES('B',1000);
INSERT INTO account(`name`,money) VALUES('C',1000);

更新表,一个账号减少的同时另一个账户金额增加

    @Test
    public void test() {
        //配置信息
        //useUnicode=true&characterEncoding=utf-8 解决中文乱码
        String url="jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf-8";
        String username = "root";
        String password = "123456";

        Connection connection = null;

        //1.加载驱动
        try {
            Class.forName("com.mysql.jdbc.Driver");
            //2.连接数据库,代表数据库
             connection = DriverManager.getConnection(url, username, password);

            //3.通知数据库开启事务,false 开启
            connection.setAutoCommit(false);

            String sql = "update account set money = money-100 where name = 'A'";
            connection.prepareStatement(sql).executeUpdate();

            //制造错误
            //int i = 1/0;

            String sql2 = "update account set money = money+100 where name = 'B'";
            connection.prepareStatement(sql2).executeUpdate();

            connection.commit();//以上两条SQL都执行成功了,就提交事务!
            System.out.println("success");
        } catch (Exception e) {
            try {
                //如果出现异常,就通知数据库回滚事务
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

15. SMBMS项目搭建(超市订单管理系统)

笔记参考 https://blog.csdn.net/bell_love/article/details/106157413

用户管理页面和订单管理,供应商管理类似,都含有增删改查功能
数据库
用户表,对应的角色表;订单表,供应商表

** 项目如何搭建?**
考虑是否使用Maven? 依赖jar

15.1 项目搭建准备工作

1.搭建一个mavenweb项目
2. 配置Tomcat
3. 测试项目是否能跑起来
4.导入项目中会使用的jar包
jsp.Servlet.mysql驱动,jstl,stand...
5. 创建项目包结构

6. 编写实体类
ORM映射,表-->类映射
7. 编写基础公共类
1.数据库配置文件

driver=com.mysql.jdbc.Driver
#在和mysql传递数据的过程中,使用unicode编码格式,并且字符集设置为utf-8
url=jdbc:mysql://127.0.0.1:3306/smbms?useSSL=false&useUnicode=true&characterEncoding=utf-8
user=root
password=root

2.编写数据库公共类

package dao;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

/**
 * 操作数据库的基类--静态类
 * @author Administrator
 *
 */
public class BaseDao {
	
	static{//静态代码块,在类加载的时候执行
		init();
	}
	
	private static String driver;
	private static String url;
	private static String user;
	private static String password;
	
	//初始化连接参数,从配置文件里获得
	public static void init(){
		Properties params=new Properties();
		String configFile = "database.properties";
		InputStream is=BaseDao.class.getClassLoader().getResourceAsStream(configFile);
		try {
			params.load(is);
		} catch (IOException e) {
			e.printStackTrace();
		}
		driver=params.getProperty("driver");
		url=params.getProperty("url");
		user=params.getProperty("user");
		password=params.getProperty("password");

	}   
	
	
	/**
	 * 获取数据库连接
	 * @return
	 */
	public static Connection getConnection(){
		Connection connection = null;
		try {
			Class.forName(driver);
			connection = DriverManager.getConnection(url, user, password);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return connection;
	}
	/**
	 * 查询操作,和下面的更改操作重载,多了个参数ResultSet
	 * @param connection
	 * @param pstm
	 * @param rs
	 * @param sql
	 * @param params
	 * @return
	 */
	public static ResultSet execute(Connection connection,PreparedStatement pstm,ResultSet rs,
			String sql,Object[] params) throws Exception{
		pstm = connection.prepareStatement(sql);
		for(int i = 0; i < params.length; i++){
			pstm.setObject(i+1, params[i]);
		}
		rs = pstm.executeQuery();
		return rs;
	}
	/**
	 * 更新操作(增删改)
	 * @param connection
	 * @param pstm
	 * @param sql
	 * @param params
	 * @return
	 * @throws Exception
	 */
	public static int execute(Connection connection,PreparedStatement pstm,
			String sql,Object[] params) throws Exception{
		int updateRows = 0;
		pstm = connection.prepareStatement(sql);
		for(int i = 0; i < params.length; i++){
			pstm.setObject(i+1, params[i]);
		}
		updateRows = pstm.executeUpdate();
		return updateRows;
	}
	
	/**
	 * 释放资源,不用重载区分查询还是增删改,没有ResultSet直接传递null
	 * @param connection
	 * @param pstm
	 * @param rs
	 * @return
	 */
	public static boolean closeResource(Connection connection,PreparedStatement pstm,ResultSet rs){
		boolean flag = true;
		if(rs != null){
			try {
				rs.close();
				rs = null;//GC回收
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				flag = false;
			}
		}
		if(pstm != null){
			try {
				pstm.close();
				pstm = null;//GC回收
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				flag = false;
			}
		}
		if(connection != null){
			try {
				connection.close();
				connection = null;//GC回收
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				flag = false;
			}
		}
		
		return flag;
	}

}
  1. 编写字符编码过滤器

15.2 登录流程实现


1.编写前端页面
2.设置首页
在web.xml中改Tomcat欢迎页面,此时浏览器中输入http://localhost:8080直接进入login页面.

 <welcome-file-list>
    <welcome-file>login.jsp</welcome-file>
  </welcome-file-list>

3.编写dao层用户登录的接口

public User getLoginUser(Connection connection, String userCode) throws Exception;

4.编写dao接口的实现类

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import dao.BaseDao;
import pojo.User;

public class UserDaoImpl implements UserDao{
	//持久层只做查询数据库的内容
	public User getLoginUser(Connection connection, String userCode) throws Exception{
		//准备三个对象
		PreparedStatement pstm = null;
		ResultSet rs = null;
		User user = null;
		//判断是否连接成功
		if(null != connection){
			String sql = "select * from smbms_user where userCode=?";
			Object[] params = {userCode};
			rs = BaseDao.execute(connection, pstm, rs, sql, params);
			if(rs.next()){
				user = new User();
				user.setId(rs.getInt("id"));
				user.setUserCode(rs.getString("userCode"));
				user.setUserName(rs.getString("userName"));
				user.setUserPassword(rs.getString("userPassword"));
				user.setGender(rs.getInt("gender"));
				user.setBirthday(rs.getDate("birthday"));
				user.setPhone(rs.getString("phone"));
				user.setAddress(rs.getString("address"));
				user.setUserRole(rs.getInt("userRole"));
				user.setCreatedBy(rs.getInt("createdBy"));
				user.setCreationDate(rs.getTimestamp("creationDate"));
				user.setModifyBy(rs.getInt("modifyBy"));
				user.setModifyDate(rs.getTimestamp("modifyDate"));
			}
			BaseDao.closeResource(null, pstm, rs);
		}
		return user;
	}	
}
  1. 业务层接口
//用户登录
public User login(String userCode, String userPassword);
  1. 业务层实现类
import java.sql.Connection;

//import org.junit.Test;

import dao.BaseDao;
import dao.user.UserDao;
import dao.user.UserDaoImpl;
import pojo.User;

public class UserServiceImpl implements UserService{
	//业务层都会调用dao层.所以我们要引入Dao层(重点)
	//只处理对应业务
	
	private UserDao userDao;
	public UserServiceImpl(){
		userDao = new UserDaoImpl();
	}
	
	public User login(String userCode,String userPassword) {
		// TODO Auto-generated method stub
		Connection connection = null;
		//通过业务层调用对应的具体数据库操作
		User user = null;
		try {
			connection = BaseDao.getConnection();
			user = userDao.getLoginUser(connection, userCode);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			BaseDao.closeResource(connection, null, null);
		}
		return user;
	}
	
	/*@Test
	public void test() {
		UserServiceImpl userService = new UserServiceImpl();
		String userCode = "admin";
		String userPassword = "12345678";
		User admin = userService.login(userCode, userPassword);
		System.out.println(admin.getUserPassword());

	}
	*/
}
  1. 编写Servlet
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import pojo.User;
import util.Constants;
import service.user.UserService;
import service.user.UserServiceImpl;

@SuppressWarnings("serial")
public class LoginServlet extends HttpServlet{
	//接受用户参数、调用业务层(new service)、转发视图
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO 自动生成的方法存根
		
		System.out.println("login ============ " );
		//获取用户名和密码
		String userCode = req.getParameter("userCode");
		String userPassword = req.getParameter("userPassword");
		//调用service方法,进行用户匹配
		UserService userService = new UserServiceImpl();
		User user = userService.login(userCode,userPassword);
		if(null != user){//登录成功
			//放入session
			req.getSession().setAttribute(Constants.USER_SESSION,user);
			//页面跳转(frame.jsp)
			resp.sendRedirect("jsp/frame.jsp");
		}else{
			//页面跳转(login.jsp)带出提示信息--转发,对应Jsp页面直接取$(error)值
			req.setAttribute("error", "用户名或密码不正确");
			req.getRequestDispatcher("login.jsp").forward(req,resp);
		}
	}
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO 自动生成的方法存根
		doGet(req, resp);
	}
}

8.注册Servlet

<servlet>
	<servlet-name>LoginServlet</servlet-name>
	<servlet-class>com.kuang.servlet.user.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>LoginServlet</servlet-name>
	<url-pattern>/login.do</url-pattern>
</servlet-mapping>
  1. 测试访问,确保以上功能成功

15.3 登录过程优化

1.注销功能
思路:移除session,返回登陆界面

public class LogoutServlet extends HttpServlet {

	public void doPOST(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//清除session
		request.getSession().removeAttribute(Constants.USER_SESSION);
		response.sendRedirect(request.getContextPath()+"/login.jsp");//返回登录页面
	}

}

注册servlet

<servlet>
	<servlet-name>LogoutServlet</servlet-name>
	<servlet-class>servlet.user.LogoutServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>LogoutServlet</servlet-name>
        <!--前端jsp注销的时候会访问这个url-->
	<url-pattern>/jsp/logout.do</url-pattern>
</servlet-mapping>

2.登录拦截优化,不登录不能访问除了login以外的其他页面
使用过滤器

import java.io.IOException;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import pojo.User;
import util.Constants;

public class SysFilter implements Filter{
	public void init(FilterConfig filterConfig) throws ServletException{
		
	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {
		// 转成HttpServletRequest才能取到session
		HttpServletRequest request =  (HttpServletRequest)req;
		HttpServletResponse response = (HttpServletResponse)resp;
		
		//过滤器,从session中获取用户
		User user = (User)request.getSession().getAttribute(Constants.USER_SESSION);
		if(user == null){//已经被移除或者注销了,或者未登录
			response.sendRedirect("/smbms/error.jsp");
		}else {
			chain.doFilter(req, resp);
		}
	}

	@Override
	public void destroy() {
		// TODO 自动生成的方法存根
		
	}
}

注册过滤器

<!-- 用户登录过滤器 -->
<filter>
	<filter-name>SysFilter</filter-name>
	<filter-class>filter.SysFilter</filter-class>
</filter> 
<filter-mapping>
	<filter-name>SysFilter</filter-name>
	<url-pattern>/jsp/*</url-pattern>
</filter-mapping>

测试,登录,注销,权限,都保证OK

15.4 密码修改

1.导入前端素材

<li><a href="${pageContext.request.contextPath }/jsp/pwdmodify.jsp">密码修改</a></li>

2.写项目建议从底层往上写
先分析逻辑调用顺序,然后从底层开始写

3. UserDao

//修改当前用户密码
public int updatePwd(Connection connection,int id,int password)throws SQLException, Exception;
  1. UserDaoImpl
//调用BaseDao重载的增删改方法,参数没有结果集的
public int updatePwd(Connection connection, int id, int password) throws Exception {
	PreparedStatement pstm = null;
	int execute =0;
	if(connection!=null) {
		String sql = "update smbms_user set userPassword = ? where id = ?";
		Object[] params = {password,id};
		execute = BaseDao.execute(connection, pstm, sql, params);
                //只关闭在本方法创建的资源对象
		BaseDao.closeResource(null, pstm, null);
	}
	return execute;			
}

5.UserService

//修改用户密码
public boolean updatePwd(int id,int password)throws SQLException, Exception;
  1. UserServiceImpl
    业务层存在事务,失败了会回滚。
public boolean updatePwd(int id, int password) throws SQLException, Exception {
		// 业务层存在事务,失败了会回滚,connection在这定义为null
		Connection connection = null;
		boolean flag = false;
		//修改密码
		try {
			connection = BaseDao.getConnection();
			if(userDao.updatePwd(connection, id, password)>0) {
				flag = true;
			}
		} catch (SQLException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		} finally {
			BaseDao.closeResource(connection, null, null);
			
		}
		return flag;
	}

  1. Servlet
public class UserServlet extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String method = req.getParameter("method");
		if (method != "savepwd" && method != null) {
			this.updatePwd(req, resp);
		}
		//实现复用~~~~~~
		// 想添加新的增删改查,直接用if(method != "savepwd" && method != null);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO 自动生成的方法存根
		doGet(req, resp);
	}

	public void updatePwd(HttpServletRequest req, HttpServletResponse resp) {
		// 通过session获得用户id,现在session存在本地,但以后可能存在缓存服务器,此时不适合强转
		Object o = req.getSession().getAttribute(Constants.USER_SESSION);
		String newpassword = req.getParameter("newpassword");
		boolean flag = false;
		if (o != null && newpassword != null) {
			UserService userService = new UserServiceImpl();

			try {
				flag = userService.updatePwd(((User) o).getId(), newpassword);
			} catch (SQLException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			} catch (Exception e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
			if (flag) {
				req.setAttribute("message", "密码修改成功,请退出,使用新密码登录");
				// 密码修改成功,移除session(移除后不能再次修改密码,建议不移除)
				req.getSession().removeAttribute(Constants.USER_SESSION);
			} else {
				// 密码修改失败
				req.setAttribute("message", "密码修改失败");
			}

		} else {
			// 密码修改有问题
			req.setAttribute("message", "新密码有问题");
		}
		try {
			req.getRequestDispatcher("/jsp/pwdmodify.jsp").forward(req, resp);
		} catch (ServletException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}
}

注册servlet

        <servlet>
		<servlet-name>UserServlet</servlet-name>
		<servlet-class>servlet.user.UserServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>UserServlet</servlet-name>
		<url-pattern>/jsp/user.do</url-pattern>
	</servlet-mapping>
  1. 测试

15.5 优化密码修改使用Ajax(旧密码异步比对)

阿里巴巴的fastJson(百度关键字 fastJson Maven)
pom.xml

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.68</version>
</dependency>

后台代码
在userServlet里增加pwdModify()的方法,该方法已经不需要去后台查询数据,原密码已经存在session里了,可以直接比对

import java.io.IOException;
import java.io.PrintWriter;
//import java.io.Writer;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.mysql.cj.util.StringUtils;
import com.mysql.cj.xdevapi.JsonArray;

//import com.mysql.cj.util.StringUtils;

import pojo.User;
import service.user.UserService;
import service.user.UserServiceImpl;
import util.Constants;

@SuppressWarnings("serial")
public class UserServlet extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// 根据前端传递的隐藏方法域的值来选择走对应的方法,实现servlet复用
		String method = req.getParameter("method");
		if (method.equals( "savepwd") && method != null) {
			this.updatePwd(req, resp);
		}else if (method.equals( "pwdmodify") && method != null) {
			this.pwdmodify(req,resp);
		}
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO 自动生成的方法存根
		doGet(req, resp);
	}

	public void updatePwd(HttpServletRequest req, HttpServletResponse resp) {
		// 通过session获得用户id
		Object o = req.getSession().getAttribute(Constants.USER_SESSION);
		String newpassword = req.getParameter("newpassword");
		boolean flag = false;
		if (o != null && newpassword != null) {
			UserService userService = new UserServiceImpl();

			try {
				flag = userService.updatePwd(((User) o).getId(), newpassword);
			} catch (SQLException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			} catch (Exception e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
			if (flag) {
				req.setAttribute("message", "密码修改成功,请退出,使用新密码登录");
				// 密码修改成功,移除session(移除后不能再次修改密码,建议不移除)
				req.getSession().removeAttribute(Constants.USER_SESSION);
			} else {
				// 密码修改失败
				req.setAttribute("message", "密码修改失败");
			}

		} else {
			// 密码修改有问题
			req.setAttribute("message", "新密码有问题");
		}
		try {
			req.getRequestDispatcher("/jsp/pwdmodify.jsp").forward(req, resp);
		} catch (ServletException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}

	public void pwdmodify(HttpServletRequest req, HttpServletResponse resp) {
		// 通过session获得用户id
		Object o = req.getSession().getAttribute(Constants.USER_SESSION);
		String oldpassword = req.getParameter("oldpassword");
		
                //万能的map,结果集。一切的东西都可以用map传
		Map<String, String> resultMap = new HashMap<String, String>();
                //null表示session失效,session过期了,通常session都要设置过期时间
		if(o==null) {
			resultMap.put("result","seesionerror");
		}else if(StringUtils.isNullOrEmpty(oldpassword)){//输入密码为空
			resultMap.put("result","error");
		}else {
			String userPassword = ((User)o).getUserPassword();//seesion中的用户密码
			if(oldpassword.equals(userPassword)) {
				resultMap.put("result","true");
			}else {
				resultMap.put("result","false");
			}
		}
		
		
		try {
			resp.setContentType("application/json");
			PrintWriter writer = resp.getWriter();
			/*
			 * resultMap = ["result","sessionerror","result",error]
			 * josn格式={key,value} 前后端数据使用json交互
			 */
			writer.write(JSONArray.toJSONString(resultMap));
			//writer.write(JsonArray.class.toString());
			writer.flush();
			writer.close();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}
}

web.xml
默认session过期时间,真是业务需求。(人走了过一会过期,要重新登录)

<session-config>
  <session-timeout>30</session-timeout>
</session-config>

测试

15.6 用户管理实现

(这个点击后走的是一个请求,不是直接是页面,而是通过servlet返回前端页面)
一个页面的数据,可能从多个表里联合查询,到分布式的时候,还可能从多个数据库中查询

这个页面涉及用户表,角色表,总数

如限定age范围0~150,不合理的不予设置
1.导入分页的工具类
PageSupport.java 处理分页的工具类

public class PageSupport {
    //当前页码来自用户输入
    private int currentPageNo = 0;
    //总数量(表)
    private int totalCount=0;
    //页面容量
    private int pageSize = 0;
    //总页数-totalCount/pageSize(+1)
    private int totalPageCount = 1;

    public int getCurrentPageNo() {
        return currentPageNo;
    }
    public void setCurrentPageNo(int currentPageNo) {
        //OOP三大特性--封装,继承,多态。其中封装主要指属性私有,set/get,
        // 同时在set中限定一些不安全的情况
        if(currentPageNo>0) {
            this.currentPageNo = currentPageNo;
        }
    }

    public int getPageSize() {
        return pageSize;
    }
    public void setPageSize(int pageSize) {
        if(pageSize>0) {
            this.pageSize = pageSize;
        }
    }

    public void setTotalPageCount(int totalPageCount) {
        if(totalPageCount>0) {
            this.totalPageCount = totalPageCount;
        }
    }

    public int getTotalPageCount() {
        return totalPageCount;
    }

    public int getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(int totalCount) {
        if(totalCount>9){
            this.totalCount = totalCount;
            this.setTotalPageCountByRs();
        }

    }
    public void setTotalPageCountByRs(){
        if(this.totalCount % this.pageSize == 0){
            this.totalPageCount = this.totalCount / this.pageSize;
        }else if(this.totalCount % this.pageSize > 0){
            this.totalPageCount = this.totalCount / this.pageSize + 1;
        }else{
            this.totalPageCount = 0;
        }
    }
}

  1. 用户列表页面导入
    userlist.jsp
    rollpage.jsp(在userlist里引入的分页页面)

15.6.1.获取用户数量

1.UserDao

//根据用户名或者角色查询用户总数(页面有这两个查询条件,没有就展示总联表)
public int getUserCount(Connection connection,String username ,int userRole)throws SQLException, Exception;

2.UserDaoImpl

@Override
	public int getUserCount(Connection connection, String userName, int userRole)
			throws Exception {
		// TODO Auto-generated method stub
		PreparedStatement pstm = null;
		ResultSet rs = null;
		int count = 0;
		if(connection != null){
                        //使用stringBuffer可变长追加 
			StringBuffer sql = new StringBuffer();
                        //用户和角色联表的总表
			sql.append("select count(1) as count from smbms_user u,smbms_role r where u.userRole = r.id");
			List<Object> list = new ArrayList<Object>();
                        //用户姓名查询条件
			if(!StringUtils.isNullOrEmpty(userName)){
				sql.append(" and u.userName like ?");
				list.add("%"+userName+"%");
			}
                        //角色限定查询条件
			if(userRole > 0){
				sql.append(" and u.userRole = ?");
				list.add(userRole);
			}
			Object[] params = list.toArray();
			System.out.println("sql ----> " + sql.toString());
			rs = BaseDao.execute(connection, pstm, rs, sql.toString(), params);
			if(rs.next()){
				count = rs.getInt("count");
			}
			BaseDao.closeResource(null, pstm, rs);
		}
		return count;
	}

3.UserService

//查询记录数
	public int getUserCount(String username, int userRole);

4.UserServiceImpl
记住service有获取数据库连接处理事务的过程

//查询记录数
	@Override
	public int getUserCount(String queryUserName, int queryUserRole) {
		// TODO Auto-generated method stub
		Connection connection = null;
		int count = 0;
		System.out.println("queryUserName ---- > " + queryUserName);
		System.out.println("queryUserRole ---- > " + queryUserRole);
		try {
			connection = BaseDao.getConnection();
			count = userDao.getUserCount(connection, queryUserName,queryUserRole);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			BaseDao.closeResource(connection, null, null);
		}
		//System.out.println("count"+count);
		return count;
	}

15.6.2 获取用户列表

1.UserDao

//通过条件查询-userList
public List<User> getUserList(Connection connection, String userName, int userRole, int currentPageNo, int pageSize)throws Exception;

2.UserDaoImpl
用户列表存进arryList集合里

@Override
	public List<User> getUserList(Connection connection, String userName,int userRole,int currentPageNo, int pageSize)
			throws Exception {
		PreparedStatement pstm = null;
		ResultSet rs = null;
		List<User> userList = new ArrayList<User>();
		if(connection != null){
			StringBuffer sql = new StringBuffer();
			sql.append("select u.*,r.roleName as userRoleName from smbms_user u,smbms_role r where u.userRole = r.id");
			List<Object> list = new ArrayList<Object>();
			if(!StringUtils.isNullOrEmpty(userName)){
				sql.append(" and u.userName like ?");
				list.add("%"+userName+"%");
			}
			if(userRole > 0){
				sql.append(" and u.userRole = ?");
				list.add(userRole);
			}
			//在数据库中,分页显示 limit startIndex,pageSize;总数
			//当前页  (当前页-1)*页面大小
			//0,5	1,0	 01234
			//5,5	5,0	 56789
			//10,5	10,0 10~
			sql.append(" order by creationDate DESC limit ?,?");
			startIndex= (currentPageNo-1)*pageSize;
			list.add(startIndex);
			list.add(pageSize);
			
			Object[] params = list.toArray();
			System.out.println("sql ----> " + sql.toString());
			
			rs = BaseDao.execute(connection, pstm, rs, sql.toString(), params);
			while(rs.next()){
				User _user = new User();
				_user.setId(rs.getInt("id"));
				_user.setUserCode(rs.getString("userCode"));
				_user.setUserName(rs.getString("userName"));
				_user.setGender(rs.getInt("gender"));
				_user.setBirthday(rs.getDate("birthday"));
				_user.setPhone(rs.getString("phone"));
				_user.setUserRole(rs.getInt("userRole"));
				_user.setUserRoleName(rs.getString("userRoleName"));
				userList.add(_user);
			}
			BaseDao.closeResource(null, pstm, rs);
		}
		return userList;
	}

3.UserService

/根据条件查询用户列表
public List<User> getUserList(String queryUserName, int queryUserRole, int currentPageNo, int pageSize);

4.UserServiceImpl

@Override
	public List<User> getUserList(String queryUserName,int queryUserRole,int currentPageNo, int pageSize) {
		// TODO Auto-generated method stub
		Connection connection = null;
		List<User> userList = null;
		System.out.println("queryUserName ---- > " + queryUserName);
		System.out.println("queryUserRole ---- > " + queryUserRole);
		System.out.println("currentPageNo ---- > " + currentPageNo);
		System.out.println("pageSize ---- > " + pageSize);
		try {
			connection = BaseDao.getConnection();
			userList = userDao.getUserList(connection, queryUserName,queryUserRole,currentPageNo,pageSize);
		} catch (Exception e) {
			// TODO Auto-generated catch block 
			e.printStackTrace();
		}finally{
			BaseDao.closeResource(connection, null, null);
		}
		return userList;
	}

15.6.3 获取角色列表

建立角色类一个pojo对应一套dao,一套service,代码结构好看
1.RoleDao

//获取角色列表
public List<Role> getRoleList(Connection connection)throws Exception;

2.RoleDaoImpl

public class RoleDaoImpl implements RoleDao {

	@Override
	public List<Role> getRoleList(Connection connection) throws Exception {
		PreparedStatement pstm = null;
		ResultSet rs = null;
		List<Role> roleList = new ArrayList<Role>();
		if (connection != null) {
			String sql = "select * from smbms_role";
			Object[] params = {};
			rs = BaseDao.execute(connection, pstm, rs, sql, params);
			while (rs.next()) {
				Role _role = new Role();
				_role.setId(rs.getInt("id"));
				_role.setRoleCode(rs.getString("roleCode"));
				_role.setRoleName(rs.getString("roleName"));
				roleList.add(_role);
			}
			BaseDao.closeResource(null, pstm, rs);
		}

		return roleList;
	}
}

3.RoleService

public interface RoleService {
	//角色列表查询
	public List<Role> getRoleList();  
	
}

4.RoleServiceImpl

public class RoleServiceImpl implements RoleService{
	
	private RoleDao roleDao;
	
	public RoleServiceImpl(){
		roleDao = new RoleDaoImpl();
	}
	
	@Override
	public List<Role> getRoleList() {
		Connection connection = null;
		List<Role> roleList = null;
		try {
			connection = BaseDao.getConnection();
			roleList = roleDao.getRoleList(connection);
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			BaseDao.closeResource(connection, null, null);
		}
		return roleList;
	}
	
}

15.4 用户显示的servlet

  1. 获取用户前端的数据
  2. 判断请求是否需要执行,看参数的判断
  3. 为了实现分页,需要计算出当前页面和总页面,页面大小...(限制当前页数不能小于1,不能大于总页数)
  4. 查询用户列表,角色表
  5. 返回前端
/重点、难点
	private void query(HttpServletRequest req, HttpServletResponse resp) {
		//查询用户列表
		//从前端获取数据
		//查询用户列表
		String queryUserName = req.getParameter("queryname");
		String temp = req.getParameter("queryUserRole");
		String pageIndex = req.getParameter("pageIndex");
		int queryUserRole = 0;
		
		//获取用户列表
		UserServiceImpl userService = new UserServiceImpl();
		List<User> userList = null;
		
		//第一此请求肯定是走第一页,页面大小固定的
		//设置页面容量
    	        int pageSize = 5;//把它设置在配置文件里,后面方便修改
    	        //当前页码
    	        int currentPageNo = 1;
		
		if(queryUserName == null){
			queryUserName = "";
		}
		if(temp != null && !temp.equals("")){
			queryUserRole = Integer.parseInt(temp);
		}
		if(pageIndex != null) {
			currentPageNo = Integer.parseInt(pageIndex);
		}
		//获取用户总数(分页	上一页:下一页的情况)
		//总数量(表)	
    	         int totalCount	= userService.getUserCount(queryUserName,queryUserRole);
			
		//总页数支持
		PageSupport pageSupport = new PageSupport();
		pageSupport.setCurrentPageNo(currentPageNo);
		pageSupport.setPageSize(pageSize);
		pageSupport.setTotalCount(totalCount);
		
		int totalPageCount =pageSupport.getTotalPageCount();//总共有几页
		//(totalCount+pageSize-1/pageSize)取整
		// pageSupport.getTotalCount()
		
		//System.out.println("totalCount ="+totalCount);
		//System.out.println("pageSize ="+pageSize);
		//System.out.println("totalPageCount ="+totalPageCount);
		//控制首页和尾页
		//如果页面小于 1 就显示第一页的东西
		if(currentPageNo < 1) {
			currentPageNo = 1;
		}else if(currentPageNo > totalPageCount) {//如果页面大于了最后一页就显示最后一页
			currentPageNo =totalPageCount;
		}
		
		userList = userService.getUserList(queryUserName, queryUserRole, currentPageNo, pageSize);
		req.setAttribute("userList", userList);
		
		RoleServiceImpl roleService = new RoleServiceImpl();
		List<Role> roleList = roleService.getRoleList();
		req.setAttribute("roleList", roleList);
		req.setAttribute("totalCount", totalCount); 
		req.setAttribute("currentPageNo", currentPageNo);
		req.setAttribute("totalPageCount", totalPageCount);
		req.setAttribute("queryUserName", queryUserName);
		req.setAttribute("queryUserRole", queryUserRole);
		
		//返回前端
		try {
			req.getRequestDispatcher("userlist.jsp").forward(req, resp);
		} catch (ServletException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}

	}

调试方法
小黄鸭调试法,bug从头到尾分析一遍,点的是在哪显示的,值是怎么传过来的

15.5 增删改用户代码略

  • 主要代码量在获取和封装数据,注意增删改要在service开启手动开启事务,报错catch exception回滚
  • 部署编译好的项目,解压包,复制一份tomcat目录,修改server.xml里tomcat的端口号,把解压后的项目放到webapp里。如果页面卡了,就在启动tomcat的终端上回车下。
  • 从class字节码反编译成java,只要没有switch和中文的,反编译文件和源文件区别都不大

16. 文件传输

16.1 文件传输环境搭建

笔记参考 https://blog.csdn.net/qq_54897873/article/details/118551792

  1. new project->Empt Project 创建空的项目有一个好处

    2.空项目弹出设置

    3.file->New Module

    4.配置Tomcat以及下载jar包
    没用maven建项目,需要手动下载jar包,手动导入项目里
    文件上传涉及两个包common-io以及common-fileupload包
    下载地址:
    https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload
    https://mvnrepository.com/artifact/commons-io/commons-io
    注意将jar放在新创建的lib目录下后,右键(或者project structure->library)将lib目录add as library
    在Project structure->Artifacts->加号filewar exploded加上lib目录,让项目发布时lib里的依赖包也参与发布

16.2 文件上传原理


文件上传调优【注意事项】

  • 为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,如WEB-INF目录下
  • 为防止文件覆盖现象发生,要为上传问价产生一个唯一的文件名(uuid)
  • 要限制上传文件的最大值
  • 可以限制上传文件的类型,在收到上传文件时,判断后缀名是否合法
    【需要用到的类详解】
    ServletFileUpload负责处理上传的数据,并将表单中每个输入项封装成一个FileItem对象,在使用ServletFileUpload对象解析请求时需要DiskFileItemFactory对象。
    所以,我们需要在进行解析工作前构造好DiskFileItemFactory对象,通过ServletFileUpload对象的构造方法或setFileItermFactory()方法设置ServletFileUpload对象的fileItemFactory属性
    FileItem类
    在HTML页面input必须有name < input type=“file” name=“filename” >
    表单如果包含一个文件上传输入项的话,这个表单的enctype属性就必须设置为multipart/form-date
<form action="" method="post" enctype="multipart/form-data">
  上传用户: <input type="text" name="username"><br>
  <p><input type="file" name="file1"></p>
  <p><input type="file" name="file2"></p>
  <p><input type="submit"> | <input type="reset"></p>
</form>

浏览器表单的类型如果为multipart/form-data,在服务器端想获取数据就要通过流。
【常用方法介绍】

//isFormField方法用于判断FileItem类对象封装的数据是一个普通文本表单还是一个文件表单,如果是普通文本表单字段则返回true,否则返回false
boolean isFormField();

//getFieldName方法用于返回表单标签name属性的值
String getFieldName();
//getString方法用于将FileItem对象中保存的数据流内容以一个字符串返回
String getString();

//getName方法用于获得文件上传字段中的文件名
String getName();

//以流的形式返回上传文件的内容数据
InputStream getInputStream();

//delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容呗保存在临时文件中,delete方法将删除该临时文件
void delete();

ServletFileUploda类
ServletFileUpload负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem对象中,使用其parseRequest(HttpservletRequest)方法可以将通过表单中每一个HTML标签提交的数据封装成一个FileItem对象,然后以List列表的形式返回。使用该方法处理上传文件简答易用,不用自己再写流传输

16.3 代码编写

空项目src->右键->new servlet直接可以new servlet 自己完善相应的映射
UploadFileServlet

package com.wang.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;

public class FileServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //判断上传的文件是普通表单还是带文件的表单
        if (!ServletFileUpload.isMultipartContent(request)){
            return;//终止方法运行,说明这是一个普通的表单,直接返回
        }

        //创建上传文件的保存路径,建议在WEB-INF路径下,安全,用户无法直接访问上传的文件;
        String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
        File uploadFile = new File(uploadPath);
        if (uploadFile.exists()){//.exists()取反
            uploadFile.mkdir();//创建这个目录
        }

        //缓存,临时文件
        //临时路径,加入文件超过了预期的大小,我们就把它放到一个临时文件中,过几天自动删除,或者提醒用户转存为永久
        String tmpPath = this.getServletContext().getRealPath("/WEB-INF/tmp");
        File file = new File(tmpPath);
        if (file.exists()){//.exists()取反
            file.mkdir();//创建这个临时目录
        }

        // 处理上传的文件,一般都需要通过流来获取,我们可以使用request.getInputstream(),原生态的文件上传流获取,十分麻烦
        // 但是我们都建议使用 Apache的文件上传组件来实现, common-fileupload,它需要依赖于commons-io组件;


        try {
            // 1.创建DiskFileItemFactory对象,处理文件路径或者大小限制
            DiskFileItemFactory factory = getDiskFileItemFactory(file);
            /*
             * //通过这个工厂设置一个缓冲区,当上传的文件大于这个缓冲区的时候,将他放到临时文件 factory.setSizeThreshold(1024 *
             * 1024); //缓存区大小为1M factory.setRepository (file);//临时目录的保存目录,需要一个File
             */

            // 2、获取ServletFileUpload
            ServletFileUpload upload = getServletFileUpload(factory);

            // 3、处理上传文件
            // 把前端请求解析,封装成FileItem对象,需要从ServletFileUpload对象中获取
            String msg = uploadParseRequest(upload, request, uploadPath);

            // Servlet请求转发消息
            System.out.println(msg);
            if(msg.equals("文件上传成功!")) {
                // Servlet请求转发消息
                request.setAttribute("msg",msg);
                request.getRequestDispatcher("info.jsp").forward(request, response);
            }else {
                msg ="请上传文件";
                request.setAttribute("msg",msg);
                request.getRequestDispatcher("info.jsp").forward(request, response);
            }

        } catch (FileUploadException e) {
            e.printStackTrace();
        }
    }

    public static DiskFileItemFactory getDiskFileItemFactory(File file) {
        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 通过这个工厂设置一个缓冲区,当上传的文件大于这个缓冲区的时候,将他放到临时文件中;
        factory.setSizeThreshold(1024 * 1024);// 缓冲区大小为1M
        factory.setRepository(file);// 临时目录的保存目录,需要一个file
        return factory;
    }

    public static ServletFileUpload getServletFileUpload(DiskFileItemFactory factory) {
        ServletFileUpload upload = new ServletFileUpload(factory);
        // 监听上传进度
        upload.setProgressListener(new ProgressListener() {

            // pBYtesRead:已读取到的文件大小
            // pContextLength:文件大小
            public void update(long pBytesRead, long pContentLength, int pItems) {
                System.out.println("总大小:" + pContentLength + "已上传:" + pBytesRead);
            }
        });

        // 处理乱码问题
        upload.setHeaderEncoding("UTF-8");
        // 设置单个文件的最大值
        upload.setFileSizeMax(1024 * 1024 * 10);
        // 设置总共能够上传文件的大小
        // 1024 = 1kb * 1024 = 1M * 10 = 10м

        return upload;
    }

    public static String uploadParseRequest(ServletFileUpload upload, HttpServletRequest request, String uploadPath)
            throws FileUploadException, IOException {

        String msg = "";

        // 把前端请求解析,封装成FileItem对象
        List<FileItem> fileItems = upload.parseRequest(request);
        for (FileItem fileItem : fileItems) {
            if (fileItem.isFormField()) {// 判断上传的文件是普通的表单还是带文件的表单
                // getFieldName指的是前端表单控件的name;
                String name = fileItem.getFieldName();
                String value = fileItem.getString("UTF-8"); // 处理乱码
                System.out.println(name + ": " + value);
            } else {// 判断它是上传的文件

                // ============处理文件==============

                // 拿到文件名
                String uploadFileName = fileItem.getName();
                System.out.println("上传的文件名: " + uploadFileName);
                if (uploadFileName.trim().equals("") || uploadFileName == null) {
                    continue;
                }

                // 获得上传的文件名/images/girl/paojie.png
                String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("/") + 1);
                // 获得文件的后缀名
                String fileExtName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1);

                /*
                 * 如果文件后缀名fileExtName不是我们所需要的 就直按return.不处理,告诉用户文件类型不对。
                 */

                System.out.println("文件信息[件名: " + fileName + " ---文件类型" + fileExtName + "]");
                // 可以使用UID(唯一识别的通用码),保证文件名唯
                // 0UID. randomUUID(),随机生一个唯一识别的通用码;
                String uuidPath = UUID.randomUUID().toString();

                // ================处理文件完毕==============

                // 存到哪? uploadPath
                // 文件真实存在的路径realPath
                String realPath = uploadPath + "/" + uuidPath;
                // 给每个文件创建一个对应的文件夹
                File realPathFile = new File(realPath);
                if (!realPathFile.exists()) {
                    realPathFile.mkdir();
                }
                // ==============存放地址完毕==============


                // 获得文件上传的流
                InputStream inputStream = fileItem.getInputStream();
                // 创建一个文件输出流
                // realPath =真实的文件夹;
                // 差了一个文件;加上翰出文件的名产"/"+uuidFileName
                FileOutputStream fos = new FileOutputStream(realPath + "/" + fileName);
                System.out.println("path:"+realPath + "/" + fileName);
                // 创建一个缓冲区
                byte[] buffer = new byte[1024 * 1024];
                // 判断是否读取完毕
                int len = 0;
                // 如果大于0说明还存在数据;
                while ((len = inputStream.read(buffer)) > 0) {
                    fos.write(buffer, 0, len);
                }
                // 关闭流
                fos.close();
                inputStream.close();

                msg = "文件上传成功!";
                fileItem.delete(); // 上传成功,清除临时文件
                //=============文件传输完成=============
            }
        }
        return msg;

    }
}

安装的java里JNI是JAVA Native Interface,jawt,是图像界面支持
操作系统存的是离散的大约数,所以float和double的数据不准确

17 邮件发送原理及实现

参考 https://blog.csdn.net/qq_54897873/article/details/118557180

17.1 发送邮件的原理

  • 用户张三通过网络基站发送给网易163服务器,网易服务器发现收件人是qq.com,通过基站网络又发给QQ服务器,当李四打开邮箱发现有新邮件
  • 邮件服务器SMTP服务器地址是固定的,一般是smtp.xxx.com,如163邮箱是smtp.163.com,qq邮箱是smtp.qq.com
  • 用户注册邮箱就是在相应的服务器里文件系统里分配地址给该用户
  • 发送邮件协议SMTP协议,接收邮件协议POP3协议

17.2 邮件支持包

mail.jar
activation.jar (从maven里下载,如果下载不了抓network jar包里面有实际下载地址,切http试试)
JavaMail是sun公司提供的实现邮件发送和接收功能的一套java标准开发包,支持协议SMTP,IMAP,MIME

  • 主要的核心类

    重点理解产生的三个对象,session存环境信息,transport发送对象,message邮件内容对象
  • MIME(Mutipurpose Internet Mail Extensions)多用途互联网邮件扩展类型,即附件图片等

17.3 开启服务获取授权码

QQ邮箱要手动在设置里开启POP3/SMTP服务,然后点击生成授权码

17.4 发送邮件例子

例1.简单文本内容邮件发送

import com.sun.mail.util.MailSSLSocketFactory;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.security.GeneralSecurityException;
import java.util.Properties;

public class MailDemo01 {
    public static void main(String[] args) throws Exception {

        Properties prop=new Properties();
        prop.setProperty("mail.host","smtp.qq.com");///设置QQ邮件服务器
        prop.setProperty("mail.transport.protocol","smtp");///邮件发送协议
        prop.setProperty("mail.smtp.auth","true");//需要验证用户密码

        //QQ邮箱需要设置SSL加密,其他邮箱不需要
        MailSSLSocketFactory sf=new MailSSLSocketFactory();
        sf.setTrustAllHosts(true);
        prop.put("mail.smtp.ssl.enable","true");
        prop.put("mail.smtp.ssl.socketFactory",sf);

        //使用javaMail发送邮件的5个步骤
        //1.创建定义整个应用程序所需要的环境信息的session对象,里面有谁发送的等

        //QQ才有!其他邮箱就不用
        Session session=Session.getDefaultInstance(prop, new Authenticator() {
            public PasswordAuthentication getPasswordAuthentication() {
                //发件人邮件用户名、授权码
                return new PasswordAuthentication("XXXX@qq.com","授权码");
            }
        });

        //开启session的debug模式,这样可以查看到程序发送Email的运行状态
        session.setDebug(true);
        //2.通过session得到transport发送对象
        Transport ts=session.getTransport();

        //3.使用邮箱的用户名和授权码连上邮件服务器
        ts.connect("smtp.qq.com","XXXX@qq.com","授权码");

        //4.创建邮件:写文件
        //注意需要传递session
        MimeMessage message=new MimeMessage(session);
        //指明邮件的发件人
        message.setFrom(new InternetAddress("XXXX@qq.com"));
        //指明邮件的收件人
        message.setRecipient(Message.RecipientType.TO,new InternetAddress("XXXX@qq.com"));
        //邮件标题
        message.setSubject("发送的标题");
        //邮件的文本内容,像文本颜色等格式都是设置了html标签,所以此处类型html
        message.setContent("内容","text/html;charset=UTF-8");
        
        //5.发送邮件
        ts.sendMessage(message,message.getAllRecipients());

        //6.关闭连接
        ts.close();

    }

}

例2 复杂文件内容发送
MIME
MIMEBodyPart类------一个MIME消息
MimeMutiPart类------用来组合多个MIME消息,一个MimeMutiPart对象可以包含多个MIME消息的MimeBodyPart对象

只要设置为mixed,就可以包容纯文本+插入图片(内嵌资源)+附件


mport com.sun.mail.util.MailSSLSocketFactory;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.util.Properties;

public class MailDemo02 {
    public static void main(String[] args) throws Exception {
        Properties prop=new Properties();
        prop.setProperty("mail.host","smtp.qq.com");///设置QQ邮件服务器
        prop.setProperty("mail.transport.protocol","smtp");///邮件发送协议
        prop.setProperty("mail.smtp.auth","true");//需要验证用户密码
        //QQ邮箱需要设置SSL加密
        MailSSLSocketFactory sf=new MailSSLSocketFactory();
        sf.setTrustAllHosts(true);
        prop.put("mail.smtp.ssl.enable","true");
        prop.put("mail.smtp.ssl.socketFactory",sf);

        //使用javaMail发送邮件的5个步骤
        //1.创建定义整个应用程序所需要的环境信息的session对象
        Session session=Session.getDefaultInstance(prop, new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication("11927XXX@qq.com","授权码");
            }
        });
        //开启session的debug模式,这样可以查看到程序发送Email的运行状态
        session.setDebug(true);
        //2.通过session得到transport对象
        Transport ts=session.getTransport();
        //3.使用邮箱的用户名和授权码连上邮件服务器
        ts.connect("smtp.qq.com","11927XXX@qq.com","授权码");
        //4.创建邮件:写文件
        //注意需要传递session
        MimeMessage message=new MimeMessage(session);
        //指明邮件的发件人
        message.setFrom(new InternetAddress("11927XXX@qq.com"));
        //指明邮件的收件人
        message.setRecipient(Message.RecipientType.TO,new InternetAddress("11927XXX@qq.com"));
        //邮件标题
        message.setSubject("java发出");

        //邮件的文本内容
        //=================================准备图片数据=======================================
        MimeBodyPart image=new MimeBodyPart();
        //图片需要经过数据化的处理
        DataHandler dh=new DataHandler(new FileDataSource("D:\\Bert\\1594126632(1).jpg"));
        //在part中放入这个处理过图片的数据
        image.setDataHandler(dh);
        //给这个part设置一个ID名字
        image.setContentID("bz.jpg");

        //准备正文的数据,cid就是上面的contentID
        MimeBodyPart text=new MimeBodyPart();
        text.setContent("这是一张正文<img src='cid:bz.jpg'>","text/html;charset=UTF-8");

        //描述数据关系
        MimeMultipart mm=new MimeMultipart();
        mm.addBodyPart(text);
        mm.addBodyPart(image);
        mm.setSubType("related");

        //设置到消息中,保存修改
        message.setContent(mm);
        message.saveChanges();
        //5.发送邮件
        ts.sendMessage(message,message.getAllRecipients());

        //6.关闭连接
        ts.close();

    }
}

例3 带附件写法

package com.wang;
import com.sun.mail.util.MailSSLSocketFactory;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.security.GeneralSecurityException;
import java.util.Properties;

public class MailDemo03 {
    public static void main(String[] args) throws MessagingException, GeneralSecurityException {

        //创建一个配置文件保存并读取信息
        Properties properties = new Properties();

        //设置qq邮件服务器
        properties.setProperty("mail.host","smtp.qq.com");
        //设置发送的协议
        properties.setProperty("mail.transport.protocol","smtp");
        //设置用户是否需要验证
        properties.setProperty("mail.smtp.auth", "true");


        //=================================只有QQ存在的一个特性,需要建立一个安全的链接
        // 关于QQ邮箱,还要设置SSL加密,加上以下代码即可
        MailSSLSocketFactory sf = new MailSSLSocketFactory();
        sf.setTrustAllHosts(true);
        properties.put("mail.smtp.ssl.enable", "true");
        properties.put("mail.smtp.ssl.socketFactory", sf);

        //=================================准备工作完毕

        //1.创建一个session会话对象;
        Session session = Session.getDefaultInstance(properties, new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication("24736743@qq.com", "授权码");
            }
        });

        //可以通过session开启Dubug模式,查看所有的过程
        session.setDebug(true);


        //2.获取连接对象,通过session对象获得Transport,需要捕获或者抛出异常;
        Transport tp = session.getTransport();

        //3.连接服务器,需要抛出异常;
        tp.connect("smtp.qq.com","24736743@qq.com","授权码");

        //4.连接上之后我们需要发送邮件;
        MimeMessage mimeMessage = imageMail(session);

        //5.发送邮件
        tp.sendMessage(mimeMessage,mimeMessage.getAllRecipients());

        //6.关闭连接
        tp.close();

    }


    public static MimeMessage imageMail(Session session) throws MessagingException {

        //消息的固定信息
        MimeMessage mimeMessage = new MimeMessage(session);

        //邮件发送人
        mimeMessage.setFrom(new InternetAddress("24736743@qq.com"));
        //邮件接收人,可以同时发送给很多人,我们这里只发给自己;
        mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("24736743@qq.com"));
        mimeMessage.setSubject("我也不知道是个什么东西就发给你了"); //邮件主题


        /*
        编写邮件内容
        1.图片
        2.附件
        3.文本
         */

        //图片
        MimeBodyPart body1 = new MimeBodyPart();
        body1.setDataHandler(new DataHandler(new FileDataSource("src/resources/yhbxb.png")));
        body1.setContentID("yhbxb.png"); //图片设置ID

        //文本
        MimeBodyPart body2 = new MimeBodyPart();
        body2.setContent("请注意,我不是广告<img src='cid:yhbxb.png'>","text/html;charset=utf-8");

        //附件,此时设置的setFileName,不是ContentID
        MimeBodyPart body3 = new MimeBodyPart();
        body3.setDataHandler(new DataHandler(new FileDataSource("src/resources/log4j.properties")));
        body3.setFileName("log4j.properties"); //附件设置名字

        MimeBodyPart body4 = new MimeBodyPart();
        body4.setDataHandler(new DataHandler(new FileDataSource("src/resources/1.txt")));
        body4.setFileName(""); //附件设置名字

        //拼装邮件正文内容
        MimeMultipart multipart1 = new MimeMultipart();
        multipart1.addBodyPart(body1);
        multipart1.addBodyPart(body2);
        multipart1.setSubType("related"); //1.文本和图片内嵌成功!

        //new MimeBodyPart().setContent(multipart1); //将拼装好的正文内容设置为主体
        MimeBodyPart contentText =  new MimeBodyPart();
        contentText.setContent(multipart1);

        //拼接附件
        MimeMultipart allFile =new MimeMultipart();
        allFile.addBodyPart(body3); //附件
        allFile.addBodyPart(body4); //附件
        allFile.addBodyPart(contentText);//正文
        allFile.setSubType("mixed"); //正文和附件都存在邮件中,所有类型设置为mixed;


        //放到Message消息中
        mimeMessage.setContent(allFile);
        mimeMessage.saveChanges();//保存修改


        return mimeMessage;

    }

}

17.4 javaWeb发送邮件

网站注册提交表单时,会给用户邮箱发送邮件激活账户超链接等。这部分功能就是借助JavaMail
1.新建一个JavaWeb项目,配置tomcat,添加jar包

配置好Tomcat后首先启动运行下,确保搭建成功
配置maven如果仓库里已经有这个Jar包了,可以在类上右键->add Maven Dependency->直接在pom.xm里生成依赖代码
2.编写对应的用户实体类

package com.wang.pojo;

import java.io.Serializable;

public class User implements Serializable {
    private String username;
    private String password;
    private String email;

    public User() {
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public User(String username, String password, String email) {
        this.username = username;
        this.password = password;
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

3.前端注册页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>注册</title>
</head>
<body>

<form action="${pageContext.request.contextPath}/RegisterServlet.do" method="post">
    用户名:<input type="text" name="username"><br/>
    密码:<input type="password" name="password"><br/>
    邮箱:<input type="text" name="email"><br/>
    <input type="submit" value="注册">
</form>

</body>
</html>

4.邮件发送工具类
注意导包的问题

package com.wang.utils;

import com.sun.mail.util.MailSSLSocketFactory;
import com.wang.pojo.User;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;

//网站3s原则:用户体验
//多线程实现用户体验!! (异步处理)
public class Sendmail extends Thread{
    //用于给用户发送邮件的邮箱
    private String from = "24736743@qq.com";
    //邮箱的用户名
    private String username = "24736743@qq.com";
    //邮箱的密码
    private String password = "授权码";
    //发送邮件的服务器地址
    private String host = "smtp.qq.com";

    private User user;
    public Sendmail(User user){
        this.user = user;
    }

    //重写run方法的实现,在run方法中发送邮件给指定的用户
    @Override
    public void run() {
        try{
            Properties prop = new Properties();
            prop.setProperty("mail.host", host);
            prop.setProperty("mail.transport.protocol", "smtp");
            prop.setProperty("mail.smtp.auth", "true");

            // 关于QQ邮箱,还要设置SSL加密,加上以下代码即可
            MailSSLSocketFactory sf = new MailSSLSocketFactory();
            sf.setTrustAllHosts(true);
            prop.put("mail.smtp.ssl.enable", "true");
            prop.put("mail.smtp.ssl.socketFactory", sf);

            //1、创建定义整个应用程序所需的环境信息的 Session 对象
            Session session = Session.getDefaultInstance(prop, new Authenticator() {
                public PasswordAuthentication getPasswordAuthentication() {
                    //发件人邮件用户名、授权码
                    return new PasswordAuthentication("24736743@qq.com", "授权码");
                }
            });

            //开启Session的debug模式,这样就可以查看到程序发送Email的运行状态
            session.setDebug(true);

            //2、通过session得到transport对象
            Transport ts = session.getTransport();

            //3、使用邮箱的用户名和授权码连上邮件服务器
            ts.connect(host, username, password);

            //4、创建邮件
            MimeMessage message = new MimeMessage(session);
            message.setFrom(new InternetAddress(from)); //发件人
            //只有这处不一样,收件人不是写死的
            message.setRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmail())); //收件人
            message.setSubject("用户注册邮件"); //邮件的标题

            String info = "恭喜您注册成功,您的用户名:" + user.getUsername() + ",您的密码:" + user.getPassword() + ",请妥善保管,如有问题请联系网站客服!!";

            message.setContent(info, "text/html;charset=UTF-8");
            message.saveChanges();

            //发送邮件
            ts.sendMessage(message, message.getAllRecipients());
            ts.close();
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

5.编写对应的servlet处理类

package com.wang.servlet;

import com.wang.pojo.User;
import com.wang.utils.Sendmail;

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 RegisterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            //接收用户请求,封装成对象
            String username = request.getParameter("username");
            String password = request.getParameter("password");
            String email = request.getParameter("email");
            User user = new User(username,password,email);

            //用户注册成功之后,给用户发送一封邮件
            //我们使用线程来专门发送邮件,防止出现耗时,和网站注册人数过多的情况;
            Sendmail send = new Sendmail(user);
            //启动线程,线程启动之后就会执行run方法来发送邮件
            send.start();

            //注册用户
            request.setAttribute("message", "注册成功,我们已经发了一封带了注册信息的电子邮件,请查收!如网络不稳定,可能过会儿才能收到!!");
            request.getRequestDispatcher("info.jsp").forward(request, response);
        } catch (Exception e) {
            e.printStackTrace();
            request.setAttribute("message", "注册失败!!");
            request.getRequestDispatcher("info.jsp").forward(request, response);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

6.配置web.xml

<servlet>
    <servlet-name>RegisterServlet</servlet-name>
    <servlet-class>com.wang.servlet.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>RegisterServlet</servlet-name>
    <url-pattern>/RegisterServlet.do</url-pattern>
</servlet-mapping>

7.发送成功或失败的反馈页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>提示信息</title>
</head>
<body>
    ${message}
</body>
</html>

8.运行Tomcat测试。
后续会用springBoot框架写起来更简单
回到顶部

posted @ 2021-09-16 23:43  晒网达人  阅读(162)  评论(0编辑  收藏  举报