第二阶段-javaweb

第二阶段学习内容:单元测试,xml文件操作,数据库,jdbc,反射与内省,mybatis基础,HTML,JavaScript,通信协议与Tomcat服务器,HTTP协议与Servlet,转发_重定向 && 数据共享 _EL _JSTL,WEB_CRUD,Cookie_Session会话跟踪技术,文件的上传和下载 三层结构 (实现登录后的头像和用户名显示,以及上传头像),分页 分页过滤_高级查询 ,用户注销_ 记住账号_验证码 , 过滤器_监听器,点击右侧目录可以进行导航

day01 单元测试,配置文件

关于软件测试的理解:

黑盒测试

黑盒测试也称功能测试,是通过测试来检测每个功能是否能正常使用,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,在程序的接口上进行测试,检查程序功能是否 按照需求规格说明书 的规定正常使用。

结论: 手动测试,不用写代码

作用:主要试图发现下列几类错误
功能是否不正确或遗漏;
界面是否有错误;
输入和输出错误;
数据库访问错误;
性能是否有问题;
初始化和终止错误等

白盒测试

由开发人员来测试. 又称结构测试、透明盒测试、逻辑驱动测试或 基于代码 的测试。
它是按照程序内部的结构测试程序,通过测试来检测产品内部动作是否按照设计规格说明书的规定正常执行。测试者必须检查程序的内部结构,从检查程序的逻辑着手,得出测试数据。

结论:代码测试,需要写代码

作用
检查程序内部结构是否存在遗漏
程序功能是否完成
程序功能是否有错
程序功能是否按规格说明书实现

而作为 Java 攻城狮,白盒测试是离不开的,接下来要学习的 JUnit 测试就是白盒测试

JUnit包的导入与使用

JUnit包的导入

  • idea自带junit包,没有的话可以通过市场搜索下载
  • 可以手动添加,记得添加两个,junit-4.12.jar,hamcrest-core-1.3.jar(junit的依赖包);

junit测试工具的使用

@Test
public void testXxx() throws Exception {}

关于配置文件

配置文件的了解

配置文件的出现原因:

当我们上传文件给服务器,需要修改java文件里面的代码时,因为这些是硬编码,需要用户更新或者暂停使用,这样会损失资金。这时需要配置文件来存储一些必要的参数或者信息,这样当我们需要修 改的时候,只要修改里面的配置文件信息,配置文件属于静态资源文件,不是硬编码,所以不需要更新

​ 按理说只要能保存一些配置信息,供程序动态读取数据就OK,但是为了提高效率,在 IT 行业中,习惯使用两种具有特殊特点的文件来作为配置文件,分别是 properties 文件 和 XML文件

properties配置文件的使用

该文件称属性文件 / 资源文件 / 配置文件, 以 properties 作为文件后缀名

存数据特点: key=value 格式,多对数据使用换行分开。

user.properties

username=root

​ password=admin

使用注意事项

1.配置文件需要跟随着字节码走.需要放在 Resource Root 中. 会直接编译到字节码输出路径

2 在配置文件中,所有的数据都是字符串,不需要使用引号

3 在配置文件中不需要使用空格

1.使用绝对路径

@Test
public void getProperties() throws IOException {//先拿到配置文件类Properties properties = new Properties();//通过输入流,加载配置文件,使用绝对路径FileInputStream fileInputStream = new FileInputStream("D:\\WorkSpace\\idea-workspace\\第二阶段:javaweb\\resources\\user.properties");//配置文件类根据文件输入流来加载资源properties.load(fileInputStream);String usesrname = properties.getProperty("username");String password = properties.getProperty("password");System.out.println(usesrname+password);

2.使用相对路径

@Test
public void getPTest2() throws IOException {//拿到class文件加载器,默认是class文件目录ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();//从编译后的class文件里面找到资源文件InputStream fileInputStream =                             contextClassLoader.getResourceAsStream("user.properties");Properties properties =new Properties();//配置类,加载带有文件的文件输入流进行读取properties.load(fileInputStream);System.out.println(properties.getProperty("username"));System.out.println(properties.getProperty("password"));
}

day02:xml文件操作

1.关于xml文件的了解

XML 是用来做数据传输的,最终是需要读取到程序保存到内存中的.而从面相对象的角度来思考,

XML 的各个组成部分都需要使用一个类型来描述.

  1. XML 文件:把 XML 文档加载到内存中,使用 Document 对象来描述整个文档.
  2. 标签/元素:所有的标签,使用 Element 对象来描述.
  3. 属性:标签的属性,使用 Attribute 来描述.
  4. 文本:文本内容(文本/空格·回车)使用 Text 来描述.

根据上面四种成员的共性,继续抽象出父类:org.w3c.dom.Node.所以说,在XML中,一切皆节点.

2.操作xml文件,进行读写操作

2.1第一种方式:通过java的api

//students.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<students> <student id="01"> <name>牛逼</name> </student>  <student id="02"> <name>牛逼哈哈哈</name> </student>  <student><name>小黑</name></student><studnet id="4"><name>西门吹水</name></studnet>
</students>
    @Testpublic void getDateByXmlTest() throws ParserConfigurationException, IOException, SAXException, TransformerException {//先拿到document的工厂类,DocumentBuilderFactoryInputStream file2 =Thread.currentThread().getContextClassLoader().getResourceAsStream("students.xml");File file =new File("D:\\WorkSpace\\idea-workspace\\第二阶段:javaweb\\resources\\students.xml");DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();//在通过工厂类拿到documents的builder类DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();//在通过buider类的解析方法来获取到docunment类//绝对路径//Document document = documentBuilder.parse(new File("resources/students.xml"));Document document = documentBuilder.parse(file);System.out.println(document);//获取根元素Element root = document.getDocumentElement();System.out.println(root);/*** Element extends Node   ,所以说Element是子类,node是父类* root.getElementsByTagName获取的是一个nodelist,要用到Element.getTextContent()* 所以要强转为Element,这样Element既有Element状态又有node状态*/Element student = (Element) root.getElementsByTagName("student").item(1);System.out.println(student);Element name = (Element) student.getElementsByTagName("name").item(0);System.out.println(name);System.out.println(name.getTextContent());//插入Element studentRoot =  document.createElement("student");Element studentName =  document.createElement("name");studentName.setTextContent("小黑");studentRoot.appendChild(studentName);root.appendChild(studentRoot);//同步转换器Transformer transformer = TransformerFactory.newInstance().newTransformer();transformer.transform(new DOMSource(document),new StreamResult(file));}

2.2.第二种方式:用dom4j的jar包

@Test
public void dom4jTest() throws DocumentException, IOException {/*** 获取文档对象的内容*///获取文档对象File file = new File("resources/students.xml");//Stream API for XML把XML文档作为一个流来处理bai,就像java IO中的du流的概念。//这样就bai不同于DOM解析的方式,需要把XML文档整du个加载到内存中(有些XML是很大的),就可以像JAVA读写一般文件那样,读一点,处理一点。SAXReader saxReader = new SAXReader();org.dom4j.Document document = saxReader.read(file);//获取根元素org.dom4j.Element rootElement = document.getRootElement();//获取根元素下面的所有linknode元素List<org.dom4j.Element> student = rootElement.elements("student");for (org.dom4j.Element element : student) {String name = element.elementText("name");System.out.println(name);System.out.println("-----------------------------------------");}/*** 像文档对象插入信息*/org.dom4j.Element studentsElement = rootElement.addElement("studnet").addAttribute("id", "4");studentsElement.addElement("name").setText("西门吹水");//创建良好的缩进格式,用于描述一个MapReduce作业的输出格式OutputFormat prettyPrint = OutputFormat.createPrettyPrint();//用于向xml文件里写入东西,需要传入一个字节输出流的文件,和输出格式XMLWriter xmlWriter = new XMLWriter(new FileWriter(file), prettyPrint);//将新的文档对象写入xml中xmlWriter.write(document);xmlWriter.close();
}

day03:数据库

1.关于数据库与数据库管理系统的了解

1.1数据库(mysql底层是B树)

​ 数据库是按照数据结构来组织、存储和管理数据的仓库。----> 存储和管理数据的仓库.(说白了,数据库还是一些文件,只是这些文件使用了对应的数据结构来组织数据的).

1.2数据库管理系统

DBMS (Database Management System):专门用于管理数据库的计算机系统软件
数据库管理系统能够为数据库提供数据的定义、建立、维护、查询和统计等操作功能,并完成对数据完整性、安全性进行控制的功能。我们一般说的数据库,就是指的DBMS(数据库服务器:管理多个数据库文件的软件)

2.数据库的常用类型

MySQL Java
INT int
BIGINT long
DECIMAL BigDecimal
DATE/DATETIME java.util.Date
VARCHAR String

3.表的操作(DDL掌握)

CREATE TABLE 表名 (列名1    列的类型   [约束],列名2    列的类型   [约束],....列名N    列的类型   约束
);
-- 注意:最后一行没有逗号
DROP TABLE 表名;

表的约束(针对于某一列):

  1. 非空约束:NOT NULL,不允许某列的内容为空。
  2. 设置列的默认值:DEFAULT。
  3. 唯一约束:UNIQUE,在该表中,该列的内容必须唯一
  4. 主键约束:PRIMARY KEY, 非空且唯一。
  5. 主键自增长:AUTO_INCREMENT,从 1 开始,步长为 1。
  6. 外键约束:FOREIGN KEY,A表中的外键列. A表中的外键列的值必须参照于B表中的某一列(B表主键)

主键设计 :

  1. 单字段主键,单列作为主键,建议使用
  2. 复合主键,使用多列充当主键,不建议。

主键分为两种 :

  1. 自然主键: 使用有业务含义的列作为主键 (不推荐使用);
  2. 代理主键:使用没有业务含义的列作为主键 (推荐使用);

4.DML 操作(掌握)

-- -增加
INSERT INTO 表名 (列1,列2,column3...)  VALUES(值1,值2,value3...);
-- -修改
UPDATE 表名
SET 列1 = 值1, 列2 = 值2, column3 = value3...
WHERE [条件]
-- -删除
DELETE FROM 表名 WHERE [条件]

5.DQL 查询操作(掌握)

5.1普通查询

-- -去重查询
SELECT  DISTINCT 列名, ...  FROM 表名;-- -使用运算符
对 number 型数据可以使用算数操作符创建表达式(+  -  *  /)
对 date 型数据可以使用部分算数操作符创建表达式 (+  -)-- - 使用别名
SELECT 列名 AS 别名 FROM 表名 [WHERE];
SELECT 列名 别名 FROM 表名 [WHERE]-- -使用连接符,按格式输出
CONCAT(字符串1,字符串2,...)-- 查询商品的名字和零售价。格式: xxx 商品的零售价为:ooo
SELECT CONCAT(productName,'商品的零售价为:',salePrice) FROM product

5.2 过滤查询

-- - 使用where进行过滤
SELECT  <selectList>
FROM    表名
WHERE   条件;-- - 根据比较运算符号
=,  >  ,  >=  ,  <  ,  <=  ,  !=
不等于:<>  等价  !=-- -逻辑运算符AND (&&)  | 如果组合的条件都是TRUE,返回TRUE                      OR (\|\|) | 如果组合的条件之一是TRUE,返回TRUE                    NOT (!)   | 如果下面的条件是FALSE,返回 TRUE,如果是 TRUE ,返回 FALSE-- -运算符优先级
优先级         运算符
-----------------------------
1               所有比较运算符
2               NOT
3               AND
4               OR-- -范围和集合WHERE 列名 BETWEEN minValue AND maxValue; -- 闭区间WHERE 列名 IN (值1,值2....);-- -判断空WHERE  列名 IS NULL;-- -模糊匹配查询WHERE 列名 Like '%M_'-- -结果排序使用 ORDER BY 子句将查询结果进行排序ASC : 升序,缺省。DESC: 降序。
ORDER BY 子句出现在 SELECT 语句的最后。-- -分页查询
SELECT <selectList>
FROM 表名
[WHERE]   LIMIT ?,?
-- 第一个? : 开始行的索引数 beginIndex
-- 第二个? : 每页显示的最大记录数 pageSize
beginIndex = (currentPage - 1) * pageSize
-- 每页显示 3条数据
-- 第一页: SELECT * FROM product LIMIT 0,3
-- 第三页: SELECT * FROM product LIMIT 6,3-- -统计函数
+ COUNT(*) : 统计表中有多少条记录
+ SUM(列)  : 汇总列的总和
+ MAX(列)  :  获取某一列的最大值
+ MIN(列)  : 获取某一列的最小值
+ AVG(列)  : 获取列的平均值

6.数据库的备份

忠告: 在企业修改数据库之前先备份

MySQL自身的数据库维护:

通过 cmd 命令进入dos窗口
1.导出:mysqldump -u账户 -p密码 数据库名称 > 脚本文件存储地址

 mysqldump -uroot -padmin jdbcdemo >  D:/jdbcdemo_bak.sql

2.导入:mysql -u账户 -p密码 数据库名称 < 脚本文件存储地址

 mysql -uroot -padmin jdbcdemo <   D:/jdbcdemo_bak.sql

2.导入:mysql -u账户 -p密码 数据库名称 < 脚本文件存储地址
mysql -uroot -padmin jdbcdemo < D:/jdbcdemo_bak.sql

Navicat工具的导入和导出:
Navicat工具的备份和还原: 需要工具支持的格式才可以恢复

day04 jdbc01

一 、关于jdbc的持久化理解

持久化:将数据存储在一个无电源的存储设备中以供以后使用。

在大多数情况下,特别是在企业应用程序中,数据持久性意味着内存中的数据存储在硬盘上并“固化”,而持久性的实现主要是通过各种关系数据库来实现的。存储在关系数据库中的内存数据也可以存储在磁盘文件或XML数据文件中。在Java中,数据库访问技术只能通过JDBC访问数据库。

JDBC对数据库的访问有两种主要形式:

  • 直接使用JDBC API访问数据库服务器(MySQL/Oracle)。
  • 间接使用JDBC API访问数据库服务器的第三方0/R映射工具,如Hibernate、MyBatis等(底层仍然是JDBC)

JDBC是Java访问数据库的基石,而其他技术都是对JDBC的封装。

二、jdbc的由来以及概述

什么是 jbdc?

​ JDBC( Java数据库连接)是一个Java API来执行SQL语句,可以提供多种关系数据库统一访问,它由一组类和接口用Java语言编写的。JDBC提供了一个基准,使数据库开发人员能够编写数据库应用程序。

​ JDBC为访问不同数据库提供了统一的方式,为开发人员屏蔽了细节。

​ JDBC的目标是使Java程序员可以使用JDBC连接实现任何JDBC数据库系统(驱动程序),

​ 这使得程序员不需要拥有太多特定数据库系统的属性,从而极大地简化和加快了开发过程。

jdbc的接口的实现类形成?

总结:JDBC 本身是Java连接数据库的标准,是数据库连接的抽象层由一组用Java编写的类和接口组成,接口的实现由不同的数据库供应商完成

三、jdbc的基本操作

导入mysql-connector-java-5.1.47.jar

  • 加载数据库驱动
  • 获取数据库连接对象
  • 通过数据库连接对象获取语句对象或者预编译对象,并编写sql语句
  • 然后通过预编译对象设置占位符值
  • 预编译(语句)对象执行sql语句
  • 关闭资源

第一种方式:Statement

public void insert2(User user) throws ClassNotFoundException, SQLException {Connection conn = null;Statement statement = null;//加载数据库驱动try {Class.forName("com.mysql.jdbc.Driver");//获取连接对象(通过驱动注册器来)conn= DriverManager.getConnection("jdbc:mysql:///practice","root","admin");//创建预编译对象和sql语句String sql = "insert t_user(t_name,age) values('"+user.getUsername()+"',"+user.getAge()+")";System.out.println(sql);statement = conn.createStatement();//sql语句里的占位符设置值//执行sql语句statement.executeUpdate(sql);} catch (Exception e) {e.printStackTrace();}finally {//关闭资源try {if (statement != null){statement.close();}} catch (Exception e) {e.printStackTrace();}try {if (conn != null){conn.close();}} catch (Exception e) {e.printStackTrace();}}}
@Test
public void insert() throws SQLException, ClassNotFoundException {User user = new User("石文轩", 18);userDao.insert2(user);
}

第二种方式:PreparedStatement预编译对象

//这里以插入为例子
public void insert(User user) {Connection conn = null;PreparedStatement statement = null;//加载数据库驱动try {Class.forName("com.mysql.jdbc.Driver");//获取连接对象(通过驱动注册器来)conn= DriverManager.getConnection("jdbc:mysql:///practice","root","admin");//创建预编译对象和sql语句String sql = "insert into t_user(t_name,age) values(?,?) ";statement = conn.prepareStatement(sql);//sql语句里的占位符设置值statement.setString(1,user.getUsername());statement.setInt(2,user.getAge());//执行sql语句statement.execute();} catch (Exception e) {e.printStackTrace();}finally {//关闭资源try {if (statement != null){statement.close();}} catch (Exception e) {e.printStackTrace();}try {if (conn != null){conn.close();}} catch (Exception e) {e.printStackTrace();}}}
private UserDAO userDao = new UserDAOImpl();
@Test
public void insert() throws SQLException, ClassNotFoundException {User user = new User("石文轩", 23);userDao.insert(user);
}

四、dao层思想

数据访问对象是一个面向对象的数据接口。

顾名思义就是处理数据库,夹在业务逻辑和数据库资源之间,所有对数据源的访问操作抽象封装在一个公共API中。编写程序是要建立一个接口,该接口定义了应用程序将在所有事务中使用的方法。主操作:添加和删除(CRUD)。

dao层结构如下:

day05 jdbc02 (抽成封装,事务,数数据库连接池的使用与理解,statement与prepareStatement的区别,)

一、抽成封装的历程

1.1.简单封装成工具类

//获取数据库连接
public Connection getConnnection(){try {Class.forName("com.mysql.jdbc.Driver");return DriverManager.getConnection("jdbc:mysql///practice");} catch (Exception e) {e.printStackTrace();}return null;
}//关闭资源
public static void  close(Connection conn, Statement preparedStatement, ResultSet resultSet){try {//关闭前先判断,防止空指针异常if (resultSet!=null){resultSet.close();}} catch (SQLException e) {e.printStackTrace();}finally {try {if (preparedStatement != null){preparedStatement.close();}} catch (Exception e) {e.printStackTrace();}try {if (conn != null){conn.close();}} catch (Exception e) {e.printStackTrace();}}
}

1.2.使用静态代码块和properties配置文件

原因:由于每次都要加载数据库驱动,导致性能变差,以及数据库的配置账号密码url等导致硬编码问题的出现

private static Properties properties = new Properties();
//加载静态资源文件的静态代码块
static {try {InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties");properties.load(resourceAsStream);Class.forName(properties.getProperty("Driver"));} catch (Exception e) {e.printStackTrace();}
}
//获取数据库连接
public static Connection getConnection(){try {return DriverManager.getConnection(properties.getProperty("url"),properties.getProperty("username"),properties.getProperty("password"));} catch (SQLException e) {e.printStackTrace();}return null;}

二、jdbc的事务操作:

事务操作的特征;

  • 原子性:原子在化学中是最小的单位,事务里的操作,要么全部发生,要么都不发生
  • 一致性:事务必须使数据库从一个一致性状态转换为另外一个一致性状态(保证数据完整性,不被破坏)
  • 隔离性:1.一个事务的执行操作不被其他事务干扰,2.事务内部的操作和数据对内部是互不干扰的
  • 持久性:**事务一旦提交,那么数据库中的数据改变就是永久性的,**接下来的操作也不会有上一步有干扰
try{//取消事务的自动提交机制,设置为手动提交.//默认是帮你自动提交事务connection对象.setAutoCommit(fal se);//操作1//操作2//异常//操作311..//手动提交事务connection对象.commit();}catch(Exception e){//处理异常//回滚事务connection对象.rol1back();}

注意细节:

  • 默认情况下,DDL操作是默认提交事务的
  • 查询是不需要事务的,因为只是取出数据而没有改变数据,但是一般开发我们会把查询放在事务中一起提交
  • 开发过程中,数据库数据没变,注意那就是没有提交事务
  • 在MySQL中i’n’no’DB支持事务和外键,而MYISAM不支持

三、数据库连接池

3.1.关于数据库连接池的理解,为什么会出现连接池

当我们没有数据库连接池时,

1.当一个用户进行增删改查时,连接被创建和占用, < --------------------------------------------------------

2.当用户进行完操作时,连接被销毁 -

3.当用户那么下一个用户必须等待上一个用户使用后,才能进行增删改查的数据库操作。 -

4.下一个用户进行增删改查时--------------------------------------------------------------------------------------------

举列子:当一个人过河时,他会先搭建一座桥,过去,当回来时,他会把桥给拆掉,下一个人过去时,要重新搭建一座桥,当回来时则也会拆掉这座桥,一直反复。

这样我们发现,当多个用户进行操作时(高并发),则下一个用户会需要一个很长的等待时间。

当我们有数据库连接池时,

数据库连接池里会根据当前用户请求连接的数量来初始化connection对象,也就是说有多个连接对象

当一个用户进行操作使用完连接对象时,对象会被返回给下一个等待的用户继续使用,而不会销毁在创建

举例子;当人们要过桥时,政府会根据过河的人数来创建桥的个数,当一个人来回一座桥后,这座桥不会被摧毁,下一个人继续用桥,这样多个桥重复使用。

数据库连接池大大提高了数据库的访问性能,增强了用户的体验。

3.2.关于数据库连接池的理解和使用

sum公司,定义了dataSouorce的接口,给mysq’l的各个开发商来对各种语言进行实现和开发(与jdbc一样)

连接池目前有很多种,其中性能最好的是阿里巴巴开发的druid连接池

druid连接池的两种获取连接对象方法:

注意首先导入:dom4j-2.1.1.jar

1.直接获取DruidDataSource

/**
* 方式一
* 用来获取德鲁池对象的getconnection方法来获取连接对象
* new一个德鲁池对象,并为此设置四个参数
*/
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(properties.getProperty("DriverClassName"));
druidDataSource.setUrl(properties.getProperty("url"));
druidDataSource.setUsername("username");
druidDataSource.setPassword("password");
//通过getConnection方法来获取druidDataSourcConnection(实现了Connection接口的子类)
druidDataSource.getConnection()

2.获取Datasource

/**
* 方式二* 返回对象改变为dataSource改变
* 通过传入properties配置文件给德鲁依池的工厂类,来获取datasource对象
* 注意:配置文件的key值必须按照德鲁池对象里规定写
*/
private static DataSource dataSource = null;
dataSource =  DruidDataSourceFactory.createDataSource(properties);
//注意这里的properties文件必须按照DruidDataSourceFactory里的的规范进行key的编写

注意细节;

1.通过DriverManager.getConnection(“jdbc:mysql///practice”);

​ 返回的实现connection接口实现类,close()关闭方法是关闭connection连接

DriverManager.getConnection("jdbc:mysql///practice");

2.通过连接池获取的对象是实现了Datasource的实现类

其close()方法,不是关闭连接,而是该connection连接对象返回连接池。

连接池有个超长等待时间,在超过等待时间之后,没有请求连接的话,连接池会关闭所有连接对象。

  DruidPooledConnection d = druidDataSource.getConnection()d.close();

3.都实现了connection接口的实现类,根据需求close方法不同

四、statement与prepareStatement的区别

4.1.使用prepareStatement的可读性好,可维护性高

为啥可读性好?

statement进行的语句需要进行字符串的拼接:

例如:

String sql = "insert t_user(t_name,age) values('"+user.getUsername()+"',"+user.getAge()+")";

prepareStatement的语句通过占位符来进行参数的设置值:

例如:

String sql = "delete from t_user where  id = ?";
statement = conn.prepareStatement(sql);
//sql语句里的占位符设置值
statement.setInt(1, (int) id);

为啥可维护性高?

当我们需要修改需求时,既是修改sql语句

使用statement则需要进行复杂的字符串拼接,且容易写错,当修改的量多时,非常不好维护。

使用preparement时,则只需要设置占位符的值,则不容易出错,相比之下更好维护。

4.2.使用prepareStatement安全性高,不会有sql注入的危险

为啥安全性更高?

当使用statement时,由于sql语句是字符串的拼接,所以很有可能导致程序漏洞,使黑客潜入

/**
通过这样的操作,黑客可以越过登录而直接进入系统
*/
String username = " ' OR 1=1 OR ' ";
String password = "admin";
String sql = "select * from user where username ='"+username+"'and password = '"+password+"' ";

而当我们使用prepareStatement时,则不存在拼接问题

String username = " ' OR 1=1 OR ' ";
String password = "admin";
String sql = "select * from user where username = ? and password = ? ";
PreparedStatement statement = connection.prepareStatement(sql);
System.out.println(sql);
statement.setString(1,username);
statement.setString(2,password);

day06 反射与内省

反射

关于反射的理解

什么是反射?

反射是根据字节码对象,去获取到类中的成员信息(构造器,方法,字段)。

反射的好处?

当我们不清楚外界会传入什么对象类型时,既是不清楚外界的真实类型,我们可以通过传参,通过泛型配合反射,拿到当时对象的真实类型进行操作,动态的进行操作某些东西,例如框架的底层就是反射。

什么是字节码对象?

字节码对象的形成过程图:

当我们写一个java程序时,程序经过javac编译成.class的字节码文件,当虚拟机jvm执行java.exe时,将这些字节码文件抽成一个通用的模板,里面有构造器,方法,字段,当我们需要创建一个对象时,jvm会调出包含person.class信息的class字节码对象,通过这个字节码对象去实例对象(通过构造器),拿取方法,字段。

有关反射操作

获取字节码对象

/***获取字节码对象的三种方式*/
//最常用的方式
Class aClass = Class.forName("反射.Person");  //权限类名
Class bClass =  new Person().getClass();
Class cClass = Person.class;
System.out.println(aClass);
System.out.println(bClass);
System.out.println(cClass);
//可知字节码只会加载一次,无论那种方式都是一样的
System.out.println(int.class);
System.out.println(int[].class);
//可知对象不同,获取到字节码对象也不同
System.out.println("------------------------------------------------");
/**

利用反射获取构造器的五种方法

 /*** 利用反射操作构造器的五种方法*/Class pClass = Person.class;//1.获取所有public构造器Constructor[] constructors = pClass.getConstructors();for (Constructor constructor : constructors) {System.out.println(constructor);}System.out.println("-------------------------------------------------------");//2.获取所有构造器,包括privateConstructor[] declaredConstructors = pClass.getDeclaredConstructors();for (Constructor declaredConstructor : declaredConstructors) {System.out.println(declaredConstructor);}System.out.println("-------------------------------------------------------");//3.获取无参构造器Constructor constructor = pClass.getConstructor();System.out.println(constructor);System.out.println("-------------------------------------------------------");//4.获取带参构造器Constructor constructor1 = pClass.getConstructor(String.class, String.class);System.out.println(constructor1);System.out.println("-------------------------------------------------------");//5.获取指定private构造器Constructor declaredConstructor = pClass.getDeclaredConstructor(String.class);System.out.println(declaredConstructor);System.out.println("-------------------------------------------------------");//实战,私有,公共构造器创造实例化对象//无参构建对象Class rClass = Class.forName("反射.Person");
//        Constructor constructor2 = rClass.getConstructor(String.class, String.class);
//        Object o = constructor2.newInstance("小黑", "dfdf");Object o = rClass.newInstance();System.out.println(o);//私有构建对象Constructor declaredConstructor1 = rClass.getDeclaredConstructor(String.class);//设置私有化构造器为可访问declaredConstructor.setAccessible(true);Object  object2 = declaredConstructor.newInstance("llll");System.out.println(object2);//使用一字节码创建对象一步到位Class rClass = Class.forName("反射.Person");Object o = rClass.newInstance();

利用反射获取方法

 Class c = Person.class;
Object o = c.newInstance();
/*** 获取所有public方法,包括父类的*/
Method[] methods = c.getMethods();
for (Method method : methods) {System.out.println(method);
}
System.out.println("-------------------------------------");/*** 获取所有private方法,不包括父类的*/
Method[] declaredMethods = c.getDeclaredMethods();
for (Method m : declaredMethods) {System.out.println(m);
}
System.out.println("-------------------------------------");
/*** 获取指定参数的方法,包括父类*/
Method run = c.getMethod("run", String.class);
System.out.println(run);
run.invoke(o,"fdf");
System.out.println("-------------------------------------");
/*** 获取指定参数的private方法,不包括父类*/
Method dowork = c.getDeclaredMethod("dowork", String.class);
System.out.println(dowork);
dowork.setAccessible(true);
dowork.invoke(o, "hhhhh");/*** 调用静态方法,静态方法是类来调用*/
Method sayHellow = c.getMethod("sayHello", String.class);
sayHellow.invoke(null,"iiiiii");

利用反射获取字段

     Class aClass  =  Person.class;Object o = aClass.newInstance();/*** 获取字段*///获取公共字段publicField[] fields = aClass.getFields();for (Field field : fields) {System.out.println(field);}System.out.println("-------------------");//获取所有字段包括provateField[] declaredFields = aClass.getDeclaredFields();for (Field declaredField : declaredFields) {System.out.println(declaredField);}System.out.println("-------------------");/*** 获取单个字段*/Field a = aClass.getDeclaredField("username");System.out.println(a);a.setAccessible(true);a.set(o,"哈哈哈哈");System.out.println(a.get(o));System.out.println("-------------------");
}

内省

什么是内省?

/*** 用反射获取javabean的属性以及方法方法非常麻烦,所以sum公司专门提供了一套操作api,内省* 属性指的是(除去get与set开头剩下的英文)- > 首字母小写*/

内省的作用?

以属性为主

  • 获取到属性名和属性类型相关的状态信息
  • 获取到属性对应的读写方法操作属性

内省的操作

    //创建对象来调用方法Person person = Person.class.newInstance();//讲javabean转为BeaninfoBeanInfo beanInfo = Introspector.getBeanInfo(Person.class,Object.class);//获取所有属性PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();for (PropertyDescriptor p : propertyDescriptors) {//属性名System.out.println(p.getName());//属性类型System.out.println(p.getPropertyType());//获取属性的getter与setter方法//获取get方法Method readMethod = p.getReadMethod();Method writeMethod = p.getWriteMethod();System.out.println("getter方法"+readMethod);System.out.println("setter方法"+writeMethod);if ("username".equals(p.getName())){writeMethod.invoke(person,"hhhhh");}System.out.println(readMethod.invoke(person));}
}

javaBean与map之间的转换

map转javaBean

public static Object Map2Bean(HashMap<String, Object> hashMap,Class clazz) throws IntrospectionException, IllegalAccessException, InstantiationException, InvocationTargetException {/*** 将map里的属性值键值对,存入一个对象里面*/Object o =  clazz.newInstance();BeanInfo beanInfo = Introspector.getBeanInfo(clazz,Object.class);PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();for (PropertyDescriptor p : propertyDescriptors) {String key = p.getName();Object value = hashMap.get(key);p.getWriteMethod().invoke(o,value);}return o;
}

javaBean转map

//将javabean里的属性封装到map里面
public static Map<String,Object> bean2Map(Object o) throws IntrospectionException, IllegalAccessException, InstantiationException, InvocationTargetException {HashMap<String,Object> hashMap = new HashMap<>();//获取所有属性以及值BeanInfo beanInfo = Introspector.getBeanInfo(Person.class);PropertyDescriptor[] p = beanInfo.getPropertyDescriptors();for (PropertyDescriptor propertyDescriptor : p) {String key = propertyDescriptor.getName();Object value = propertyDescriptor.getReadMethod().invoke(o);hashMap.put(key,value);}return hashMap;
}

day07 mybaits基础

什么是框架和半成品?

  • 框架:框架是一个半成品,对一些基础代码的封装并封装打包成api,用来提高工作效率和简化代码的开发量
  • 半成品:指的是某件事物物品需要其他的东西来配合(驱动)才能使用。

​ 举列子:早餐店的包子,店家进货的时候,不能直接拿来吃,而是需要加热煮熟

​ 它并不能在进货的时候直接拿来吃,而是需要加热,所以说包子是个半成品。

什么是mybaits框架和好处?

mybaits框架一个优秀的持久层框架,支持定制sql,存储过程以及高级映射。mybaits几乎避免了所有jdbc代码和手动设置参数以及手动获取结果集(从而避免了代码量多而容易代码出错)

mybatis入门案列之增删改查,引入数据库配置文件db.properties

包的结构为:

DAO思想的项目结构:

全局配置文件:mybaits-cfg.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><properties resource="db.properties" /><typeAliases><!-- <typeAlias type="cn.ybzy.jdbc.domain.Food" alias="Food"></typeAlias>--><package name="cn.ybzy.jdbc.domain"/></typeAliases><environments default="dev"><environment id="dev"><!-- mybits内置的事务管理器,对应jdbcTreansaction类--><transactionManager type="JDBC"></transactionManager><!--mybaits内置连接池--><dataSource type="POOLED"><!--连接数据库的四要素--><property name="driver" value="${Driver}"/><property name="url" value="${url}"/><property name="username" value="${username}"/><property name="password" value="${password}"/></dataSource></environment></environments><mappers><!-- 将某个类的配置文件放进全局配置文件,因为正在加载是全局配置文件,不配置的话,找不到--><!-- 配置分配置文件所在的包路径--><mapper resource="cn/ybzy/jdbc/mapper/FoodMapper.xml"></mapper></mappers></configuration>

局部配置文件:FoodMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.ybzy.jdbc.mapper.UserMapper"><insert id="insert" >insert into food(name,price) values (#{name},#{price})</insert><delete id="deleteById">delete from food where  id = #{id}</delete><update id="updateById">update food set name = #{name },price = #{price} where  id = #{id}</update><select id="selectById" resultType="cn.ybzy.jdbc.domain.Food">select  * from food where  id = #{id}</select><select id="selectAll" resultType="Food">select * from food;</select>
</mapper>

实现类:FoodDAOImpl

public class FoodDAOImpl  implements FoodDAO {@Overridepublic void insert(Food food) throws IOException {//1.通过Resouce资源类来拿到全局配置文件InputStream resourceAsStream = Resources.getResourceAsStream("mybaits-cfg.xml");//2.通过SqlSessionFactoryBuilder解析全局配置文件来并获取SqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);//3.通过SqlSessionFactory来获取SqlSession对象SqlSession sqlSession = sqlSessionFactory.openSession();//4.调用方法sqlSession.insert("cn.ybzy.jdbc.mapper.UserMapper.insert",food);//提交事务sqlSession.commit();//关闭资源sqlSession.close();}@Overridepublic void deleteById(Long id) throws IOException {//1.通过Resouce资源类来拿到全局配置文件InputStream resourceAsStream = Resources.getResourceAsStream("mybaits-cfg.xml");//2.通过SqlSessionFactoryBuilder解析全局配置文件来并获取SqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);//3.通过SqlSessionFactory来获取SqlSession对象SqlSession sqlSession = sqlSessionFactory.openSession();//4.调用方法sqlSession.delete("cn.ybzy.jdbc.mapper.UserMapper.deleteById",id);//提交事务sqlSession.commit();//关闭资源sqlSession.close();}@Overridepublic void updateById(Food food) throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("mybaits-cfg.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();sqlSession.update("cn.ybzy.jdbc.mapper.UserMapper.updateById",food);sqlSession.commit();sqlSession.close();}@Overridepublic Food selectById(Long id) throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("mybaits-cfg.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();Food food  =  sqlSession.selectOne("cn.ybzy.jdbc.mapper.UserMapper.selectById",id);sqlSession.commit();sqlSession.close();return food;}@Overridepublic List<Food> selectAll() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("mybaits-cfg.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();List<Food> foodList =  sqlSession.selectList("cn.ybzy.jdbc.mapper.UserMapper.selectAll");sqlSession.commit();sqlSession.close();return foodList;}
}

测试类:

package cn.ybzy.jdbc.dao.impl;import cn.ybzy.jdbc.dao.FoodDAO;
import cn.ybzy.jdbc.domain.Food;
import org.junit.Test;import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;import static org.junit.Assert.*;public class FoodTest {private FoodDAO foodDAO = new FoodDAOImpl();@Testpublic void insert() throws IOException {Food f = new Food("dfdf",new BigDecimal("15"));foodDAO.insert(f);}@Testpublic void deleteById() throws IOException {foodDAO.deleteById(1L);}@Testpublic void updateById() throws IOException {Food food = new Food(2L, "可乐", new BigDecimal("4.00"));foodDAO.updateById(food);}@Testpublic void selectById() throws IOException {Food food = foodDAO.selectById(2L);System.out.println(food);}@Testpublic void selectAll() throws IOException {List<Food> foodList = foodDAO.selectAll();System.out.println(foodList);}
}

设置别名

    <typeAliases><!-- 两种方式--><!-- 第一种方式 --><!-- <typeAlias type="cn.ybzy.jdbc.domain.Food" alias="Food"></typeAlias>--><!-- 第二种方式 --><package name="cn.ybzy.jdbc.domain"/></typeAliases>

日志框架

导入日志框架包

log4j,slf4j-api,slf4j-logj12

# Global logging configuration
log4j.rootLogger=ERROR, stdout
# MyBatis logging configuration...
log4j.logger.cn.ybzy.jdbc.mapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

myBaitsUtils工具类抽取

public class MyBaitsUtil {private MyBaitsUtil() {}private  static SqlSessionFactory sqlSessionFactory;static {try {InputStream resourceAsStream = Resources.getResourceAsStream("mybaits-cfg.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);} catch (IOException e) {e.printStackTrace();}}public  static  SqlSession getSqlSession(){return  sqlSessionFactory.openSession();}
}

细节:

异常:
delete is ambiguous in Mapped Statements collection (try using the full name including the namespace, or rename one of the entries)

当有多个mapper文件时,且他们的删除语句的id都是delete时,session.delete(“delete”,user);就不能辨别是哪个文件的delete

day08 HTML

8.1 浏览器的发展

  • 浏览器的内核是用来进行页面的渲染,不同浏览器用的内核不同,同样的代码渲染出来的页面也不一样。
  • 360,QQ,搜狗这些使用的不是自己的内核
  • I微软IE,Edge,谷歌,火狐有自己的内核

而I微软IE,Edge不遵循万维网w3chool制定的网页行业规范,而其他人使用规范,导致很多人不用,都不愿意写多份代码,此为由于ie不遵循规范导致效率低,性能也低。然后微软推出edge,但还是没有遵循规范,打开页面显示“请给edge一次机会”。火狐也有自己的内核且遵循规范。可是由于它开发新功能不是很成熟导致没有谷歌好用,所以建议用谷歌。

8.2 html的基本操作

8.2.1 HTML的基本语法

8.2.2 常用的HTML标签

8.2.3 超链接和锚点

8.2.4 列表标签

8.2.5 图片标签

8.2.6 表格标签

表格的分层标签,当我们使用时,将thead,tbody,tfoot顺序任意变换都不会影响,因为这些分层标签已经区分了位置。

8.2.7 表单标签

day09 javaScript day01

9.1 什么是js,js的作用是什么?

JavaScript(简称“JS”) 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。用于web开发页面的脚本语言。

js的组成部分与相关作用:

  • ECMAScript(核心) :JavaScript 语言基础(规定了 JavaScript 脚本的核心语法
  • **DOM(文档对象模型)**规定了访问 HTML 和 XML 的接口(提供了访问 HTML 文档(如 body、form、div、textarea 等)的途径以及操作方法);Node:Document、Element、Attr、Text
  • BOM(浏览器对象模型):提供了独立于内容在浏览器窗口之间进行交互的对象和方法(提供了访问某些功能(如浏览器窗口大小、版本信息、浏览历史记录等)。

9.2 关于变量,数据类型

数据类型如下:

简单类型: StringNumberBooleanNullundefined

对象类型: Object, Array, Function

注意:string的单引号和双引号都表示字符串

  • 变量的定义使用var的关键字,如果没有初始化,则默认是undefine
  • 变量的数据类型由变量的类型决定

9.3 js常用的方法

alert:弹窗

console.log控制台打印信息

typeof:判断变量的类型

9.4 运算符

9.4.1 比较运算符

= :表示赋值

== : 表示比较内容,不考虑数据类型

===: 表示比较内容,考虑数据类型

console.log("比较运算符----------------------");
var b = 2;
b = 3
console.log(b);
console.log("18" == 18)  //true
console.log("18" === 18)  //false

9.4.2 逻辑运算符

&& :找到第一个false的值,否则打印最后一个值

console.log("逻辑运算符&&----------------------");
console.log(true && true);  //true
console.log(1 && true);      //true
console.log(1 && 2);         //2
console.log("A" && 2);       //2
console.log("" && 2);         //""
console.log(null && "B");     //null
console.log("A" && "B");      //"B"
console.log(1 && 2 && 3);      //3
console.log(1 && null && 3);  //null
console.log("" && null && 0);  //""

|| :找到第一个true的值,否则打印最后一个值

console.log("逻辑运算符||----------------------");
console.log(true || true);     //tue
console.log(1 || true);       //1
console.log(1 || 2);       //1
console.log("A" || 2);     //"A"
console.log("" || 2);       //2
console.log(null || "B");    //"B"
console.log("A" || "B");     //"A"
console.log(1 || 2 || 3);    //1
console.log(1 || null || 3);   //1
console.log("" || null || 0);  // 0

9.5 函数

9.5.1 普通函数

// JS中不用指定函数的返回值类型(无论怎样都有返回,JS是弱类型语言)
function 函数名([参数名称1, 参数名称2, ..., 参数名称N]){
//程序代码
[return 值;]
}
//关于函数的运用
function print() {console.log("无参无返回值");
}
function print2() {return 2;
}
print();
console.log(print2());
function print3(x) {console.log("有参数,没返回值"+x);
}
print3(4);
function print4(x) {return x;
}
console.log(print4(5));

9.5.2 匿名函数

var 变量名 = function([参数名称1, 参数名称2, ..., 参数名称 N]){
//程序代码
[return 值;]
}
console.log("匿名函数的测试||----------------------");
var add = function (x,y) {return x + y;
}
console.log(add(1,1));

9.5.3 普通函数调用匿名函数(常用!)以及arguments使用

通过控制台打印arguments,可以知道被调用的函数,需要传入几个参数

console.log("普通函数调用匿名函数-------------------");
//普通函数调用匿名函数
//定义匿名函数
var a1 = function (x,y) {return x + y;
}
//普通函数
function a2(fun) {console.log(fun(1, 3));
}
a2(a1);
//arguments,可以知道执行函数的参数个数,根据他可以确定要传入几个参数
a2(function (x,y) {console.log(x);console.log(arguments);
})

9.5.4 箭头函数

console.log("箭头函数----------------------------------");
var h = () => {console.log("我是箭头函数");
}
h();
var  h2 = username =>{console.log(username);
}
h2("username")
var  h3 = (username,password) =>{console.log(username,password);
}
h3("username","password");

9.6 对象操作

9.6.1 对象的基本创建与基本操作

console.log("构造函数-----------------------------------------");
function Person(username,password) {  //构造函数的首字母必须大写this.username = username;this.password = password;this.sleep = function (time) {console.log(time);}
}
var p = new Person("aaaa","bbbb");
console.log(p.username);
console.log(p.password);
p.sleep(1);
console.log(p);

9.6.2 对象的简便创建

注意对象的属性和方法可以在对象创建完后,任意添加属性和方法

var h = {username : "cccc",password : "dddd",a : function () {console.log(this.username);}}console.log(h.username);
console.log(h.password);
h.a();
h.o = "dfdf";
console.log(h);
h.a= function () {console.log("改变了函数");
}
h.a();

9.7 数组

9.7.1 数组的定义和基本使用

console.log("数组的使用----------------------------")
//创建数组对象
var arr = new Array();
console.log(arr)
//静态
var arr1 = new Array("1","2");
console.log(arr1)
//动态
var arr2 = new  Array(2);
console.log(arr2)
//简写
var arr3 = ["1","2"];
console.log(arr3)//数组没有越界,元素会显示undefine
console.log(arr1[3]);
arr1[2] = "12";
console.log(arr1[2]);
arr2[3] = "444";
console.log(arr2);
console.log(arr2.join(","));

9.7.2 数组遍历的五种方式

jsconsole.log("数组的五种遍历方式------------")
//1.for循环
var a = new Array("A","B","C");
for(var i = 0; i < a.length; i++){console.log(a[i]);
}
//forEach遍历
a.forEach(function (item,index,array) {console.log(a[index]);console.log(item);
})//通过map集合----特殊
a.map(function (item,index,array) {console.log(item+index);
})
//遍历数组中的每个元素,将回调函数中的返回值存到一个新的数组中,并返回
var newArr = arr.map(function(item,index,array){ return item+index;
}); //newArr中的数据:["A1","B2","C3","D4"]
//for in
for(var i in a){console.log(a[i]);
}
//遍历对象,用for,in表示遍历的是属性名
var obj = new Object();
obj.username = "df";
obj.age = 12;
for (var name in obj){console.log(name);console.log(obj[name]);
}

day10 javaScript day02

10.1 元素获取的三种方式以及window.onload

关于window.onload的理解

浏览器加载html标签的时候,会把标签中的内容放到document对象中,我们可以直接通过document去内存操作dom树

1.浏览器会为当前的html创建一个空的document对象

2.然后从上往下加载html标签,存入document对象

3.使用window.onload函数,可以延迟代码的执行,等html执行完,才获取到标签元素

举个例子:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script>//此时并不能加载获取到div1的内容,因为他先执行代码,才获取,程序是从上往下执行var divxEl = document.getElementById("div1");//可以访问得到,当我们点击的div1的时候触发,也就是代码执行到了div1,点击可以执行上述的js代码function k(){}</script>
</head>
<body><div id="div1" onclick = "k()"></div><div id="div2" class="divClz"></div><div id="div3" class="divClz"></div>
</body>
</html>

Title

元素获取的最基本的三种方式

  • getElementById
  • getElementsByTagName
  • getElementsByClassName

当然还有其他的例如getElementsByName

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script>//元素获取的三种方式//根据id获取window.onload = function () {var divxEl = document.getElementById("div1");console.log(divxEl);//根据标签获取var divList = document.getElementsByTagName("div");console.log(divList[0]);//根据class来获取元素对象,class可以重复var divByClass = document.getElementsByClassName("divClz");console.log(divByClass)}</script>
</head>
<body><div id="div1"></div><div id="div2" class="divClz"></div><div id="div3" class="divClz"></div>
</body>
</html>

10.2 元素节点的属性操作

10.2.1 属性

标准属性访问的三种方式:

  • 元素对象.属性

  • 元素对象[属性]

  • 元素对象.getAttribute(“属性”)

    属性设值的三种方式

  • 元素对象.属性 = 值

  • 元素对象[属性] = 值

  • 元素对象.setAttribute(“属性”,值)

非标准属性,自定义属性的访问和设值,只能通过get和setAttritude方式

10.2.2 一些小细节

1.class属性特殊访问:
class(关键字)属性的获取,只能通过Attritude或者用class的别名–className

2.给属性名属性值相同的属性赋值或者获取,都是true,false

3.操作**样式中的属性,**使用【】,或者别名

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script>window.onload = function () {//************关于标准属性*******************************//通过1.元素对象.属性,  2.元素对象【属性】  3.元素对象的get和setAttritudevar divEl = document.getElementById("div1");console.log(divEl.id);console.log(divEl["id"]);console.log(divEl.getAttribute("id"));//自定义的属性,只能用get和setAttritudeconsole.log(divEl.getAttribute("ooo"))divEl.setAttribute("ooo","ccc");//属性名称中包含特殊字符(. -)只能用第2,3种访问方式// console.log(divEl["xxx.ooo"])//**************class属性特殊访问****************************//class(关键字)属性值的获取,只能通过Attritude//或者用class的别名--classNameconsole.log(divEl.getAttribute("class"));console.log(divEl["className"]);//**************元素的设值***********************************divEl.id = "fff";console.log(divEl.id)divEl["className"] = "hhhhh";console.log(divEl["className"]);divEl.setAttribute("id","uuu");console.log(divEl.id)//***************自定义属性***********************************// divEl.setAttribute("xxx","ooo");// console.log(divEl[xxx]);//给属性名和属性值相同的属性赋值或者获取,都是true,falsevar inputEl = document.getElementById("oo");console.log(inputEl.checked);//操作样式中的属性,使用【】,或者别名console.log(divEl.style["background-color"]);console.log(divEl.style.backgroundColor);}</script>
</head>
<body>
<div id="div1" ooo = "xxx" style="background-color: aqua"  class="divClz" ></div>
<input id="oo" type="checkbox" checked />
<div id="div2"  class="divClz"></div>
<div id="div3" class="divClz"></div>
</body>
</html>

10.3 元素节点的增删改查

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>DOM</title><script type="text/javascript">function fun1(){//获取span1元素var spanEl = document.getElementById("span1");//获取div1元素var div1 = document.getElementById("div1");div1.appendChild(spanEl);}function fun2() {//获取到所有的span标签var spanAll = document.getElementsByTagName("span");//获取div2var div2 = document.getElementById("div2");//z这里注意了,当我们div2.appendChild(spanAll[i])时//我们第一次拿到【0】,然后这个元素下沉作为div2的child,而原来的【1】变成了【0】//此时i = 1;则第三个span下沉变为了div2的child//如此以来,实现不了功能/*解决一:for (var i = spanAll.length; i > 0; i--) {div2.appendChild(spanAll[i]);}解决二,每次都会下沉,所以一直拿第【0】for (var i = 0; i < spanAll.length; i++) {div2.appendChild(spanAll[0]);}*/for (var i = 0; i < spanAll.length; i++) {div2.appendChild(spanAll[0]);}}function fun3() {//方法一:(推荐!)//获取div3元素var div3EL = document.getElementById("div3");div3EL.innerHTML = div3EL.innerHTML + "<span> 新的标签 </span>"//方法二:(不推荐!)//创建一个span元素// var div3EL = document.getElementById("div3");// var spanEl = document.createElement("span");// spanEl.innerHTML = "新的标签";// div3EL.appendChild(spanEl);}function fun4() {//获取到关羽var select = document.getElementById("character");var guanYu = document.getElementById("item2");var zhuG = document.createElement("option");zhuG.innerHTML = "诸葛亮";console.log(zhuG);select.insertBefore(zhuG,guanYu);}function fun5() {var guanYu = document.getElementById("item2");guanYu.innerText = "魏延";}function fun6() {var select = document.getElementById("character");var zhangF = document.getElementById("item3");select.remove(zhangF);}</script>
</head>
<body>
<span id="span1">span1</span>
<span>span2</span>
<span>span3</span>
<span>span4</span>
<span>span5</span>
<div id="div1" style="background-color: green">1</div>
<div id="div2" style="background-color: yellow">2</div>
<div id="div3" style="background-color: red;">3</div>
<button onclick="fun1()">把span1添加到div1</button>
<button onclick="fun2()">把span添加到div2</button>
<button onclick="fun3()">新建span元素添加到div3</button><br/>
<select id="character" size="7"><option id="item1">刘备</option><option id="item2">关羽</option><option id="item3">张飞</option><option id="item4">赵云</option>
</select>
<button onclick="fun4()">在关羽之前插入诸葛亮</button>
<button onclick="fun5()">把关羽换成魏延</button>
<button onclick="fun6()">删除张飞</button><br/>
</body>
</html>

10.4 事件

事件的基本操作

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<script>window.onload = function () {var btn = document.getElementById("btn");btn.onclick = function () {alert("点我干嘛2.0");}}function fun() {alert("你又点我3.0")}
</script>
<body>
<!--绑定事件的三种方式-->
<input type="button" value="点我啊" onclick="alert('点我干嘛')">
<input id="btn" type="button" value="点我啊2.0" >
<input id="btn2" type="button" value="点我啊3.0" onclick="fun()" >
</body>
</html>

案列:图片的切换

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<script>function fun(imgUrl) {var img = document.getElementById("imageA");img.src = imgUrl;}
</script>
<body>
<img src="data:image/a.jpg" id="imageA" onmouseover="fun('image/b.jpg')" onmouseout="fun('image/a.jpg')">
</body>

10.5 练习

10.5.1复选框练习

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>checkbox</title>
<script type="text/javascript"  src="checkbox.js" ></script><script type="text/javascript">//全选和全不选function checkAll(flag) {var hobby = document.getElementsByName("hobby");for (var i = 0; i < hobby.length ; i++) {hobby[i].checked = flag;}}//反选function checkUnAll() {var hobby = document.getElementsByName("hobby");for (var i = 0; i < hobby.length ; i++) {hobby[i].checked = !hobby[i].checked;}}//全选/全不选function checkChange(ele) {var hobby = document.getElementsByName("hobby");for (var i = 0; i < hobby.length ; i++) {hobby[i].checked = ele.checked;}}</script>
</head>
<body>请选择你的爱好:<br/><input type="checkbox" onchange="checkChange(this)" id="checkAll"/>全选/全不选<br/><div><input type="checkbox" name="hobby"/>JAVA&nbsp;<input type="checkbox" name="hobby"/>打篮球&nbsp;<input type="checkbox" name="hobby"/>上网&nbsp;<input type="checkbox" name="hobby"/>撩妹&nbsp;</div><div><input type="button" id="btn_checkAll" onclick="checkAll(true)" value="全选"/><input type="button" onclick="checkAll(false)" value="全不选"/><input type="button" onclick="checkUnAll()" value="反选"/></div>
</body>
</html>

10.5.2 权限管理练习

<script type="text/javascript"  src="select.js" ></script><script type="text/javascript">function moveSelected(source,dest) {//获取源和目标var source = document.getElementById(source);var dest = document.getElementById(dest);//获取要添加的目标var options = source.getElementsByTagName("option");//添加for (var i = 0; i < options.length; i++) {if (options[i].selected){dest.appendChild(options[i]);}}}function moveAll(source,dest) {//获取源和目标var source = document.getElementById(source);var dest = document.getElementById(dest);//获取要添加的目标var options = source.getElementsByTagName("option");//添加//由于每次添加完,都会导致索引上浮(索引--),导致部分不能添加//解决方法一:////   for (var i = options.length - 1 ; i >= 0 ; i--) {//  dest.appendChild(options[i])// }//解决方法二for (var i = 0; i < options.length ; i++) {dest.appendChild(options[i])i--; //每次添加完,索引会-1,所以i-1让索引跟着回到上个位置,不会错过元素}}</script>
</head>
<body><table border="1"><tr><td><select id="select1" style="width:100px;height:200px" size="10" multiple="multiple"><option value="选项1">选项1</option><option value="选项2">选项2</option><option value="选项3">选项3</option><option value="选项4">选项4</option><option value="选项5">选项5</option><option value="选项6">选项6</option><option value="选项7">选项7</option><option value="选项8">选项8</option><option value="选项9">选项9</option></select></td><td align="center"><input type="button" onclick="moveSelected('select1','select2')" value="-->"/><br/><input type="button" onclick="moveAll('select1','select2')" value="==>"/><br/><input type="button" onclick="moveSelected('select2','select1')" value="<--"/><br/><input type="button" onclick="moveAll('select2','select1')" value="<=="/></td><td><select id="select2" style="width:100px;height:200px" size="10" multiple="multiple"></select></td></tr></table>
</body>
</html>

10.5.3 用户管理练习

<script>window.onload = function () {var btnSubmit = document.getElementById("btn_submit");btnSubmit.onclick = function () {//获取用户的信息var username = document.getElementById("username").value;var email = document.getElementById("email").value;var tel = document.getElementById("tel").value;//创建存贮用户信息的节点var userTr = document.createElement("tr");var usernameTrEl = document.createElement("td");var emailTrEl = document.createElement("td");var telTrEl = document.createElement("td");var deleteInfo = document.createElement("td");usernameTrEl.innerHTML= username;emailTrEl.innerHTML = email;telTrEl.innerHTML =  tel;deleteInfo.innerHTML = "<a href=\"delTr(this)\">删除</a>";userTr.appendChild(usernameTrEl);userTr.appendChild(emailTrEl);userTr.appendChild(telTrEl);userTr.appendChild(deleteInfo);//将节点添加到tbodyvar tbody = document.getElementById("userTbody");tbody.appendChild(userTr);}var deletALl = document.getElementById("btn_removeAll");deletALl.onclick = function () {var tbody = document.getElementById("userTbody");tbody.innerHTML = "";}}function delTr(ele) {//ele:表示当前的删除按钮//ele.parent.parent:tr//第一种方式://  var tbody = document.getElementById("userTbody");//  tbody.remove(ele.parentNode.parentNode);//第二种方式:父亲杀孩子var tr = ele.parentNode.parentNode;tr.parentNode.removeChild(tr);}</script>
</head>
<body><form name="userForm"><center>用户录入<br/>用户名:<input id="username" name="username" type="text" size=15/>E-mail:<input  id="email" name="email" type="text"  size=15/>电话:<input id="tel" name="tel" type="text" size=15/><input type="button" value="添加" id="btn_submit"/><input type="button" value="删除所有" id="btn_removeAll"/></center></form><hr/><table border="1" align="center" cellpadding=0 cellspacing=0 width=400> <thead><tr><th>用户名</th><th>E-mail</th><th>电话</th><th>操作</th></tr></thead><tbody id="userTbody"><tr id="tr1"><td>张无忌</td><td>wujizhang@163.com</td><td>18212345678</td><td><a href="#" onclick="delTr(this)">删除</a></td></tr></tbody></table>

10.6 总结

当我们遍历执行某些操作时候,要注意索引的改变,索引可能上浮-1

day11 通信协议和Tomcat服务器

11.1 网络通信协议的理解

11.1.1 CS/BS

CS是指客户端和服务器结构,客户与数据库服务器(与数据库交互)进行交互

​ 优缺点:所有的程序代码都在用户端软件中,安全性高,但是更新时,需要重新安装,移植性低,开发维护成本高

BS指的是浏览器与服务器结构,浏览器和服务端进行交互,服务端和服务器跟数据库进行交互

可移植性高,开发维护好,(用户体验不好,界面不丰富,安全性低) -> 这些都是以前的缺点,放到现在都不是问题了

新型BS结构:需求和技术发展的产物

​ 瘦客户端 : 传统B/S , 服务器:负责业务逻辑,客户端:负责界面渲染

​ 富客户端 : 超级 BS=CS+BS , 界面华丽. 服务端:负责业务逻辑; 客户端: 负责界面渲染 + 少量的业务逻辑

11.1.2 网络通信三要素以及通信协议

IP地址:用来给网络中的计算机设备做的唯一编号

域名:映射到某个ip的一组英文单词组合,为了更好的记忆

端口号:唯一标识设备中的应用程序

通信协议:指的是计算机遵守这些规则,计算机之间才能通信(汽车遵守交通规则)

11.1.3 UDP协议

指的是无连接通信协议,不建立逻辑连接,当一台计算机向另外一台计算机

发送数据时,发送端不会确认接收端是否存在,直接发数据,同样接收端在收到数据时,也不会向发送

端反馈是否收到数据。

11.1.4 TCP协议

指的是面向连接通信协议

即在传输数据前先在客户端和

服务器端建立逻辑连接,然后再传输数据。它提供了两台计算机之间可靠无差错的数据传输。

需要经过三次握手(确认双方都在)四次挥手(确认数据的传输以及关闭)

11.1.5 客户端,服务端的代码操作(Tomcat的底层)

客户端代码;

package net;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;//客户端
public class Client {//步骤//1.创建一个连接某个服务端的Socket对象//2.从Socket中拿到输出流往服务端发送数据//3.调用Socket对象shutdownOutput方法通知服务端数据发送完毕//4.从Scoket中拿到输入流获取服务端回馈的数据//5.调用Socket对象对象的shutdownInput方法通知服务端数据接收完毕//6.关闭socket对象public static void main(String[] args) throws IOException {//1.创建一个连接某个服务端的Socket对象    三次握手Socket socket = new Socket("127.0.0.1", 3360);//2.从Socket中拿到输出流往服务端发送数据OutputStream outputStream = socket.getOutputStream();outputStream.write("你好服务器".getBytes("UTF-8"));//3.调用Socket对象shutdownOutput方法通知服务端数据发送完毕socket.shutdownOutput();//4.从Scoket中拿到输入流获取服务端回馈的数据InputStream inputStream = socket.getInputStream();byte[] buffer = new byte[1024];int len = -1;while ((len = inputStream.read(buffer) )!= -1){System.out.println(new String(buffer,0,len));}//5.调用Socket对象对象的shutdownInput方法通知服务端数据接收完毕socket.shutdownInput();//6.关闭socket对象socket.close();}}

服务端代码:

package net;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;//服务器端
public class Server {//    步骤:
//    1:创建一个服务端对象(ServerSocket)
//    2:侦听客户端的连接,获取和客户端通信的Socket对象
//    3:从Socket获取输入流,接收服务器发送的数据
//    4:调用Socket对象对象shutdownInput方法通知客户端数据接收完毕 叩丁狼教育
//    5:从Socket中拿到输出流往客户端发送数据
//    6:调用Socket对象对象shutdownOutput方法通知客户端数据发送完毕
//    7:关闭Socket对象public static void main(String[] args) throws IOException {//    1:创建一个服务端对象(ServerSocket)ServerSocket serverSocket = new ServerSocket(8888);//    2:侦听客户端的连接,获取和客户端通信的Socket对象Socket socket = serverSocket.accept();//    3:从Socket获取输入流,接收服务器发送的数据InputStream inputStream = socket.getInputStream();byte[] buffer = new byte[1024];int len = -1;while ((len = inputStream.read(buffer))!= -1 ){System.out.println(new String(buffer,0,len));}//    4:调用Socket对象对象shutdownInput方法通知客户端数据接收完毕 叩丁狼教育socket.shutdownInput();//    6:调用Socket对象对象shutdownOutput方法通知客户端数据发送完毕OutputStream outputStream = socket.getOutputStream();outputStream.write("你好啊客户端".getBytes("UTF-8"));//    6:调用Socket对象对象shutdownOutput方法通知客户端数据发送完毕socket.shutdownOutput();//    7:关闭Socket对象socket.close();}
}

访问web网页服务器

package net;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class Server2 {public static void main(String[] args) throws IOException {//    1:创建一个服务端对象(ServerSocket)ServerSocket serverSocket = new ServerSocket(8888);//    2:侦听客户端的连接,获取和客户端通信的Socket对象Socket socket = serverSocket.accept();//    3:从Socket获取输入流,接收服务器发送的数据
//        InputStream inputStream = socket.getInputStream();
//        byte[] buffer = new byte[1024];
//        int len = -1;
//        while ((len = inputStream.read(buffer))!= -1 ){//            System.out.println(new String(buffer,0,len));
//        }BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));String info = null;while ((info  = bufferedReader.readLine())!= null){//结束标志if ("".equals(info)){break;}System.out.println(info);}//    4:调用Socket对象对象shutdownInput方法通知客户端数据接收完毕 叩丁狼教育socket.shutdownInput();//    6:调用Socket对象对象shutdownOutput方法通知客户端数据发送完毕PrintWriter printWriter = new PrintWriter(socket.getOutputStream());printWriter.println("HTTP/1.1 200 OK");printWriter.println("Content-Type: text/html;charset=utf-8");printWriter.println();printWriter.println("<html><head></head><body>dfdfdfdf</body></html>");printWriter.flush();//    6:调用Socket对象对象shutdownOutput方法通知客户端数据发送完毕socket.shutdownOutput();//    7:关闭Socket对象socket.close();}
}

由于会有多个服务器访问,所以服务器不能关

package net;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class net implements Runnable{private  Socket socket;public net( Socket socket) {this.socket = socket;}@Overridepublic void run() {try {//2.从Socket中拿到输出流往服务端发送数据//    1:创建一个服务端对象(ServerSocket)//    2:侦听客户端的连接,获取和客户端通信的Socket对象//    3:从Socket获取输入流,接收服务器发送的数据InputStream inputStream = socket.getInputStream();byte[] buffer = new byte[1024];int len = -1;while ((len = inputStream.read(buffer))!= -1 ){System.out.println(new String(buffer,0,len));}//    4:调用Socket对象对象shutdownInput方法通知客户端数据接收完毕 叩丁狼教育socket.shutdownInput();//    6:调用Socket对象对象shutdownOutput方法通知客户端数据发送完毕OutputStream outputStream = socket.getOutputStream();outputStream.write("你好啊客户端".getBytes("UTF-8"));//    6:调用Socket对象对象shutdownOutput方法通知客户端数据发送完毕socket.shutdownOutput();//    7:关闭Socket对象} catch (IOException e) {e.printStackTrace();}finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}
}
package net;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;//客户端
public class Server3 {//步骤//1.创建一个连接某个服务端的Socket对象//2.从Socket中拿到输出流往服务端发送数据//3.调用Socket对象shutdownOutput方法通知服务端数据发送完毕//4.从Scoket中拿到输入流获取服务端回馈的数据//5.调用Socket对象对象的shutdownInput方法通知服务端数据接收完毕//6.关闭socket对象public static void main(String[] args) throws IOException {//1.创建一个连接某个服务端的Socket对象    三次握手ServerSocket serverSocket = new ServerSocket(3360);while (true){Socket socket = serverSocket.accept();new Thread(new net(socket)).start();}}}

11.2 Tomcat服务器

11.2.1 什么是服务器

软件服务器指的是一个软件,提供了应用运行的环境

硬件服务器指的是一台电脑,该电脑安装了软件服务器

软件服务器分类

http 服务器:用于运行静态的网页(html)

Web服务器实现了 JavaEE 部分规范(比如Servlet/JSP)、没有实现 JavaEE 中的 EJB 规范。

常用的Web服务器有:Tomcat(Java代码写的,开源的服务器, Jetty等应用服务器:实现了 JavaEE 全部的规范/支持 EJB 的:TomEE,GlassFish,JBoss,Weblogic,WebSphere

11.2.2 关于Tomcat的理解

以上程序已经实现了可以和浏览器进行通信,但是存在许多的问题:

1:该程序在并发的情况下,效率低

2:启动服务绑定的端口是写死了,万一该端口被其他程序占用,则服务器无法启动

3:以上编程模型中的 IO 是属于阻塞IO,传输数据的效率极低等等

所以我们使用别人写的Tomcat服务器

11.2.3 Tomcat的目录结构

  • bin 启动和关闭Tomcat可执行文件

  • conf 配置文件夹,有些重要配置文件在这下面。如:server.xml, web.xml等 不建议乱修改,可能导致Tomcat不启动

  • lib Tomcat使用时依赖的一些第三方jar包

  • logs 日志记录文件,记录服务器运行过程中一些事件。包括运行过程中出现异常

  • temp 临时文件夹,保存服务器运行过程中生成的一些垃圾文件

  • webapps 我们做好的项目发布的目录,可以通过浏览器来直接访问

  • work Tomcat 工作目录,存储 Tomcat 运行过程中产生的文件,比如 jsp 翻译后的class文 件

    默认访问的是webapp(项目)文件夹里的example项目,默认访问的是index文件

11.2.4 web项目的基本架构

用idea创建项目的结构如下:

web标准目录结构:

day12 Http协议&Servlet

12.1 Http协议

12.1.1 什么是Http协议以及特点

概念:HTTP 是 HyperText Transfer Protocol (超文本传输协议)的简写,它是 TCP/IP 协议之上的一个应用层协议。简单理解就是 HTT协议底层是对 TCP/IP 协议的封装。

作用:用于规定浏览器和服务器之间数据传输的格式

须知:HTTP 协议端口号是 80,Tomcat 默认是 8080

特点:

  • 无状态:协议对客户端没有状态存储,对事务处理没有“记忆”能力. HTTP/1.0,
  • 每次请求需要通过 TCP 三次握手建立连接,请求结束通过四次挥手断开连接,一次连接只能处理一个请求数据的传输。HTTP/1.1 一次连接成功之后可以处理多个请求的数据传输。
  • 基于请求和响应:基本的特性,由客户端发起请求,服务端响应
  • 简单快速、灵活

12.1.2 get与post区别

12.2 Servlet

12.2.1 什么是Servlet?

Servlet是一个用java编写的小应用程序,运行在web服务器,用于交互式地浏览和生成数据。

作用:也是服务器的一种资源,可以被浏览器通过映射的路径去访问到某个具体的Servletl类进行操作(可以接收数据和响应浏览器)。

12.2.2 关于Servletd的开发

有两种方式,第一种使用xml方式,第二种使用注解

12.2.3 Servlet的生命周期与Servlet的请求执行顺序

Servlet的生命周期:

1.Tomcat创建Servlet的实例

2.该容器调用init()方法,调用一次

2.当有请求Servlet,则容器调用service()方法,调用多次

3.销毁实例(关闭程序或者重启服务器)之前调用destory()方法,调用一次

Servlet的请求执行顺序:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A2vaqqPk-1619670625201)(\img\请求流程2.png)]

总结:首先通过浏览器的路径解析,然后映射到相应的Servlet类,然后通过tomcat反射实例化Servlet对象,创建request和response

对象,然后将这两个对象传入service中。

12.2.4 Servlet继承体系和相关类的概述

GenericServlet类实现了servlet和ServletConfig两个接口,它的子类是HttpServlet,如果我们写的Servlet使用的是Http协议,建议继承于HttpServlet。现在所有浏览器都是使用http协议,所以我们以后都要继承于HttpServlet类就可以了

注意:在使用继承使用HttpServlet时,调用service方法不可以调用父类的,因为父类有两个service方法,jvm找不到是哪个?

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {super.service(req, resp);   //HttpServlet类里有两个serivce方法,不能确认调用哪个方法}

12.2.5 ServletConfig对象和LoadOnStartup属性

ServletConfig对象:主要用来封装Servlet初始化的时候的一些配置信息。可以配置初始化参数来改变硬编码问题

LoadOnStartup属性:先实例化的优先顺序,在tomcat启动过程中就开始实例化。

day13 Servlet02与jsp

13.1映射

13.1.1 配置多个路径:

  • 一个 中写多个
  • 一个对应多个

13.1.2 通配符的映射

/* :表示匹配所有的访问地址

13.1.3 映射的注意事项

servlet-name不能用defaultjsp

  • 因为tomcat内部已经有这两个servlet,其中DefaultServlet用来处理静态资源,而JSPServlet用来将jsp文件翻译成java文件
  • 如果我们的servlet-name和内部的重名了,则会覆盖内部的(我们可以从conf/web.xml来查看公用的信息)

13.2.请求对象

理解:HttpServletRequest req请求对象,主要用于浏览器向服务器请求交互

作用:通过请求对象,我们可以通过req的一些api拿到浏览器传来的一些参数和网页信息,比如请求头信息,网页提交的表单信息。

注意:如果req接受网页传来的提交表单信息有中文的话,则需要改变req的编码格式,req.setCharacterEncoding(“utf-8”),告诉浏览器我们的编码格式,让我们获取到的编码格式为中文,这样就不会乱码

13.3 响应对象

理解:HttpServletResponse响应对象,用于服务器向浏览器响应交互

作用:通过响应对象,我们可以通过请求对象拿到的浏览器传来的信息,做一些相关的处理,并且返回一些数据给浏览器

注意:如果我们返回的数据里面包括了中文信息,则会出现编码问题。这个时候我们按照常规resp.setCharacterEncoding(“utf-8”)是不行的,因为我们并没有告诉浏览器我们返回的是什么类型的数据(图片,音频,文字…)使用utf-8来编码,浏览器默认使用另外一种码表的方式进行解码,因此我们要设置resp.setContextType(“text/html;charset = utf-8”); 告诉我们要返回的数据类型和用的编码方式

13.4 jsp

13.4.1 什么是jsp?

jsp是运行在服务端页面的java页面,解决了响应对象返回给数据(html数据)给浏览器的拼接繁琐,相当于可以在html中嵌套java代码。

13.4.2 jsp的执行流程?

细节:当浏览器发送请求访问Jsp资源时,需要经过Tomcat服务器,而Tomcat服务器中的JSPServlet,它会对服务器中对应的JSP文件,进行编码转换成对应Servlet的java类,在由虚拟机JVM将这个java文件转换为字节码文件,。运行该字节码文件,动态生成web的内容,最终返回给浏览器。

day14 转发_重定向 && 数据共享 _EL _JSTL

14.1 为什么需要跳转和数据共享?

因为servlet类擅长做数据处理,而Jsp(实际上也可以看做Servlet类)擅长做数据的显示,为了将后台的数据传到前台页面,我们需要将Servlet类和Jsp进行交互,也就是Servlt跳转到jsp,并且共享数据给Jsp,让Jsp显示数据。

14.1.1 那么如何实现跳转?

跳转有两种方式:转发和重定向

14.1.2 转发:

语法:

req.getRequestDispatcher("要转发的资源路径").forward(req,resp);

可以将页面跳转到另外一个资源页面,并且返回req请求对象和resp响应对象,这两个对象有了原本Servlet类设置的数据

当我们使用转发时,一般都会将数据带到另外一个Servlet进行处理,所以第一个Servlet中转发后写的代码没有意义

1.两个Servlet之间转发,最后显示的结果是最后一个Servlet的

2.转发时,请求转发的地址不会发生变化,只是发送了一次请求

3.请求转发的地址中带的参数,可以共享给后面的Servlet

4.请求转发可以访问WEB-INF下面的资源(例如:/WEB-INF/index.jsp…)

5.请求转发只能在服务器内部转发,不能跳转到外部项目资源(例如:http://www.baidu.com)

14.1.3 重定向:

语法:

resp.sendRedirect("要重定向的资源路径");

将页面进行重定向,期间请求两次,先请求原本的Servlet,后请求要重定向的资源路径,地址栏根据请求的资源路径发生改变

1.重定向时地址带的参数不能共享给后面的Servlet

2.重定向不能访问WEB-INF下面的资源(例如:/WEB-INF/index.jsp…)

3.重定向可以访问服务器外的资源,项目资源(例如:http://www.baidu.com)

4.可以跨域访问资源

14.1.4 注意:转发和重定向如何使用呢?

当我们需要访问WEB-INF下面的资源(例如:/WEB-INF/index.jsp…)时,使用转发

当我们需要跳转时浏览器带参数给下一个Servlet时,我们使用转发

当我们需要访问外部资源,项目资源时,我们使用重定向

其他时刻,使用转发和重定向都可!

14.2 在数据共享的时候,我们会存在一个作用域的问题,作用域是?

作用域就相当于一个盒子,用于Servlet之间数据共享的服务器内存区域,结构是一个Map<String,Object>

14.2.1 有哪些作用域?

请求域:HttpServletRequest,只在第一次请求中起作用

会话域:HttpSession,在一个会话中起作用,浏览器第一次访问到浏览器关闭称为一个会话,相当于打电话

上下文本域:ServletContext,在同一个应用中,服务器启动直到下一次服务器重启

14.2.2 作用域中的常用方法呢?

getAttritude(?) : 获取值 ,setAttritude(?,?) :设置键值对

Attribute和properties区别
javabean里面的属性有了getter与setter方法就成了properties

当我们从共享数据中获取数据时,通过jsp的java代码操作时,我们发现使用jsp里的java代码操作非常繁琐,于是就有了EL标签;

EL标签用来替换掉Jsp中的java代码,使jsp看起来更加的简洁

14.3 El标签如何使用,已经常见方法?

语法:

${?}  ? :表示作用域中的建

常用的方法:${empty ?} 判空

特别注意:

${}里的对象或者变量是空的话。最后得的是空字符串!切记!

底层调用了get方法,如果是对象.属性,则对象对应的实体类中必须有属性对应的get方法

EL中多了一个页面域:pageScope,只在当前页面中有效

14.4 JSTL引出:

当我们使用EL标签时,我们发现当我们需要取一个列表中的数据或者做一些简单的逻辑时,会很麻烦,于是就有了Jstl.

jstl提高了程序的可读性,降低了程序的维护费用。

  • 当我们使用jstl时,我们要导入使用时相对应的库,核心标签库和格式化标签库
  • 在WEB-INF下面的lib添加两个标签库的包,用ecplise时,要自己手动导入约束,在idea中自动导入

14.4.1 jstl常用的标签?

<c:if test = "${Person.age > 18}">你已经成年了
</c:if>
<c:choose><c:when test="${Person.age > 18}">你成年了</c:when><c:when test="${Person.age < 18}">你还没有成年了</c:when><c:otherwise>没出生</c:otherwise>
</c:choose>
<c:forEach var="list" items="${Person.list}" varStatus="status">${list}--------${status.count}
</c:forEach>
<fmt:formatDate value="${date}" pattern="yyyy-MM-dd"></fmt:formatDate>

day15 WEB_CRUD

页面功能

实现web的增删改查操作

请求构建

由于Servlert类里只有一个service,只能处理一个请求资源路径,当我们需要发送很多不同的请求,需要写多个Servlet,可是当我们写的是一个完整的项目时,整个操作会显得很繁琐,所以我们应该将某个业务的需求全部写在一个类中,例如关于学生的操作业务写在一个Servlet中,关于商品的操作我们也写在一个Servlet类中。所以我们要怎么解决一个Servlet里可以根据请求资源路径访问不同操作呢?我们可以在请求资源路径后面加一个参数来标记要访问的业务方法,因此我们称service为请求分发器。Servlet根据不同的请求来进行分发方法进行不同的操作。请求问题解决!切记多思考!

请求分发如下:

当我们显示页面数据时:cmd = ”“ 路径:"/student"

当我们进行删除操作时: cmd = ”delete“ 路径:/student?cmd=delete&userID=${s.userID}

当我们进行新增和修改操作时:cmd= ”saveOrUpdate“ 路径:、student?cmd=saveOrUpdate

注意!细节中的细节

当我们以参数来分发请求的时候,不能使用转发,转发地址栏信息不变,导致参数不变,进行死循环

案列:

首先发送请求/studnet?cmd=saveOrupdate

请求到servlet执行参数分配请求调用saveOrupdate方法

saveOrupdate最后进行转发到/student,因为是转发是请求地址栏不变,获取到cmd依然是saveOrupdate

所以导致一直循环。

@WebServlet("/student")
public class StudentServlet extends HttpServlet {private IStudentDao studentDao = new StudentImpl();@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding("UTF-8");String cmd = req.getParameter("cmd");cmd = cmd != null ? cmd : "";switch (cmd){case "delete":delete(req,resp); break;case "input":input(req,resp); break;case "saveOrUpdate":try {saveOrUpdate(req,resp);} catch (ParseException e) {e.printStackTrace();}break;default: listAll(req,resp);}}

功能页面设计以及实现:

主页 student.jsp :

功能需求:

  • 显示数据
  • 进行数据的删除

功能实现:

  • 主页数据的显示:当我们访问/student时,分发参数cmd为空,调用listAll方法,后调用dao数据库拿到所有数据,通过作用域回显到页面
  • 点击删除时,将页面的id坐为参数带到请求中,通过dao数据库进行删除,删除后重定向回到主页

编辑页面 studentEdit.jsp :

功能需求:用于新增和修改用户数据

功能实现:

由于新增和修改都需要编辑用户的所有数据,不过新增页面原本没有数据显示,而修改有数据回显,所以新增和修改操作可以写在同一个页面,所以当我们点击添加和编辑按钮时,添加:’"/student?cmd=input" ,编辑:”/student?cmd=input&userID=${s.userID}“

  • 当我们点击编辑时,我们把userID作为参数,放入请求中后在数据库找到该id对应的数据,然后通过作用域设值实现数据的回显到对应的studentEdit.jsp
  • 当我们点击添加的时候,我们跳转到studentEdit.jsp

优化:

1.点击每行数据进行颜色的切换

 //<tr class="trClassName" >
window.onload = function () {var trList  = document.getElementsByClassName("trClassName");for (var i = 0; i < trList.length ; i++) {trList[i].onmouseover=function () {this.style.backgroundColor = "cornflowerblue";}trList[i].onmouseout = function () {this.style.backgroundColor = "";}}}

2.将关联表对应的id转为对应的字段

显示页面和编辑页面的下拉框的修改

<!--显示页面的修改:-->
<td><c:choose><c:when test="${s.collegeID == 1}">计算机学院</c:when><c:when test="${s.collegeID == 2}">文学院</c:when><c:when test="${s.collegeID == 3}">美院</c:when></c:choose>
</td><!--编辑页面的下拉框修改:-->院系id:<select name="collegeID"><option style="text-align: center"  value="1" ${s.collegeID == 1 ? 'selected' : ''}>                                                                                          计算机学院</option><option value="2" ${s.collegeID == 2 ? 'selected' : ''}>文学院</option><option value="3" ${s.collegeID == 3 ? 'selected' : ''}>美院</option></select>

注意:

当我们通过作用域获取select的name时,它的值 = option被selected的那个option的value

day16 Cookie_Session会话跟踪技术

16.1 Cookie

16.1.1 引出Cookie:

因为浏览器用的是http协议,Http是一种无状态协议,也就是说协议对事物的处理没有记忆能力。就是说客户端发送请求时,由于Http时无状态的,它不清楚是谁上一次请求了自己,这样也导致了请求间的不能相互通信,不能共享数据哥跟踪用户信息并作出用户信息数据的处理。

因此我们可以通过给请求地址加入参数来解决http无状态的弊端,可是当参数暴露在地址栏中,导致信息数据的不安全。

哪有什么办法即不会暴露信息,又可以让请求间可以进行交互呢?cookie!

16.1.2 什么是cookie?

cookie是一种客户端技术,当我们客户端发送请求给服务器时,服务器会将客户的数据以cookie’的形式进行保存,并且将这个cookie(存储了客户数据)返回给浏览器,当客户下次在使用浏览器来访问web资源的时候,会携带浏览器的数据去到服务器,这样可以处理到用户数据了。

16.1.3 cookie的基本使用:

@WebServlet("/login")
public class cookieServlet  extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String name = req.getParameter("name");String password = req.getParameter("password");if ("admin".equals(name) && "1".equals(password)){//登录成功//cookie的创建Cookie cookie = new Cookie("name",name);//cookie设值---方式一cookie.setValue("哈哈");//cookie设值,重新创建一个同名cookie----方式二Cookie cookie2 = new Cookie("name","哈哈哈75454");//cookie设置存活时间// cookie.setMaxAge(0);//添加cookie到响应流中,也就是响应到浏览器中resp.addCookie(cookie);resp.addCookie(cookie2);
//            resp.sendRedirect("list.jsp");resp.sendRedirect("/a");}else {//登录失败resp.sendRedirect("login.jsp");}}
}
@WebServlet("/a")
public class AServlet  extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//通过浏览器的请求头拿到浏览器存储的cookie//遍历用户看看是否在登录时,填了有关于自己信息的cookie,有的话自动登录Cookie[] cookies = req.getCookies();String username ="";if (cookies != null){for(Cookie cookie :cookies){System.out.println(cookie.getName());//cookie.getName() == ("name")//如果是这样显示为falseif ((cookie.getName().equals ("name"))){username = cookie.getValue();break;}}}req.setAttribute("name",username);req.getRequestDispatcher("list.jsp").forward(req,resp);}
}

注意:

当我们使用重新创建一个同名cookie来进行赋值时,我们需要在最后提交到响应流中。也就是说出现相同的cookie键时,后提交到响应流的会覆盖前一次响应流中。

使用cookie.getName() == (“name”)不灵,要使用(cookie.getName().equals (“name”))

16.2 Session

16.2.1 什么是session?

session是一种用来记录客户端状态的机制,存在于客户端。当我们去超市买东西时,有会员卡可以加积分,一般我们报上我们的卡号就行。cookie就相当于会员卡,session相当于卡号,当输入卡号时,系统会去找到对应卡号的用户信息,进行操作。session就相当于是一把开箱子的钥匙,拿到钥匙,就可以找到个人的信息档案,就可以进行请求间的交互和操作。

16.2.2 session的操作:

req.getSession().setAttritude("?","?"),req.getSession().getAttritude("?")

day17 文件的上传和下载 三层结构 (实现登录后的头像和用户名显示,以及上传头像)

17.1 文件的上传

17.1.1 上传文件的思维考虑与细节优化:5个流程

流程1:

当我们选择上传文件,后点击提交按钮时,我们想要获取页面的参数,由于enctype=“multipart/form-data”,告诉浏览器我们提交的是二进制文件,也就是说图片音频的等等,而不是普通的文本,所以我们不能通过req.getParameter来获取页面元素的值。此时我们需要给转发的Servlet类上面贴个注解:

//作用用户获取二进制文件操作,以及设置文件的大小
@MultipartConfig(maxFileSize = 80000,maxRequestSize = 140000)

这样我们就可以通过req.getParameter来获取input类型为text的值了,但是要想获取type = file的话,我们需要通过:

//文件控件数据获取
Part headImg = req.getPart("headImg");

获取到文件后,将上传的文件输出到某个路径上:

//文件上传细节1:
// 避免了固定的文件名称,每次上传都会覆盖旧的
// **可是当遇到相同的文件名时,也会被覆盖
part.write("D:/a.png");

流程2:

根据流程1,我们发现这样每次用户上传的图片都会被覆盖掉,因为上传的文件名给写死了

所以我们可以通过获取文件的真实名称来解决:

String fileName = headImg.getSubmittedFileName();part.write("D:/" + fileName);

可是由于这个会有不同用户使用相同的文件名,这也是不可避免的

流程3:

因此我们想到要使文件明变得唯一才行!就可以使用UUID来生产唯一的字符串

然后在通过文件的后缀进行拼接

//因此我们可以 (通过UUID来随机生成字符串)  +  从文件获取文件的(后缀名)
//获取文件上传名称
String fileName = headImg.getSubmittedFileName();
//获取文件扩展名
String ext = fileName.substring(fileName.lastIndexOf("."));
//生成唯一字符串拼接扩展名
String newFileName = UUID.randomUUID().toString() + ext;
headImg.write(  "D:\\" + newFileName);

可是如果我们这个仅仅是作为头像的话,如果用户传入音频或者过大资源会导致服务器加载缓慢,从而导致用户体验不好,使用户粘度降低。

流程4:

如果是只需要上传图片的话,我们需要做一些判定:

 Part headImg = req.getPart("headImg");
String contentType = headImg.getContentType();
if (contentType!= null){if (!contentType.startsWith("image/")){req.getSession().setAttribute("errorMsg","请上传图片");}resp.sendRedirect("File.jsp");
}

可是这有一个弊端!如果用户知道我们是通过判断后缀名来判断是否是图片的话,他们可以将后缀名改为图片的后缀名

流程5:

因此为了确保传入的图片,我们还需进行一些处理,将上传资源大小限制住!!

为了统一用户传入的资源,我们可以让他们上传的资源大小在一定范围内

//作用用户获取二进制文件操作,以及设置文件的大小
@MultipartConfig(maxFileSize = 80000,maxRequestSize = 140000)

17.1.2 文件的上传路径解析,详解!!

1.可以上传到指定的本地硬盘路径

直接写入路径就可!

 headImg.write(  "D:\\" + newFileName);

2.上传到对应的服务器中

直接写入服务器所在的地址,或者购买了服务器–写协议+域名+接口

part.write("C:\\软件\\tomcat8\\apache-tomcat-8.5.51\\webapps\\ROOT\\"+fileName);

3.上传到项目路径下

​ 为了上传到项目路径了,我们需要进行三个操作

在WEB-INF下面创建一个classes(固定名称!不能乱写!切记!),然后改变项目的编译输出路径为classes文件夹

编辑tomcat,重新设置web作为整个项目的应用程序而不是web-upload_download!!!!(这点非常重要),

最后通过代码上传到指定文件夹upload

       //获取文件上传名称String fileName = headImg.getSubmittedFileName();//获取文件扩展名String ext = fileName.substring(fileName.lastIndexOf("."));//生成唯一字符串拼接扩展名   ---  文件名称String newFileName = UUID.randomUUID().toString() + ext;//保存在本地路径    -------  保存在当前项目下的upload文件夹String realPath = req.getServletContext().getRealPath("/upload");System.out.println(realPath);//关于文件保存问题://一般上传的多,我们需要买一个图片的服务器//如果公司小的话,直接保存到服务器上/项目上System.out.println(realPath + "/" + newFileName);headImg.write(realPath + "/" + newFileName);

17.2 文件的下载

17.2.1 文件的下载1.0

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>$Title$</title></head><body><h3>文件下载</h3><a href="狗.rar">dog.rar</a><br/><a href="cat.rar">猫.rar</a><br/></body>
</html>

资源放在文件web下面,用户点击可以直接下载资源

17.2.2 文件下载2.0

上面种方式,资源路径在web下,可以通过资源路径直接拿到资源

点击就直接定位到资源路径,可以直接下载,这样不符合我们程序员的风格,应该先登录收个费在给它下载

因此将资源放在WEB-INF下,这样

<a href="/WEB-INF/狗.rar">dog.rar</a><br/>

这样点击就不能直接访问拿到资源了!

将下载路径放在了WEB-INF下,这样就不能通过浏览器进行拿取资源了

我们应该请求给后台进行一些逻辑操作,才能让外人拿到资源!

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>下载</title>
</head>
<body>
<h3>文件下载</h3>
<a href="/download/dog.rar">dog.rar</a><br/>
<a href="/download/猫.rar">猫.rar</a><br/>
</body>
</html>
package cn.wolfcode.web._01_upload;
@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取用户要下载的文件名称String fileName = req.getParameter("fileName");//获取下载的资源路径String realPath = req.getServletContext().getRealPath("/WEB-INF/download/");//下载// 使用工具类 Files 的 copy 方法获取文件输入流,响应回浏览器//Paths.get(realPath, fileName) 获取到文件真正的下载路径//resp.getOutputStream() 响应到输入流,进行写入writeFiles.copy(Paths.get(realPath, fileName), resp.getOutputStream());} }

文件下载:首先通过参数来获取要下载的内容,然后获取到资源所在处也就是存放的路径,然后通过工具类,将该资源找到写入响应流中,给用户下载

17.3 三层架构----登录上传图片

三层架构无非就是Dao层(操作数据库),Service服务层(处理业务逻辑),MVC表现层(控制层)(用来于前端于后台交互),使用三层架构将责任进行分离,代码更加清晰和整洁,而且也大大降低了代码间的耦合性,使低耦合高内聚。

实现登录显示用户的姓名和头像

实现用户上传头像到服务器

功能页面如下:easy!

实现登录后显示用户的名称和头像:

首先我们给user表增加一个headImd的字段(字符串,用来存储图片的路径,绝对路径),然后赋值(给一个在服务器端的地址值),

当我们登录成功后,我们在session的会话域设置一个键值对,将用户信息返回

在登录成功后的页面,使用EL表达式来获取作用域的值。完成!!

@WebServlet("/login")
public class UserServlet extends HttpServlet {private UserService userService = new UserServiceImpl();@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String username = req.getParameter("username");String password = req.getParameter("password");User user = userService.checkLogin(username,password);if (user!= null){//登录成功req.getSession().setAttribute("user",user);resp.sendRedirect("/employee");}else {req.setAttribute("msg","账号或者密码错误");req.getRequestDispatcher("/login.jsp").forward(req,resp);}}
}

实现上传图片功能:

我们首先将上传的图片传到本地tomcat服务器上,然后从session会话域种获取登录成功后的user对象的值,给用户设置图片的路径。

@MultipartConfig
@WebServlet("/fileUpload")
public class UploadServlet extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding("utf-8");//首先获取文件控件的数据Part part = req.getPart("headImg");//获取文件名String submittedFileName = part.getSubmittedFileName();//通过文件名来获取后缀String ext = submittedFileName.substring(submittedFileName.lastIndexOf("."));//通过uuid来拼接后缀作为唯一的文件命名String fileName = UUID.randomUUID().toString() + ext;//获取图片存放的根路径String realPath = req.getServletContext().getRealPath("/headImg");System.out.println(realPath);System.out.println(realPath + "/" +fileName);System.out.println(fileName);//在Session会话中拿到登录成功后的user对象User user = (User) req.getSession().getAttribute("user");//文件保存到本地的tomcat服务器上part.write("C:\\软件\\tomcat8\\apache-tomcat-8.5.51\\webapps\\ROOT\\"+fileName);//给user对象的headimg设置图片路径user.setHeadImg("http://localhost/"+fileName);resp.sendRedirect("login.jsp");}
}

day18 分页

18.1 为什么要进行分页?

因为当我们要显示的数据很多(10多万条时),加载需要时间,而且占用的服务器内存也多,甚至可能导致内存溢出,而我们使用分页的处理方式,可以只显示少数的部分数据,当用户需要或者请求时再去加载剩下的部分数据,这样大大加快了数据从服务器加载到页面的速度,提高了性能,也减少了内存的使用。

18.2 那么我们如何实现分页呢?

18.2.1 功能需求页面:

18.2.2 功能需求分析:

功能1.默认给用户显示部分数据 (参数:需要默认的currentPage当前页,每页显示的数量pageSize)

通过这个我们可以想到Mysql的sql语句,

分页查询
SELECT <selectList>
FROM 表名
[WHERE]   LIMIT ?,?
-- 第一个? : 开始行的索引数 beginIndex
-- 第二个? : 每页显示的最大记录数 pageSize

第一个? :开始行的索引,(currentPage - 1)* pageSIze

第二个?:pageSIze

因此我们可以通过这两个参数获取需要分页显示的部分数据!!!

功能2.用户可以通过首页,上一页,下一页,末页来进行选择性地查看部分数据。

当我们点击首页时,将当前页设值为1

当我们点击上一页时,将当前页 -1

当我们点击下一页时,将当前页 + 1

当我们点击末页时, 将当前页数改为最后一页

通过分析,我们需要的参数为当前页currentPage,末页totalPage

通过步骤一,我们可以知道只需要两个参数我们就可以实现拿到需要的部分数据currentPage,pageSize

而功能二的首页,上一页,下一页,都只是改变了currentPage的值而已!!!因此我们可以通过点击提交带一个参数过去,然后提交到数据库执行sql语句。

功能2的末页功能,也是改变了currentPage的数据,将 currentPage = totalPage

通过上面的需求分析,我们知道我们需要从页面获取的参数有currentPage ,pageSIze

而我们需要返回页面的数据包括,部分客户数据,总条数,当前页(currentPage ),总页数(totalPage),由于参数的个数太多返回不了,因此我们可以通过封装到一个类中!!!

public class QueryObject {//通过外部类获取的当前信息//当前页private int currentPage = 1;//每页的数量private  int pageSize = 3;public class PageResult<T> {//通过sql语句获取//获取总条数private int totalCount;//拿当前页面到所有客户的信息private List<Employee> employeesPage;//通过计算获取//上一页private int prevPage;//下一页private int nextPage;//总页数private  int totalPage;//通过外部类获取的当前信息//当前页private int currentPage ;//每页的数量private  int pageSize ;

18.2.3 下面进行开发流程

编写顺序mapper文件 -> dao -> service -> servlet

mapper:

由于 不 支 持 表 达 式 , 而 {}不支持表达式,而 不支持表达式,而{}的底层又是传入对象的getter方法,所以我们需要重新一个方法getIndex处理,并将index作为属性

<select id="employeesPage" resultType="cn.ybzy.web.domain.Employee">select  * from employee Limit #{index} ,#{pageSize};
</select>
<select id="count" resultType= "int">select  COUNT(*) from employee ;
</select>
public class QueryObject {//通过外部类获取的当前信息//当前页private int currentPage = 1;//每页的数量private  int pageSize = 3;//由于sql语句里不支持表达式public  int getIndex(){return  (currentPage - 1) * pageSize;}

dao层:

调用接口方法,简单!懒得写!

service层:

public PageResult(int totalCount, List<Employee> employeesPage, int currentPage, int pageSize) {this.totalCount = totalCount;this.employeesPage = employeesPage;this.currentPage = currentPage;this.pageSize = pageSize;this.totalPage = totalCount % pageSize == 0 ? (totalCount / pageSize) : (totalCount / pageSize) + 1;this.prevPage  = currentPage - 1 == 0 ? currentPage : (currentPage -1);this.nextPage  = currentPage + 1 > totalPage ? totalPage : (currentPage +1);
}
@Override
public PageResult<Employee> employeesPage(QueryObject queryObject) {//获取总条数int count = employeeDao.count(queryObject);if (count == 0){return  new PageResult<Employee>(count,                                                     Collections.emptyList(),queryObject.getCurrentPage(),queryObject.getPageSize());}List<Employee> employees = employeeDao.employeesPage(queryObject);return new PageResult<Employee>(count, employees,queryObject.getCurrentPage(),queryObject.getPageSize());
}

关于页面的编写:

<tfoot><tr><td colspan="8"><a href="/employee?currentPage=1">首页</a>&emsp;&emsp;<a href="/employee?currentPage=${result.prevPage}">上一页</a>&emsp;&emsp;<a href="/employee?currentPage=${result.nextPage}">下一页</a>&emsp;&emsp;<a href="/employee?currentPage=${result.totalPage}">末页</a>&emsp;&emsp;当前第 ${result.currentPage} / ${result.totalPage} 页&emsp;&emsp;一共${result.totalCount}条数据&emsp;&emsp;跳转到 <input name="Page" > 页&emsp;&emsp;每页 <select name="Size"><option value="${result.currentPage == 3 ? "selected": ""}">3</option><option value="${result.currentPage == 5 ? "selected": ""}">5</option><option value="${result.currentPage == 10 ? "selected": ""}">10</option></select> 条数据&emsp;&emsp;</td></tr>
</tfoot>

day19 分页过滤_高级查询 (切记赋值代码时的set要改,晚上赋值两次没改浪费很多时间!!!)

19.1 为什么需要过滤查询?

为了满足客户更好的体验(快速找到并且查看自己想要的数据),我们需要将用户的请求条件参数进行筛选查询。

19.2 功能页面:

19.3 功能需求:

1.当我们输入货品名,价格的区间时进行不同查询。如果什么都不输入则查询全部。输入单个则查询单个,输入多个则查询多个。

2.当我们输入跳转到第几页时,页面跳转到对应的第几页

3.当我们选择每页显示多少时,则响应每页数据的个数

4.点击首页,上一页,下一页,尾页实现功能

19.4 功能需求开发流程分析:

思考开发流程很重要!

19.4.1 流程1:参数封装

由于需要用户填写三个参数信息,返回给后台进行对应的分页查询。注意这里需要五个参数,除了页面用户传入的三个,还有分页对应的currentPage和PageSize,由于参数过多我们将参数进行封装:

封装时的注意事项????

我们不能把三个参数信息写在QueryObject中,当我们项目需要很多实体类的信息进行分页时,会导致QueryObject类中的参数信息繁多,导致我们整个代码不清楚明了,因此我们QueryObject中只放分页需求的基本信息,而新建一个包装类ProductQuery来继承QueryObject,而ProductQuery类里只写这个类需要用到的特定信息,这样的结构利于后期的维护,并且代码更加清晰明了!!!

//基本分页信息参数的封装
public class QueryObject {//当前页private int currentPage = 1;//每页的数量private int pageSize = 5;//获取limit字句里面的索引//因为mybatis的EL表达式不支持表达式public int getIndex(){return (currentPage - 1) * pageSize;}
//对某个需要分页的实体类封装
public class ProductQuery  extends QueryObject{private String productName;private BigDecimal minSalePrice;private BigDecimal maxSalePrice;

19.4.2 流程2:sql语句_Servlet

可知有了用户传来的参数信息,我们需要传到dao层,然后执行对应的sql语句,那么sql’语句该如何写呢?

使用myabtis的动态参数

<sql id="productNameAndPrice_select"><where><if test="productName != null and productName != ''">and  productName like concat('%',#{productName},'%')</if><if test="minSalePrice != null and minSalePrice != ''">and  salePrice >= #{minSalePrice}</if><if test="maxSalePrice != null and maxSalePrice != ''">and  salePrice &lt;= #{maxSalePrice}</if></where>
</sql>
<select id="listPage" resultType="cn.ybzy.wolfcode.damian.Product">select *  from product <include refid="productNameAndPrice_select"></include>limit #{index},#{pageSize}
</select>

通过这些sql语句,我们就可以拿到用户需要的数据了!!!

此时我们应该拿到页面传来的参数信息,封装到ProductQuery类,然后在调用service层,调用dao层,执行sql

private void listAll(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {ProductQuery queryObject = new ProductQuery();String productName = req.getParameter("productName");if (StringUtil.hasLenght(productName)){queryObject.setProductName(productName);}String minSalePrice = req.getParameter("minSalePrice");System.out.println(minSalePrice);if (StringUtil.hasLenght(minSalePrice)){queryObject.setMinSalePrice(new BigDecimal(minSalePrice));}String maxSalePrice = req.getParameter("maxSalePrice");if (StringUtil.hasLenght(maxSalePrice)){queryObject.setMaxSalePrice(new BigDecimal(maxSalePrice));}String currentPage = req.getParameter("currentPage");if (StringUtil.hasLenght(currentPage)){queryObject.setCurrentPage(Integer.parseInt(currentPage));}String PageSize = req.getParameter("PageSize");if (StringUtil.hasLenght(PageSize)){queryObject.setPageSize(Integer.parseInt(PageSize));}//将用户填写的参数信息回显到页面上req.setAttribute("q",queryObject);PageResult<Product> result = productService.listPage(queryObject);req.setAttribute("result",result);req.getRequestDispatcher("/WEB-INF/product.jsp").forward(req,resp);
}

19.4.3 流程3:测试_解决BUG

完成功能测试一下!

BUG1:

当我们输入m的时候,m的数据只有13条,而页面依然显示了20条。这是为什么呢?

因此我们可以想到count总条数出现问题,count也应该带参数进行查询,listPage和count同步带参查询!!!

<select id="count" resultType="int">select Count(*) from product<include refid="productNameAndPrice_select"></include>
</select>

BUG2:

当我们输入m时,页面改变正确!

可是当我们点击下一页的时候,我们发现总页数发生了改变,错误了,因为当我们访问下一页的时候是个a标签,他只是把currentPage的信息携带了过去,而关于用户填写的其他过滤参数信息,并没有带过去,所以导致错误。

  • 因此我们可以想到,我们应该把页面的所有参数带携带到后台
  • 因此当我们应该用一个form表单来把整个页面罩住,通过点击按钮的form的action进行全部参数的提交到后台。
  • 当我们点击到首页,上一页。。。时,我们应该提交表单到后台

-------使用js代码解决发送请求时,携带过滤参数-------------------------------------------------------------------------------------

function  goPage(e) {document.getElementById("currentPage").value = e;document.forms[0].submit();
}
<a href="#" οnclick="goPage(1)">首页</a>&emsp;&emsp;
<a href="#" οnclick="goPage(${result.prevPage})">上一页</a>&emsp;&emsp;
<a href="#" οnclick="goPage(${result.nextPage})">下一页</a>&emsp;&emsp;
<a href="#" οnclick="goPage(${result.totalPage})">尾页</a>&emsp;&emsp;
当前第 ${result.currentPage} / ${result.totalPage} 页 一共 ${result.totalCount}&emsp;&emsp;
跳转到第 <input  id="currentPage"  name="currentPage" value="${result.currentPage}" οnchange="changePage()"> 页

19.4.4 流程4:完善功能

继续完善功能:

当我们输入 跳转到第?页时,页面刷新到对应的页数

当我们选择每页显示的数据条数,页面刷新

分析:

跳转到第?页,可以给一个name =currentPage参数

每页显示?条,可以给一个pageSize的参数

这两个参数就是我们基本分页信息参数

我们可以通过表单将他们一起提交

每页显示<select name="PageSize" οnchange="changePage()"><option  ${result.pageSize == 3 ? 'selected' : ''} value="3">3</option><option  ${result.pageSize == 5 ?  'selected' : ''} value="5">5</option><option  ${result.pageSize == 10 ?  'selected' : ''} value="10">10</option></select>
function  changePage (){document.forms[0].submit();
}

19.4.5 整个分页以及过滤的分析

20 用户注销_ 记住账号_验证码 过滤器_监听器

20.1 用户注销

删除session中客户的登录信息,并重定向到登录页面

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.getSession().removeAttribute("user");
//        resp.sendRedirect("/login");resp.sendRedirect("/crm.jsp");}

20.2 记住账号

点击“记住我”后,登录成功后,关闭页面,下次打开登录页面自动显示账号和勾选中记住我,

<input type="text" class="form-control" placeholder="请输入账号" name="username" value="${cookie.username.value}">
<input type="checkbox" name="remeberMe" value="true" ${cookie.username.value != null ?"checked" : ""}> 记住我
//登录成功后,判断是否选了“记住我”
String remeberMe = req.getParameter("remeberMe");
boolean b = Boolean.parseBoolean(remeberMe);
if (b) {//选择了“记住我”,则设值cookie的存活时间cookie.setMaxAge(ONE_WEEK);
} else {//没选择则,销毁cookie(设值存活时间为0)cookie.setMaxAge(DIE);
}
//响应流添加cookie,返回给浏览器
resp.addCookie(cookie);
req.getSession().setAttribute("user", user);
resp.sendRedirect("/employee");

20.3 验证码

验证码功能:首先得有一个生成验证码的程序,然后拿取验证码和用户输入进行比对

生成验证码的程序:

public class RandomCodeServlet extends HttpServlet {private static final long serialVersionUID = 1L;protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {// 生成随机数String randomCode = UUID.randomUUID().toString().substring(0, 5);// 把随机数放进 Session中req.getSession().setAttribute("RANDOMCODE_IN_SESSION", randomCode);// 创建图片对象int width = 80;int height = 40;int imageType = BufferedImage.TYPE_INT_RGB;BufferedImage image = new BufferedImage(width, height, imageType);// 画板Graphics g = image.getGraphics();

2.进行判断

//判断二维码是否正确,如果不正确则不需要任何的判断
//从RandomCodeServlet中拿到设值在作用域中的二维码
String randomcode_in_session = (String) req.getSession().getAttribute("RANDOMCODE_IN_SESSION");
//拿到用户输入的二维码
String randomCode = req.getParameter("randomCode");
//比较,注意:1.用户输入和二维码不能为空  2.用户输入的值应该先忽略大小写再和二维码进行比对
if (!(StringUtil.hasLength(randomCode)&& StringUtil.hasLength(randomcode_in_session)&& randomCode.equalsIgnoreCase(randomcode_in_session))) {//验证码不正确或者刷新不出来req.setAttribute("errorMsg", "二维码错误");req.getRequestDispatcher("/crm.jsp").forward(req, resp);
}

3.关于验证码的点击切换,点击也就是在请求一次,当我们点击请求时候,浏览器认为是同一个请求,所以不会在发送一个请求。

因此我们需要让浏览器地址发生改变,加个参数,实际上还是同一个请求,欺骗浏览器

 <img   id="randomCode" alt="验证码" src="/randomCode" οnclick="changeImg()">
<script>function changeImg() {document.getElementById("randomCode").src = "/randomCode?"+new Date().toString();}
</script>

20.4 过滤器

20.4.1 什么是过滤器?

过滤器Filter是JavaWeb的三大组件之一,可以改变一个请求和修改一个response,对指定的请求request进行了拦截后,进行处理操作,之后放行,执行请求Servlet。用于解决Serlvte代码的重复问题!!

20.4.2 基本的开发步骤和生命周期

开发的步骤和servlet基本一模一样!!

//filter的基本认识和使用
//当我们浏览器发送请求时,先触发了filter,后启动servlet,
//当我们访问浏览器的时候,先执行我们放行前的语句,在我们放行后,执行对应的请求servlet语句,最后在执行放行后的语句
public class HellowFilter implements Filter {public HellowFilter() {System.out.println("tomcat创建filter对象");}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("初始化");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("hellowFilter1");filterChain.doFilter(servletRequest,servletResponse);System.out.println("hellowFilter11");}@Overridepublic void destroy() {System.out.println("服务器被正常关闭!");}
}
<filter><filter-name>hellowFilter</filter-name><filter-class>cn.ybzy.web.Filter.HellowFilter</filter-class>
</filter>
<filter-mapping><filter-name>hellowFilter</filter-name><url-pattern>/hellow</url-pattern>
</filter-mapping>

使用注解的方式与Servlet一样!!

@WebServlet("/hellow")

Filter的生命周期:

当我们开启tomncat服务器时,会检测是否有Filter,有的话创建Filter对象

  1. 当一个请求被拦截时,则会触发Filter的init初始化方法,
  2. 当请求路径与filter的拦截路径相同时,执行DoFilter方法,对请求前进行预处理,后放行,放行后执行对应的 Servlet,这个过程每当请求路径与filter的拦截路径相同时,就会一直执行
  3. 当服务器被正常关闭时,执行destroy,filter对象被销毁。

20.4.3 过滤方式

filter默认的过滤方式是请求(重定向),也就是重定向时会过滤两次,执行两次doFilter,而过滤转发的时候,只会执行一次DoFilter,

如果想过滤转发执行两次则需要进行配置

<filter><filter-name>hellowFilter</filter-name><filter-class>cn.ybzy.web.Filter.HellowFilter</filter-class>
</filter>
<filter-mapping><filter-name>hellowFilter</filter-name><url-pattern>/*</url-pattern>filter的默认值:需要一次全新的请求<dispatcher>REQUEST</dispatcher><!-- 过滤转发--><dispatcher>FORWARD</dispatcher>
</filter-mapping>

20.4.4 配置通过的错误页面

首先在web下面编写对应的页面,后在web.xml里面进行配置

配置通用的错误页面<error-page><error-code>404</error-code><location>/404.jsp</location></error-page><error-page><error-code>500</error-code><location>/500.jsp</location></error-page>

20.4.5 字符编码过滤的三种方式,由坏到好

1.由于每个serlvelt都要进行字符编码的设值,因此我们可以通过 过滤器在请求前先进行字符编码的设置,这样减少了代码,使代码结构更加清晰,责任更分明。但是这却有一个缺点:硬编码问题,当我们需要修改字符编码时,需要停掉服务器修改代码,成本巨大

    @Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {((HttpServletRequest) servletRequest).setCharacterEncoding("utf-8");filterChain.doFilter(servletRequest,servletResponse);}

2.为了解决硬编码问题,我们可以在web文件里写一些配置参数

    <filter><filter-name>CharactorEncodingFilter</filter-name><filter-class>cn.ybzy.web.Servlet.CharactorEncodingFilter</filter-class><init-param>
<!--            解决硬编码问题--><param-name>encoding</param-name><param-value>utf-8</param-value></init-param><init-param><param-name>force</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>CharactorEncodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
public class CharactorEncodingFilter implements Filter {String encoding;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {//解决硬编码问题encoding = filterConfig.getInitParameter("encoding");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {if (StringUtil.hasLength(encoding)){servletRequest.setCharacterEncoding(encoding);}filterChain.doFilter(servletRequest,servletResponse);}

3.第二种方式的话会使所有的过滤器和页面使用一样的编码格式,那么在实际需求中,我们有一个过滤器链,在过滤时,我们会经过不同的请求和对应的页面,那么对应的过滤器和页面需要不同的编码格式,因此我们需要做出改变!!!在每个filter中配置自己的字符集编码或者不配置,和配置是否需要覆盖上一个过滤器的字符编码的标识

    <filter><filter-name>CharactorEncodingFilter</filter-name><filter-class>cn.ybzy.web.Servlet.CharactorEncodingFilter</filter-class><init-param>
<!--            解决硬编码问题--><param-name>encoding</param-name><param-value>utf-8</param-value></init-param><init-param><param-name>force</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>CharactorEncodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
//注意:根据需求,存在多个过滤器(过滤的页面)存在都有自己的编码格式,我们可以选择是否覆盖上一个编码
public class CharactorEncodingFilter implements Filter {String encoding;boolean force;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {//解决硬编码问题encoding = filterConfig.getInitParameter("encoding");force = Boolean.parseBoolean(filterConfig.getInitParameter("force"));}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {if (force || servletRequest.getCharacterEncoding() == null){servletRequest.setCharacterEncoding(encoding);}filterChain.doFilter(servletRequest,servletResponse);}

20.4.6 登录校检过滤器

因为在很多项目中,有很多页面都需要用户登录后才可以进行访问,而不是通过浏览器地址进行直接访问(不安全),因此我们可以使用过滤器,在发送特定的页面(请求登录后的页面时),我们判断是否登录了

普通方式:会显示页面重定向太多!!!

//@WebFilter("/*")
//出现问题:页面显示重定向太多
//当我们输入login.jsp的时候,被过滤,后重定向到crm.jsp,
// 在响应到页面时又被过滤,导致一直循环过滤,一直重定向
//当我们访问crm.jsp时,打开控制台,js,css这些静态资源也给过滤了
public class CheckLoginFilter  implements Filter {List<String> unCheck;String checkUrl;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {unCheck = Arrays.asList(filterConfig.getInitParameter("unCheck").split(";"));checkUrl = filterConfig.getInitParameter("CheckUrl");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = ((HttpServletRequest) servletRequest);HttpServletResponse  response   =  ((HttpServletResponse) servletResponse);User user = (User) request.getAttribute("user");//获取访问的请求资源路径String requestURI = request.getRequestURI();if (user == null){response.sendRedirect("/crm.jsp");return;}filterChain.doFilter(request,response);}

解决办法:

第一种方式:配置需要不需要检查的路径

//解决办法:我们应该对特定的一些进行检查
/*方式一: 在web.xml里配置参数信息,配置我们不过滤的页面
在过滤器里获取,并且判断访问的请求是否满足不访问,
缺点:这种方式太繁琐,页面很多需要写,包括一些css,js,这样非常多,仅仅一个检查登录就要写很多*/
public class CheckLoginFilter  implements Filter {List<String> unCheck;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {unCheck = Arrays.asList(filterConfig.getInitParameter("unCheck").split(";"));}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = ((HttpServletRequest) servletRequest);HttpServletResponse  response   =  ((HttpServletResponse) servletResponse);User user = (User) request.getAttribute("user");//获取访问的请求资源路径String requestURI = request.getRequestURI();if (user == null && !unCheck.contains(requestURI)){response.sendRedirect("/crm.jsp");return;}filterChain.doFilter(request,response);}@Overridepublic void destroy() {}

第二种方式:配置需要检查的路径

更改页面的需要检查的请求路径:/check/*

public class CheckLoginFilter  implements Filter {List<String> unCheck;String checkUrl;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {unCheck = Arrays.asList(filterConfig.getInitParameter("unCheck").split(";"));checkUrl = filterConfig.getInitParameter("CheckUrl");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = ((HttpServletRequest) servletRequest);HttpServletResponse  response   =  ((HttpServletResponse) servletResponse);User user = (User) request.getAttribute("user");//获取访问的请求资源路径String requestURI = request.getRequestURI();if (user == null){response.sendRedirect("/crm.jsp");return;}filterChain.doFilter(request,response);}
<filter><filter-name>CheckLoginFilter</filter-name><filter-class>cn.ybzy.web.Servlet.CheckLoginFilter</filter-class><init-param><param-name>unCheck</param-name><param-value>/crm.jsp;</param-value></init-param>
</filter>
<filter-mapping><filter-name>CheckLoginFilter</filter-name><url-pattern>/check/*</url-pattern>
</filter-mapping>

20.5 监听器

20.5.1 什么监听器?

监听器Listener是javaWeb的三大组件之一,主要用于监听作用域对象的创建和销毁,作用域对象的增删改

20.5.2 那么如何监听用户的访问时用户的个数呢?

不可以使用servlet因为一个用户多次访问service时,会导致客户数量不断增加

package cn.ybzy.web.Listener;import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
//用于监听游客的数量
//不能用servlet,一个游客可以访问多次servlet里的service方法,导致错误
public class VisitorListener implements HttpSessionListener {private static  int visitorCount = 0;@Overridepublic void sessionCreated(HttpSessionEvent httpSessionEvent) {//session对象创建时会执行该方法visitorCount++;System.out.println(visitorCount);}@Overridepublic void sessionDestroyed(HttpSessionEvent httpSessionEvent) {//sesssion对象销毁时会执行该方法visitorCount--;System.out.println(visitorCount);}
}

20.5.3 超级管理员的添加:

一个后台需要一个超级管理员可以对所有的数据进行操作。因此我们可以使用监听器实现,当我们服务器启动的时候增加一个超级管理员,所以我们要监听的作用域对象是ServletContext

//当服务器开启的时候,我们要开启创建一个超级管理员
//ServletContextListener 监听ServletContex作用域对象
@WebListener
public class AdminListener implements ServletContextListener {private UserService userService = new UserServiceImpl();@SneakyThrows@Overridepublic void contextInitialized(ServletContextEvent servletContextEvent) {User user = userService.checkLogin("admin", "1");if (user == null){//超级管理员还没有创建User u = new User( "super", "1",null);userService.insert(u);}}@Overridepublic void contextDestroyed(ServletContextEvent servletContextEvent) {}
}

20.5.4 细节

我们发现监听器的名称为XXXListener,而XXX表示监听的作用域对象,当我们实现了对应的监听器,我们监听和操作对应的作用域对象。

例如 ServletContextListener -> ServletContext

​ HttpSessionListener -> HttpSession

:我们应该对特定的一些进行检查
/方式一: 在web.xml里配置参数信息,配置我们不过滤的页面
在过滤器里获取,并且判断访问的请求是否满足不访问,
缺点:这种方式太繁琐,页面很多需要写,包括一些css,js,这样非常多,仅仅一个检查登录就要写很多
/
public class CheckLoginFilter implements Filter {
List unCheck;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
unCheck = Arrays.asList(filterConfig.getInitParameter(“unCheck”).split(";"));
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = ((HttpServletRequest) servletRequest);
HttpServletResponse response = ((HttpServletResponse) servletResponse);
User user = (User) request.getAttribute(“user”);
//获取访问的请求资源路径
String requestURI = request.getRequestURI();
if (user == null && !unCheck.contains(requestURI)){
response.sendRedirect("/crm.jsp");
return;
}
filterChain.doFilter(request,response);
}

@Override
public void destroy() {}

第二种方式:配置需要检查的路径更改页面的需要检查的请求路径:/check/*```java
public class CheckLoginFilter  implements Filter {List<String> unCheck;String checkUrl;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {unCheck = Arrays.asList(filterConfig.getInitParameter("unCheck").split(";"));checkUrl = filterConfig.getInitParameter("CheckUrl");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = ((HttpServletRequest) servletRequest);HttpServletResponse  response   =  ((HttpServletResponse) servletResponse);User user = (User) request.getAttribute("user");//获取访问的请求资源路径String requestURI = request.getRequestURI();if (user == null){response.sendRedirect("/crm.jsp");return;}filterChain.doFilter(request,response);}
<filter><filter-name>CheckLoginFilter</filter-name><filter-class>cn.ybzy.web.Servlet.CheckLoginFilter</filter-class><init-param><param-name>unCheck</param-name><param-value>/crm.jsp;</param-value></init-param>
</filter>
<filter-mapping><filter-name>CheckLoginFilter</filter-name><url-pattern>/check/*</url-pattern>
</filter-mapping>

20.5 监听器

20.5.1 什么监听器?

监听器Listener是javaWeb的三大组件之一,主要用于监听作用域对象的创建和销毁,作用域对象的增删改

20.5.2 那么如何监听用户的访问时用户的个数呢?

不可以使用servlet因为一个用户多次访问service时,会导致客户数量不断增加

package cn.ybzy.web.Listener;import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
//用于监听游客的数量
//不能用servlet,一个游客可以访问多次servlet里的service方法,导致错误
public class VisitorListener implements HttpSessionListener {private static  int visitorCount = 0;@Overridepublic void sessionCreated(HttpSessionEvent httpSessionEvent) {//session对象创建时会执行该方法visitorCount++;System.out.println(visitorCount);}@Overridepublic void sessionDestroyed(HttpSessionEvent httpSessionEvent) {//sesssion对象销毁时会执行该方法visitorCount--;System.out.println(visitorCount);}
}

20.5.3 超级管理员的添加:

一个后台需要一个超级管理员可以对所有的数据进行操作。因此我们可以使用监听器实现,当我们服务器启动的时候增加一个超级管理员,所以我们要监听的作用域对象是ServletContext

//当服务器开启的时候,我们要开启创建一个超级管理员
//ServletContextListener 监听ServletContex作用域对象
@WebListener
public class AdminListener implements ServletContextListener {private UserService userService = new UserServiceImpl();@SneakyThrows@Overridepublic void contextInitialized(ServletContextEvent servletContextEvent) {User user = userService.checkLogin("admin", "1");if (user == null){//超级管理员还没有创建User u = new User( "super", "1",null);userService.insert(u);}}@Overridepublic void contextDestroyed(ServletContextEvent servletContextEvent) {}
}

20.5.4 细节

我们发现监听器的名称为XXXListener,而XXX表示监听的作用域对象,当我们实现了对应的监听器,我们监听和操作对应的作用域对象。

例如 ServletContextListener -> ServletContext

​ HttpSessionListener -> HttpSession

2.20

第二阶段-javaweb相关推荐

  1. 写给java初学者,从零开始学习java开发的完整学习路线

    要问编程王者是谁?自然是非java莫属!发展了20多年,学习java的人络绎不绝.不管是有计算机基础还是没有基础,都想学习这门前途无量的技术.这时候有人担心了,零基础该怎么学java呢?会不会困难重重 ...

  2. java基础不好框架能学会吗_转行Java能学会吗?零基础学习Java的学习路线

    Java的跨平台性.通用性.安全性.高效性决定了这门语言在未来10年都会是最热门的语言之一.Java技术的安全性和平台移植性足够让他应用到不同的领域,它的工作需求足够大,现实一点来说即使Java濒临o ...

  3. Java学习路线(完整详细版)

    科技在进步,时代也在发展,很多人的理想再也不是骑马喝酒走四方,而是学习掌握java技术,真正的实现高薪就业,说到学习Java,我们今天就来说说Java开发需要学习的内容,说说java学习路线,说说Ja ...

  4. JavaEE全套资料+视频+工具

    JavaEE全套资料+视频+工具[点击链接原文] Java学习路线图引言: 一.Java学习路线图-流程篇: 二.Java学习路线图-视频篇: 1.第一阶段-Java基础入门 Java视频篇第一阶段- ...

  5. 如何进行Java学习

    最近有很多java初学者问我java该怎么学,这个说实话得需要自己有一定毅力才行,但是今天我还是给大家总结下吧. 大致需要学习这几个大纲:1·Java基础课程.2·JavaWeb.3·Java框架课程 ...

  6. 好程序员Java培训分享如何快速入门Java编程

    好程序员Java培训分享如何快速入门Java编程,作为老牌编程语言,Java拥有广阔的市场应用,企业对Java人才的需求一直居高不下.有很多非专业.零基础的人想要学习Java却不知道怎么快速入门,接下 ...

  7. 0基础java好学吗?完整学习路线图速收藏

    java的火爆袭来,吸引了大批的初学者涉猎,想入行java,却苦于自己没有基础,担心学不好.小编告诉你,这些担心都是浮云,你只是惧怕走进一个新天地,人都是一样的,面对自己不熟悉的领域总会有莫名的恐惧感 ...

  8. Java学习路线图,内附完整Java自学视频教程+工具经验

    Java学习路线图更新日志:         增加视频<2016最新视频struts2> 密码:vhfp(2016.11.10) 增加视频<6天玩转mysql视频> 密码:a8 ...

  9. JAVA培训哪里好?

    选择培训机构建议粉以下几个步骤: 第一步 分析个人情况,选择适合自己的学习形式 (一)线上教学or线下教学 培训的形式很多,有网上录播课程.网上直播.线下面授小班课程.线下面授大班课程. 到底自己适合 ...

最新文章

  1. 一个神经元的价值和一个神经病的坚持
  2. 以假乱真的“人造脸”,能骗过面部识别吗?
  3. 在centos6.5安装MariaDB(mysql)
  4. 使用 TOGAF 9.1 框架與 ArchiMate 3.0 建模語言
  5. bugku—— 代码审计 做题记录
  6. statistics_level参数的介绍
  7. [vim]在vim中格式化xml
  8. M0最高优先级的中断设计
  9. Java 中的接口有什么作用?为什么不直接用类?
  10. 同一个项目,项目名称不一致,这两个项目同时在Eclipse中出现
  11. 2022年10 款最佳计算机视觉开源数据标注工具
  12. python运行出玫瑰花的代码_一支玫瑰 - python代码库 - 云代码
  13. 缠中说禅 教你炒股票 全集列表
  14. 张飞的流水帐日记【分享】
  15. 篇16:Windows安装配置Nessus时遇到的一些问题说明
  16. vue 音乐进度条拖拽
  17. Wow64(32位进程)注入DLL到64位进程
  18. Absolute公司防盗追踪软件到底存在什么安全问题
  19. VisualStudio2019,基于.NET Framework的单元测试
  20. 有了这份攻略,再也不怕数据分析面试了!

热门文章

  1. fifa15服务器维护,菜单界面详解:游戏设置界面_FIFA15系统教程图文攻略(完结)_FIFA15图文全攻略_单机攻略_跑跑车单机游戏网...
  2. simulink Simscape Mulitybody 导入CAD装配体分析-Inventor 为例
  3. 高考最后17天,家长最关心的11个问题和答案都在这
  4. 怎样卸载干净eclipse_【eclipse卸载】eclipse卸载不掉,如何彻底卸载eclipse?
  5. 向量的几种距离计算方法
  6. Sleuth基本知识
  7. 小程序URL解码decodeURI与decodeURIComponent的区别
  8. 89年的测试员,什么也不会敢要15K,看着他的简历我思绪万千...
  9. 腾讯内部技术:《轩辕传奇》服务器架构演变
  10. PMP是终身有效吗?