JavaWeb基础

JavaWeb

1. MySQL 数据库

常用名词简称及英文翻译
数据库DB --database
数据库系统DBS --database system
数据库管理员DBA --database administrator
数据库管理系统DBMS --database manage system

1.数据类型

  1. 整型

    数据类型含义
    tinyint(m)1个字节表示(-128~127)
    smallint(m)2个字节表示(-32768~32767)
    mediumint(m)3个字节表示(-8388608~8388607)
    int(m)4个字节表示(-2147483648~2147483647)
    bigint(m)8个字节表示(±9.22*10的18次方)
  2. 浮点型

    数据类型含义
    float(m,d)4字节,8位精度,m:十进制的数字个数,d:小数点的个数
    double(m,d)8字节,16位精度

    m 只影响显示效果,不影响精度,d 却不同,会影响到精度

  3. 字符串

    数据类型含义
    char(n)固定长度的字符串,最多255个字符
    varchar(n)固定长度的字符串,最多65535个字符
    tinytext可变长度的字符串,最多255个字符
    text可变长度的字符串,最多655535个字符
    mediumtext可变长度的字符串,最多(2的24次方-1)个字符
    longtext可变长度的字符串,最多(2的32次方-1)个字符
  4. 日期时间

    数据类型含义
    date日期:‘2021-12-2’
    time时间:‘12:25:36’
    datetime日期时间:‘2021-12-2 12:25:36’
    timestamp时间戳,不固定

    timestamp:比较特殊,如果定义一个字段的类型为 timestamp,这个字段的时间会在其他字段修改时自动刷新,所以这个数据类型的字段可存放这条数据最后被修改的时间,而不是真正用来存放时间的

2. MySQL 常用运算符

类型运算符
算术运算符+、-、*、/、%
比较运算符>、>=、<、<=、=、!=不等于、<>不等于
innot inbetween andlikeexistnot existis nullnot null
逻辑运算符andornot、xor(异或)

3. 数据库操作

MySQL 数据库登录

mysql [-h 目的ip地址] -u 用户名 -p 密码

注:如果在本地, -h 目的ip地址 可以省略

3.1 数据库级别的操作

show databases;							   # 查看所有数据库
use 数据库名;								# 使用某个数据库
create database [if not exists] 数据库名;	# 创建数据库
drop database [if exists] 数据库名;			# 删除数据库
show variables like 'char%';			  # 查看字符集

3.2 表级别的操作

alter 修改,modify 修改,change 改变,add 添加,drop 删除,after 在…之后,rename…to 重命名

primary key 主键,unique 唯一约束,index 索引

    1. 创建表

      create table teacher(
          t_id int(11),
          t_name varchar(20),
          t_age int(10)
      );
      create table tea(
          t_id int(11) primary key auto_increment,
          t_name varchar(20) not null,
          t_age int(10)
      );
      
    2. 表的其他操作

      drop table teacher;				# 删除表
      desc teacher;					# 查看表结构
      show tables;					# 查看所有表
      show create table teacher;		# 查看建表信息
      alter table 旧表名 rename 新表名;	# 重命名表名
      rename table 旧表名 to 新表名;	# 重命名表名
      
  1. 表字段

    1. 添加表字段

      alter table teacher add(t_sex char(3));					# 给表添加字段
      alter table teacher add t_job varchar(20) after t_name; # 给某个字段后添加字段
      
    2. 删除表字段

      alter table teacher drop t_sex;
      
    3. 修改字段类型

      alter table teacher modify t_sex varchar(3);
      alter table teacher modify t_sex varchar(3) not null;
      
    4. 修改字段类型/字段名

      # alter table 表名 change 旧字段名 新字段名 新字段类型;
      alter table teacher change t_job t_len VARCHAR(10);
      

4. 约束主键、唯一约束、外键

  1. 主键

    主键唯一标示一条记录,不能有重复,不允许为空,用来保证数据的完整性。一个表只能有一个主键

    alter table teacher add primary key(t_id);	# 添加主键
    alter table teacher drop primary key;		# 删除主键
    
  2. 唯一约束

    alter table teacher add unique(t_name);		# 添加约束
    alter table teacher drop unique t_name;		# 删除约束
    
  3. 外键

    一个表的外键是另外一个表的主键或者是唯一约束的列,外键可以重复,外键可以为空,用来和其他表建立联系。

    一个表可以有多个外键

    image-20220114214745366

5. 索引

索引

alter table teacher add index index_name(t_age);# 添加索引并命名
alter table teacher add index(t_age);			# 添加索引
alter table teacher drop index t_age;			# 删除约束/索引

6. MySQL 常用函数

  1. 字符串函数

    length(str);		# 单位是字节
    char_length(str);	# 单位是字符
    
    select length('你好');		# UTF:6个字节
    select char_length('你好');	# 2个字符
    
  2. 聚合函数

    avg(col);		# 返回指定列的平均值
    count(col);		# 返回指定列中非 null 的个数
    
    select avg(t_age) from tea;
    select count(t_name) from tea;
    
  3. 求最大值,最小值,总和

    max(col),min(col),sum(col)
    
    select max(t_age) as '最大年龄' from tea; --as 可以省略(作用是起别名)
    select min(t_age) '最小年龄' from tea; 
    select sum(t_age) '年龄总和' from tea;
    
  4. 日期和时间函数

    now(); # 获取当前时间 
    
    select now();
    select now() from dual; # 为一个假的表名
    
  5. 格式化时间

    date_format(date,format);	# 将时间转化为按指定格式
    
    select date_format(now(),'%Y-%m-%d');
    

7. sql 语句单表操作

在没有表被引用的情况下,允许使用 dual 作为一个假的表名

1. sql 语法

  1. insert into ...values

    # 通常用第一种方式,效率更高
    insert into 表名(字段名 1,字段名 2,字段名 3,...) values(1,2,3,...);
    insert into 表名 values(1,2,3,...);
    
    # 例如
    insert into tea(tid,tname,tage,birthday) values(null,'王五',28,'2000-11-11');
    insert into tea(tid,tname,tage,birthday) values(1,'王五',28,'2000-11-11');
    insert into tea values(1,'王五',28,'2000-11-11');
    
  2. delete from

    delete from 表名;
    delete from 表名 where 条件;
    
    # 例如
    delete from tea;
    delete from tea where t_id=2;
    
  3. update set

    update 表名 set 字段名 1='值 1',字段名 2='值 2',...;
    update 表名 set 字段名 1='值 1',字段名 2='值 2',... where 条件;
    
    # 例如
    update tea set t_name='张三丰',t_sex='女';
    update tea set t_name='张三',t_sex='男' where t_id=1;
    
  4. select from

    select * from 表名;	# 效率较低,开发时一般不用
    select 字段名 1,字段名 2,... from 表名;
    
    # 例如
    select * from tea; 
    select t_id,t_name from tea;
    

2. 结合运算符的单表查询

  1. 结合比较运算符查询

    # >、>=、<、<=、=、!=或<>(不等于)
    # in、not in、between and、like、some、any、all、
    # is null、is not null
    select * from tea where t_id > 5;
    select * from tea where t_sex='女' and birthday='2000-11-11';
    select * from tea where t_sex='男' or t_id < 5;
    select * from tea where t_name like '%d';
    select * from tea where t_name like '_d';
    select * from tea where t_name like '%w%';
    
    select * from 表名 where 字段 > any(1,2,...,值 n); # 大于任意一个值即条件成立
    select * from 表名 where 字段 < all(1,2,...,值 n); # 小于所有值则条件成立.
    select * from 表名 where 字段 < some(1,2,...,值 n);# some 作用和 any 相同
    
    select * from tea where t_job is not null;
    select * from tea where t_job is null;
    
  2. 结合逻辑运算符查询

    # and、or
    select * from tea where t_age > 20 and t_age < 30;
    select * from tea where t_age between 20 and 30;
    
    select * from tea where t_age=22 or t_age=25 or t_age=28;
    select * from tea where t_age in (22,25,28);
    

注意:

  1. 没有表被引用的情况下,允许使用 dual 作为一个假的表名

  2. 可以使用 as 给字段起一个别名,可省略

    select count(t_id) '老师总数' from tea;
    

3. 单表查询的功能实现

  1. 排序order byasc升序(默认) ,desc 降序

    select * from tea order by birthday desc;
    select * from tea order by birthday asc;
    
  2. 分组group byhaving:分组的条件

    select t_sex,count(t_sex),avg(t_age) from tea group by t_sex;
    select t_sex,count(t_sex),avg(t_age) from tea group by t_sex having t_sex='女';
    
  3. 分页limit:(参数1位置默认从0开始,可省略参数2查询记录数

    select * from tea limit 0,3;
    select * from tea limit 3; 		# 位置参数为0,可以省略
    

    功能的综合实现:先分组,在排序,最后分页

    #查询各个部门员工的工资在200以上员工,平均薪水在300以上的部门的编号和平均薪资,按平均薪资逆序排列
    select avg(salary) avg_sal,dept_id from emp where salary > 200
    group by dept_id having avg_sal > 300  # 分组
    order by avg_sal desc;				   # 排序
    
  4. 去重复distinct

    select distinct 字段,字段 from 表名;
    

8. sql 语句联表查询

联表查询:也叫多表查询,就是几个表一起查

1. 内连接:join on

# 语法:
select 字段名 from 表名1 [inner] join 表名2 on 连接条件 where 条件;
等价于
select 字段名 from 表名1,表名2 where 连接条件 and 条件;
# 1.查员工的姓名和其所对应的部门名
select e.name,d.name from emp e inner join dept d on e.dept_id = d.id;
等价于
select e.name,d.name from emp e,dept d where e.dept_id = d.id;   # 简写形式
# 2.查询员工的姓名及其经理的姓名(内连接)
select e1.name '员工',e2.name '领导' from emp e1,emp e2 where e1.gmrid=e2.id;

2. (左/右)外连接:left/right join on

左外连接:不仅列出与连接条件相匹配的行,还列出左表所有符合 where 过滤条件的数据行

右外连接:不仅列出与连接条件相匹配的行,还列出右表所有符合 where 过滤条件的数据行

# 语法:
select 字段名 from 表名 1 left/right [outer] join 表名 2 on 连接条件 where 条件;
select d.name,e.name from dept d left join emp e on e.dept_id=d.id where d.name != 'ls';
select d.name,e.name from dept d right join emp e on e.dept_id=d.id;
# 列出部门名称和这些部门员工的信息,同时列出没有员工的部门(外连接)
select * from emp e right join dept d on e.dept_id=d.id;
select * from dept d left join emp e on e.dept_id=d.id;

注意:

  1. 左链接以 left join 左边表为基础表进行连接查询,则左表中的信息全部展示
    右表部分如果没有找到相关连接信息,则以 null 代替,即是右表中跟左表没关连的内容不展示。
  2. 右链接以 right join 右边表为基础表进行连接查询,则右表中的信息全部展示
    如果在左表中没有找到相关连接信息,则以 null 代替,即是左表表中跟右表没关连的内容不展示。

3. 交叉连接:cross join

没有 on 子句和 where 子句,返回连接表中所有数据行的笛卡尔积。

笛卡尔积: 1,2 a,b,c --> 1a,1b,1c,2a,2b,2c

# 语法:
select 字段名 from 表名1 cross join 表名2;
select * from1,2,...,表n;				# 多张表中的内容分别进行组合(笛卡尔积)

# 拓展:带条件的联合查询(交叉连接)
select * from1,2 where1.字段 =2.字段; # 查询出2表中,符合条件的信息.(等同于内连接)
select 字段名 from1,2 where 连接条件 and 条件;
等价于
select 字段名 from1 [inner] join2 on 连接条件 where 条件;

4. 自连接

自连接:参与连接的表是同一张表

# 语法:
select * from 表 别名 1,表 别名 2 where 别名 1.字段=别名 2.字段;
select * from emp em,emp em2 where em.mgr = em2.empno;	# 展示员工和其直属部门领导的信息
# 查询出员工的姓名和其领导的姓名。   emp e1:员工表 emp e2:经理表
select e1.name,e2.name from emp e1 inner join emp e2 on e1.gmrid=e2.id; # 内连接
select e1.name,e2.name from emp e1 left join emp e2 on e1.gmrid=e2.id;	# 左外连接
select e1.name,e2.name from emp e1 right join emp e2 on e1.gmrid=e2.id;	# 右外连接	

5. 临时表

查询到的结果当做一个临时表其他表进行联表查询

# 查询至少有一个员工的部门,显示部门的编号,名称和人数。
# 1.把员工表 emp 根据 dept_id 分组,统计每个部门的人数。  先分组
select dept_id,count(id) coun from emp group by dept_id having dept_id is not null;

# 2.将第一步中得到的结果当成一个临时表与部门表 dept 进行联合查询,得到部门的信息。 再联表查询
select d.id,d.name,em.coun from dept d,
(select dept_id,count(id) coun from emp group by dept_id having dept_id is not null) em
where d.id=em.dept_id and em.coun > 0;

6. 子查询

某些情况下,当进行查询的时候,需要的条件另外一个 select 语句的结果,这时就要用子查询

为了给主查询(外部查询)提供数据而首先执行的查询就叫子查询(内部查询)

用子查询的关键字有:**in、not in、exist、not exist、=、<>**等

select * from1 where 字段 in (select 字段 from2);
select * from1 where exists (select * from2 where1.字段=2.字段);
select * from1 where not exists(select * from2 where1.字段=2.字段);

# 查询月薪最高的员工的名字
select max(salary) from emp;
select name,salary from emp where salary=(select max(salary) from emp);

# 查询月薪比平均月薪高的员工的名字
select avg(salary) from emp;
select name,salary from emp where salary > (select avg(salary) from emp);
# 查询工资比剑圣高的员工(子查询)
select salary from emp where name='剑圣';
select * from emp where salary > (select salary from emp where name='剑圣');

9. mysql 要注意的地方

  1. 表名,字段名windows 下不区分大小写,在 linux 系统下区分大小写

  2. 索引不可以更改,只能删除重建

  3. 空值(’’)不占用空间null 的值是未知的,占用空间,不走索引

    建议在建表的时候最好设置字段为 not null 来避免这种低效率的事情发生。

  4. count()统计某列非空的记录数,如果是 null 系统会自动忽略掉,但是空值('')会统计到其中

  5. 对于 timestamp 数据类型,如果往这个数据类型的列插入 null,则出现的是当前系统时间,如果往这个数据类型的列插入空值(’’),则会出现’0000-00-00 00:00:00’

    区别空值null
    是否占空间不占空间占空间
    是否走索引走索引不走索引
    **count()**统计要统计忽略不统计
    timestamp 类型出现’0000-00-00 00:00:00’当前系统时间

2. JDBC

1. JDBC 简介

JDBC:Java Database Connection,表示数据库连接,是 java 中专门提供的一组用于操作数据库标准,所有数据库支持此标准。既然是标准,所以说 JDBC 实际上是一套类库的接口

主要的操作类和接口DriverManager 类、Connection 接口,Statement 接口,PreparedStatement 接口,ResultSet 接口

2. JDBC 使用步骤

准备工作:
	1.导包:mysql-connector-java-5.1.15-bin.jar
	2.创建所用的表
  1. 加载数据库驱动程序

    Class.forName("com.mysql.jdbc.Driver");//加载驱动
    
  2. 通过连接地址,用户名和密码获取数据库连接对象

    Connection conn = DriverManager.getConnection(连接地址 url,用户名,密码);
    //连接路径url:jdbc:mysql://localhost:3306/数据库名
    
  3. 构造 sql 语句

    String sql = "sql 语句";
    
  4. 获得 Statement 实例(其实就是 sql 语句的发送器)

    Statement stmt=conn.createStatement();
    
  5. 执行 sql 语句

    stmt.executeQuery(sql); //用于查询
    stmt.executeUpdate(sql);//用于增,删,改
    
  6. 关闭连接

    stmt.close();
    conn.close();
    

3. JDBC 连接池

每次访问数据库时都需要创建一个 Connection 对象,得到一个数据库的连接,用完之后再给他销毁,关闭这个连接。Connection 对象创建和销毁的过程非常消耗资源的,可能代码比较简单,但内部执行的过程比较复杂。比如说,在 mysql 服务器执行 sql 语句可能就用了 10ms,而创建和销毁数据库连接就可能花了 1000ms。所以说,频繁的创建和销毁连接就可能影响程序的性能。因此我们的连接池就出现了,用连接池去管理这个数据库的连接,如果一个连接创建完了,可以重复的去利用这个连接,避免频繁的创建和销毁数据库的连接。编程的时候用的最多的连接池就是 C3P0,一个开源的组件,用起来比较简单,配置一下就行了。

C3P0 概述

C3P0 是一个开源的 JDBC 连接池

使用它的开源项目有 Hibernate(是一个非常著名的持久化框架,专门做数据库操作的),Spring 等。

下载网址:http://sourceforge.net/projects/c3p0/

使用 C3P0 配置数据库连接池

  1. 导入 jar 包c3p0-0.9.2.1.jarmchange-commons-java-0.2.3.4.jar

  2. 创建一个 xml 文件保存配置信息,取名 c3p0-config.xml,直接在 src 文件夹下面创建

  3. 通过 java 代码创建 C3P0 数据库连接池

    1. 创建数据源

      ComboPooledDataSource ds = new ComboPooledDataSource(); 		// 加载默认配置
      ComboPooledDataSource ds = new ComboPooledDataSource("MySQL"); 	// 加载命名配置
      
    2. 获取连接

      Connection conn = ds.getConnection(); // 使用JDBC连接池,获得connection连接对象
      

4. 数据库工具类

  1. 数据库连接工具类

    public class JdbcUtil {
        public static String driver = "com.mysql.jdbc.Driver";
        public static String url = "jdbc:mysql://localhost:3306/mysql";
        public static String username = "root";
        public static String password = "root";
        public static Connection conn;
        public static PreparedStatement ps;
        public static ResultSet rs;
        public static int i;
        static{ //加载驱动程序
            try {
            	Class.forName(driver);
            } catch (ClassNotFoundException e) {
            	e.printStackTrace();
            }
        }
        //获取数据库连接对象
        public static Connection getConn() throws SQLException{
            conn = DriverManager.getConnection(url, username, password);
            return conn;
        }
        //创建一个方法适用于所有的查询
        public ResultSet select_for_allcases(String sql,Object[] obj) 
            							throws SQLException{
            ps = conn.prepareStatement(sql);
            if(obj!=null){
                for(int i=1;i<=obj.length;i++){
                	ps.setObject(i, obj[i-1]);
                }
            }
            rs = ps.executeQuery();
            return rs;
        }
        //创建一个方法适用于所有的增,删,改
        public int update_for_allcases(String sql,Object[] obj) throws SQLException{
            ps = conn.prepareStatement(sql);
            if(obj!=null){
                for(int i=1;i<=obj.length;i++){
                    ps.setObject(i, obj[i-1]);
                }
            }
            i = ps.executeUpdate();
            return i;
        }
        //统一处理关闭资源
        public static void close() throws SQLException{
            if(rs != null){ rs.close(); }
            if(ps != null){ ps.close(); }
            if(conn != null){ conn.close(); }
        }
    }
    
  2. 数据库**带连接池的连接工具类**

    // 使用饿汉式的单例模式
    public class DBUtil {
        private static ComboPooledDataSource ds = null;
        static{
            //1.创建数据源   默认加载 src/c3p0-config.xml 中的配置
            ds = new ComboPooledDataSource();
            // 在配置文件中设置,在程序中还可以重新设置相关信息
            /*try {
                ds.setDriverClass("com.mysql.jdbc.Driver");
                ds.setJdbcUrl("jdbc:mysql://localhost:3306/book_shop");
                ds.setUser("root");
                ds.setPassword("root");
                ds.setInitialPoolSize(5);
                ds.setMinPoolSize(5);
                ds.setMaxPoolSize(20);
            } catch (PropertyVetoException e) {
            	e.printStackTrace();
            }*/
        }
        public static Connection getConnection(){
            try {
            	return ds.getConnection();
            } catch (SQLException e) {
            	e.printStackTrace();
            }
            return null;
        }
    }
    

3. Web

网络模式:

  1. C/S 模式(Client/Server,客户端/服务器模式)
  2. B/S 模式(Browser/Server,浏览器/服务器模式)

URL 简介

URL(uniform resource locators):统一资源定位符。web 上的每一个资源都有唯一的地址,采用的就是 url 格式

协议://域名:端口号/路径/请求资源的名字
http://www.weibo.com:80/images/canglaoshi.jpg

web 基本概述

  1. web 介绍

    web 是建立在 Internet 上的一种服务(service)。

    web 是 Internet 上多种服务的一种Internet 上服务包括 email流媒体ftp等。

    web 作为一种服务,web 定义了web 客户端(浏览器)和web 服务器(tomcat)是如何通过 Internet 通信

    web 客户端和服务器是通过“请求/响应”模型(request/response model)与 Internet 进行通信

    “请求/响应”模型http协议

    1. 客户端发送给服务器的请求叫 http 请求request 对象
    2. 服务器返回给客户端的响应叫 http 响应response 对象

    请求一个特殊的资源,如 web 页面,服务器如果有就响应该请求。

    所以,进行 web 开发,就是频繁的处理 http 请求和 http 响应

  2. Tomcat(web 服务器)

    tomcat 是 Apache 组织的 Jakarta 项目中的一个重要子项目,是 Sun 公司推荐运行 servletjsp 的容器(引擎),其源代码完全公开。

    作用管理和运行 web 应用程序

    tomcat 下载地址: http://www.apache.org/

1. JSP

1. JSP 概述

JSP(Java Server Pages):在传统的 html 文件中加入 java 程序片段JSP 标记,就构成了 JSP 网页

JSP 注释<!--注释--><%--注释--%>

2. JSP 元素

  1. 脚本元素

    1. 声明<%! %>,在 JSP 中声明合法的变量和方法

      <%!
          int i=10;  //JSP 的声明片段,注意:不建议在 JSP 页面中定义变量或者方法
          String name = "小黑";
          public void show(){
          	System.out.println("你好");
          }
      %>
      
    2. 表达式<%=表达式 %>,计算该表达式,将其结果转换成字符串插入到输出中

      <%=1+1 %>
      
    3. 脚本:位于**<%和%>**之间的代码,它是合法的 java 代码

      <% java 代码,一行或者多行,可以放入任何的 java 代码 %>
      
  2. 指令元素:<%@ %>

    1. page 指令:是与 JSP 容器(指服务器,这里用的是 Tomcat)的沟通方式

      <%@ page
          language="java" 			 // JSP 支持的语言
          import="引入的类库" 			  // 是唯一可以使用多次的属性
          contentType="text/html;charset=UTF-8"
          session="true/false"
          isThreadSafe="true/false" 	 // 是否线程安全
          errorPage="错误页面的路径" 	 // 指定错误页面,本页面发生错误时要跳转到的页面
          isErrorPage="true/false"    // 是否是错误页面,如果为true,发生错误时,跳转到这个页面
          pageEncoding="UTF-8"
      %>
      
    2. include 指令:是在 JSP 页面被转换成 Servlet时 将指定的页面包含进来,

      这种特性允许创建指定的导航栏联系人信息

      <%@ include file="页面路径" %>
      
    3. taglib 指令:用于导入标签库

      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/core"%>
      
  3. 动作元素

    1. include 动作

      <jsp:include page="被包含文件的路径"></jsp:include>
      

      include静态包含动态包含的区别:

      区别静态包含(include 指令)动态包含(include 动作)
      包含时机在 JSP 转换时包含文件用户请求时包含文件
      包含内容只能包含静态资源可包含动态资源
      执行过程把 file 属性设置的文件先包含进来,
      一起执行转换和编译工作,后形成一个文件
      各自编译后形成多个文件
      执行速度相对较快,灵活性较差速度相对较慢,灵活性较高
    2. param:传递参数

      该动作元素不能单独使用,动态包含,可以传参(静态包含不具有这个功能)

      <jsp:include page="被包含文件的路径" flush="true">
          <jsp:param name="username" value="tom"/><!--将参数传入被包含的文件中,默认request-->
      </jsp:include>
      
    3. forward:转发用户请求

      服务器的跳转(转发带请求数据,URL 地址不变)

      把用户提交的数据(在 request 对象中)原封不动的转发给其他页面,实际上传递了 request 对象。

      <jsp:forward page="转发的页面"></jsp:forward>
      
    4. useBean、setProperty、getProperty

      创建一个 Bean 实例并指定他的名字和作用范围。

      		<!--相当于Person p = new Person()-->
      <jsp:useBean id="p" class="com.mypackage.Person"></jsp:useBean>
      <jsp:setProperty name="p" propety="name" value="张三"/><!--p.setName("张三")-->
      <jsp:getProperty name="p" propety="name"/><!--相当于p.getName()-->
      

      javaBean:简单说,就是一个 java 类,可以重复使用

      它必须遵循以下规定:

      1. 是一个公共类,具有一个共有的无参构造器
      2. 每个属性必须定义一组 **getXXX()和 setXXX()**方法以便读取和存储它的属性值

3. JSP 九大内置对象

JSP 中一共预先定义了 9 个对象,可以不加声明和创建就可以在 JSP 页面中使用

request、response、session、application、page、pageContext、out、config、exception

  1. request 对象

    request说明
    类型javax.servlet.http.HttpServletRequest
    描述该对封装了客户端的请求信息,主要用于接受通过 http 协议传送到服务器的数据
    作用域为一次请求

    重要方法:

    request.getParameter(key); 						// 获取提交表单的数据
    request.getParameterValues(key); 				// 获取提交表单的一组数据
    request.setAttribute(key,object); 				// 设置请求对象 request 的属性
    request.getAttribute(key); 						// 获取请求对象 request 的属性
    request.setCharacterEncoding("UTF-8"); 			// 对请求数据重新编码
    request.getRequestDispatcher("test.jsp").forward(request,response);// 转发
    
  2. response 对象

    response说明
    类型javax.servlet.http.HttpServletResponse
    描述它封装了 JSP 的响应信息,发送到客户端以响应客户端的请求
    作用域page(只在 JSP 页面有效,一个页面一个响应)

    重要方法:

    response.sendRedirect("test.jsp"); 					// 重定向
    response.setCharacterEncoding("UTF-8"); 			// 设置响应的编码
    response.setContentType("text/html;charset=UTF-8"); // 设置内容类型
    
  3. session 对象

    session说明
    类型javax.servlet.http.HttpSession
    描述表示一个会话,用来保存用户信息,以便跟踪每个用户的状态
    作用域session

    重要方法:

    session.getId();							// 取得 session 的 id 号
    session.isNew();							// 判断 session 是否是新建的
    session.setAttribute(key,object);			// 往当前会话中设置一个属性
    session.getAttribute(key);					// 获取当前会话中的一个属性
    session.removeAttribute(key);				// 删除当前会话中的一个属性
    session.setMaxInactiveInterval(1000*60*60);	// 设置当前会话的失效时间(单位是毫秒)
    session.invalidate();		// 重新初始化session,删除session中的数据,在退出系统的时候使用
    

    session 会话:是指在一段时间内客户端和服务器之间一连串的相互交互的过程,同一个客户端与服务器在特定时间内的多个请求构成一个 session 会话。

    1. session 的过期时间默认是半个小时(半个小时是指,在这个时间内客户端与服务器没有交互的情况下,才会失效)。
    2. session 对象内部使用 Map 类来保存数据,因此保存数据的格式为 key/value
  4. application 对象

    application说明
    类型javax.servlet.ServletContext
    描述可将信息保存在服务器
    作用域application

    application 对象可将信息保存在服务器中,直到服务器关闭,否则 application 对象中保存的信息会在整个应用中都有效。与 session 对象相比,application 对象生命周期更长,类似于系统的“全局变量“

  5. page 对象

    page说明
    描述page 对象代表 JSP 本身,只有在 JSP 页面内才是合法的, J
    page 对象本质上包含当前 Servlet 接口引用的变量,类似于Java 编程中的 this 指针
    作用域page
  6. pageContext 对象

    pageContext说明
    类型javax.servlet.jsp.PageContext
    描述本 JSP 的页面上下文
    作用域page
    作用可以取得任何范围的参数
    可以获取 JSP 页面的 out、request、reponse、session、application对象
    创建和初始化pageContext 对象的创建和初始化都是由容器(这里指 Tomcat)来完成的
    <%
        pageContext.getRequest(); //通过 pageContext 上下文对象获取当前页面的其他内置对象
        pageContext.getResponse();
        pageContext.getSession();
    	pageContext.getServletConfig(); // 获得Config对象
    %>
    
  7. out 对象

    out说明
    类型javax.servlet.jsp.JspWriter
    描述主要用来向客户端输出数据
    作用域page(每一个单独的页面都有一个 out 对象)
    重要方法print()、println()、write()
  8. config 对象

    config说明
    类型javax.servlet.**ServletConfig **
    作用取得服务器配置信息
    作用域page

    通过 pageConext 对象的 **getServletConfig()**方法可以获取一个 config对象。当一个 Servlet 初始化时,容器把某些信息通过 config 对象传递给这个 Servlet。可以在 web.xml 文件中为应用程序环境中的 Servlet 程序JSP 页面提供初始化参数

  9. exception 对象

    exception说明
    作用显示异常信息
    作用域page

    注意:

    1. 只在包含 isErrorPage=“true” 的页面中才可被使用,在一般的 JSP 页面中使用该对象将无法编译
    2. excepation 对象和 Java 的所有对象一样,都具有系统提供的继承结构。exception 对象几乎定义了所有异常情况。在 Java 程序中,可以使用 try/catch 关键字来处理异常情况; 如果在 JSP 页面中出现没有捕获到的异常,就会生成 exception 对象,并把 exception 对象传送到在 page 指令中设定的错误页面中,然后在错误页面中处理相应的exception 对象

4. JSP 四大作用域

JSP 作用域范围从小到大顺序为:page > request > session > application

  1. page

    page 作用域仅限于当前页面,类似于 java 的 this 对象,离开当前 JSP 页面(无论是 redirect 还是 forward),则 pageContext 中的所有属性值就会丢失

    如果把变量放到 pageContext 里,则它的作用域是 page,它的有效范围只在当前 jsp 页面里。
    
    从把变量放到 pageContext开始,到 jsp 页面结束,都可以使用这个变量
    
  2. request

    request 作用域同一个请求之内,在页面跳转时,

    1. 如果通过 forward 方式跳转,则 forward 目标页面仍然可以拿到 request 中的属性值。
    2. 如果通过 redirect 方式页面跳转,由于 redirect 相当于重新发出的请求,request 中属性值会丢失
    如果把变量放到 request 里,则它的作用域是 request,它的有效范围是当前请求周期。
    
    所谓请求周期,就是指从 http请求发起,到服务器处理结束,返回响应的整个过程。在这个过程中可能使用 forward 的方式跳转了多个 jsp 页面,在这些页面里你都可以使用这个变量
    
  3. session

    session 的作用域是在一个会话的生命周期内,会话失效,则 session 中的数据也随之丢失

    如果把变量放到 session 里,则它的作用域是 session,它的有效范围是当前会话。
    
    所谓当前会话,就是指从用户打开浏览器开始,到用户关闭浏览器这中间的过程。这个过程可能包含多个请求响应。也就是说,只要用户不关浏览器,服务器就有办法知道这些请求是一个人发起的,整个过程被称为一个会话(session),而放到会话中的变量
    
  4. application

    application 作用域是最大的,它的有效范围是整个应用,只要应用不结束,则 application 对象中的数据就一直存在,并且为所有会话所共享

    如果把变量放到 application 里,则它的作用域是 application,它的有效范围是整个应用。
    
    整个应用是指从应用启动,到应用结束。我们没有说“从服务器启动,到服务器关闭”是因为一个服务器可能部署多个应用,当然你关闭了服务器,就会把上面所有的应用都关闭了。Application 作用域里的变量,它们的存活时间是最长的,如果不进行手工删除,它们就一直可以使用。
    与上述三个不同的是,application 里的变量可以被所有用户共用。如果用户甲的操作 修改了 application 中的变量,用户乙访问时得到的是修改后的值
    

5. 请求方式 GET/POST

GET 请求和 POST 请求的区别:

  1. 客服端的区别

    区别GETPOST
    是否显示提交的信息都显示在地址栏中提交的信息不显示地址栏中
    封装位置将信息封装到了请求消息的请求行将信息封装到了请求体
    安全性对于敏感的数据信息不安全对于敏感信息安全
    数据体积对于大数据不行,因为地址栏存储体积有限,大小不超过4KB可以提交大体积数据
    速度相对较快相对较慢
  2. 服务端的区别(乱码处理方式

    如果提交中文到 tomcat 服务器会出现乱码,服务器默认会用 iso-8859-1 进行解码,解决方法:

    1. get 提交和 post 提交都有效

      1. 通过 iso-8859-1 进行编码,再用指定的中文码表解码即可

        new String(queryValue.getBytes("ISO8859-1"),"UTF-8"); // 先编码,再解码
        
      2. URLDecoder.decode(user,“utf-8”) 解码

      3. 修改 tomcat 的配置文件 server.xml:加上 URIEncoding="UTF-8"

        <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/>
        
    2. 对于 post 提交,还有另一种解决办法,就是直接使用服务端一个 request 对象

      request 对象的 setCharacterEncoding 方法直接设置指定的中文码表就可以将中文数据解析出来。这个方法只对请求体中的数据进行解码

      // 在获取数据之前,设置请求的字符编码集
      request.setCharacterEncoding("UTF-8");
      

和服务端交互的三种方式

位置请求方式
地址栏输入 url 地址get
超链接get
表单get 和 post

数据校验

客服端校验服务端校验
是否校验如果在客户端进行增强型校验
(只要有一个组件内容是错误,是无法继续提交的,
只有全对才可以提交)
服务端收到数据后,也还需要校验
校验目的提高用户的上网体验效果,减轻服务器端的压力为了安全性

5. 转发与重定向

转发(forward)与重定向( sendRedirect)区别:

  1. 浏览器地址栏 URL 是否改变

    1. forward 转发是在容器内部实现的同一个 Web 应用程序的转发,所以 forward 方法只能转发到同一个 Web 应用程序中的一个资源,转发后浏览器地址栏 URL 不变
    2. sendRedirect 方法可以重定向到任何 URL, 是修改 http 头来实现的,URL 没什么限制,重定向后浏览器地址栏 URL 改变
  2. 是否能传递 Request 对象

    1. forward原始的 HTTP 请求对象(request)从一个 servlet 实例传递到另一个实例,能传递 Request
    2. sendRedirect 方式两者不是同一个 application,不能传递 Request 对象
  3. 参数的传递方式

    1. forward 的 form 参数跟着传递,所以在转发后的实例中可以取得 HTTP 请求的参数

    2. sendRedirect 只能通过链接传递参数

      response.sendRedirect(“login.jsp?param1=a&param2=b”)
      
  4. 能否处理相对 URL

    1. forward 转发不能处理相对 URL

    2. sendRedirect 能够处理相对 URL,把它们自动转换成绝对 URL

      1. 如果地址是相对的,没有一个‘/’,那么 Web 容器就认为它是相对于当前的请求 URL 的

        比如 response.sendRedirect("login.jsp"),则会从当前servlet的 URL 路径下找login.jsp
        当前servlet的 URL 路径:http://10.1.18.8:8080/dms/servlet/Servlet 
        	重定向后的 URL 路径: http://10.1.18.8:8080/dms/servlet/login.jsp
        
      2. 如果地址是绝对的,有一个‘/’,那么 Web 容器则会从当前应用路径下查找 url

        比如 response.sendRedirect("/login.jsp"),则会从当前应用路径下查找login.jsp
            当前应用路径为:http://10.1.18.8:8081/
         重定向后的URL路径:http://10.1.18.8:8081/login.jsp
        

6. Cookie

6.1 Cookie 简介

cookie:是一种 web 服务器通过浏览器访问者的硬盘存储信息的手段。

cookie 的目的:就是为了给用户带来方便,为网站带来增值

  • 浏览器一般只允许存放 300 个 cookie每个站点最多存放 20 个 cookie,每个 cookie 的大小限制为 4KB,因此,cookie 不会塞满你的硬盘

cookie 带来的好处

  1. cookie 能使站点跟踪特定访问者的访问次数最后访问时间等。
  2. cookie 能告诉在线广告商广告被点击的次数,从而可以更精确的投放广告
  3. cookie 的期限未到时,cookie 能使用户不键入用户名和密码的情况下自动登录网站
  4. cookie 可以帮助站点实现很多个性化服务

cookie 涉及到很多你的隐私,因为可以通过 cookie 跟踪你访问过的网站。 浏览器也可以禁用 cookie 。所以说,不能用 cookie 来完成核心业务,只能做一些锦上添花的功能,比如说自动登录,有没有都可以

6.2 Cookie 的使用

Cookie 是 Web 服务器发给浏览器保存在客户端用户磁盘上一段文本。Cookie 允许一个 Web 站点在用户电脑上保存信息并且随后再取回它。一个 Web 站点可能会为每一个访问者产生一个唯一的 ID,然后以 Cookie 文件的形式保存在每个用户的机器上。

  1. Cookie 对象的创建

    Cookie cookie = new Cookie("username","john"); //Cookie 名字、Cookie 值
    
  2. Cookie 常用方法

    cookie.getMaxAge();
    cookie.getName();
    cookie.getValue();
    cookie.setValue(String newValue);
    cookie.setMaxAge(secondsTime);// 设置 Cookie 对象的有效时间,
    							  // secondsTime 表示cookie的最大生存时间
    							  // 负值:关闭浏览器时,清除该cookie
    							  //  0 :立即清除该cookie
    request.getCookies();		// 获得所有客户端传来的 Cookie 对象以数组的形式排列
    response.addCookie(cookie);	// 将封装好的 Cookie 对象加入到响应中,传送到客户端
    
  3. Cookie 分类

    1. session cookie: 临时的 cookie

      随着浏览器的关闭而消失,通过 sessionid 建立起与 session 会话之间的联系,一旦禁用 cookie,session cookie 就不能用了。

    2. persistent cookie:持续性的 cookie,也就是自己创建的 Cookie 对象

Cookie 对象的典型应用是用来统计网站的访问人数。由于代理服务器缓存等的使用,唯一能帮助网站精确统计来访人数的方法就是为每个访问者建立一个唯一 ID。使用 Cookie,网站可以完成以下工作:

  1. 测定多少人访问过;

  2. 测定访问者有多少是新用户(即第一次来访),多少是老用户;

  3. 测定一个用户多久访问一次网站;

  4. 测定一个用户访问网站的次数;

    当一个用户第一次访问时,网站在数据库中建立一个新的 ID,并把 ID 通过 Cookie 传送给用户。用户再次来访时,网站把该用户 ID 对应的计数器加 1,得到用户的来访次数

2. EL 表达式

1. EL 简介

EL(Expression Language):它是一种数据访问语言

它是一种简单的语言,基于可用的命名空间PageContext 属性,即页面上下文)、嵌套属性对集合、操作符(算术型、关系型和逻辑型)的访问符映射到 Java 类中静态方法可扩展函数以及一组隐式对象(就是内置对象的意思)。

EL 表达式简述
简介它是一种数据访问语言,不是编程语言,甚至不是脚本编制语言
目的为了使 JSP 写起来更加简单
作用主要用于查找作用域中的数据,然后对它们执行简单操作
说明1、JSP2.0EL 表达式添加为一种脚本编制元素
即是:在 JSP2.0 之后, JSP 可以直接使用 EL,Tomcat5 之后也支持 EL 表达式。

2、EL 表达式通常与 JSTL 标记一起使用,
能用简单而又方便的符号来表示复杂的行为,来实现在 jsp 中不出现 java 代码段

2. EL 表达式

1. EL 语法
EL 语法:${表达式或者变量}
2. EL 运算符
类型运算符
算术运算符+、-、*、/、%
关系运算符==、!=、>、>=、<、<=
逻辑运算符&&、||、!
验证运算符empty:验证值是否为空(null)
特殊运算符"."运算符与"[]"运算符
3. EL 隐含对象(11个)
隐含对象类型说明
pageContextjavax.servlet.ServletContext表示此JSP的pageContext
pageScopejava.util.Map获得page范围的属性名所对应的值
requestScopejava.util.Map获得request范围的属性名所对应的值
sessionScopejava.util.Map获得session范围的属性名所对应的值
applicationScopejava.util.Map获得application范围的属性名所对应的值
paramjava.util.Map如同request.getParameter(String name)
paramValuesjava.util.Map如同request.getParameters(String name)
headerjava.util.Map如同request.getHeader(String name)
headerValuesjava.util.Map如同request.getHeaders(String name)
cookiejava.util.Map如同request.getCookies()
initParamjava.util.Map如同application.getInitParameter(String name)

3. JSTL 标签库

1. JSTL 简介

JSTL 的全称为 JavaServer Pages Standard Tag Library,中文名称为 JSP 标准标签函数库

JSTL 是由 JCP(Java Community Process)所指定的标准规格,它主要提供给 Java Web 开发人员一个标准通用的标签函数库。

web 程序开发人员能够利用 JSTLEL 来开发 web 程序,取代传统直接在页面上嵌入 java 程序代码的做法,以提高程序可读性,维护性和方便性。

导包:使用 JSTL,则必须引用 jstl.jarstandard.jar 两个包。

2. JSTL 核心标签库

JSTL 所提供的标签函数库主要分为五大类,最常用的就是核心标签库,其他四类基本上不用。

核心标签库包含了 web 应用的常见功能,比如:循环表达式赋值基本输入输出等。

  1. 引入标签库

    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!--uri:命名空间-->
    
  2. 常见标签

    1. 表达式操作

      <c:out>		// 主要用来显示数据的内容,就像是<%=表达式%>一样
      <c:set>		// 主要用来将变量存储至 JSP 范围中或是 JavaBean 的属性中
      <c:remove>	// 主要用来移出变量
      <c:catch>	// 主要用来处理产生错误的异常状况,并将错误信息存储起来
      
    2. 流程控制标签

      <c:if> 		// 用途和我们在一般程序中用的 if 一样(没有 else)
      <c:choose>	// 本身只当做<c:when>和<c:otherwise>的父标签,相当于 java 里的 case 
          <c:when>
          <c:otherwise>
      </c:choose>
      
    3. 迭代操作标签

      <c:forEach>		// 为循环控制,它可以将集合(Collection)中的成员循序浏览一遍
          			// 运作方式为当条件符合时,就会持续重复执行<c:forEach>的本体内容
      <c:forTokens>	// 用来浏览一字符串中所有的成员,其成员是由定义符号(delimiters)所分割的
      
      <c:forEach var="stu" items="${requestScope.page.list}" varStatus="st">
          <tr>
          	<td>${st.count}</td>
          </tr>
      </c:forEach>
      

4. Servlet

1. Servlet 概述

Servlet 是一种独立于平台和协议服务端 Java 应用程序,可以生成动态的 Web 页面

Servlet 是位于 Web 服务器内部服务端 Java 应用程序Servlet 由 Web 服务器进行加载,该 Web 服务器(如 Tomcat)必须包含支持 Servlet 的 Java 虚拟机。(因为 Tomcat 就是运行 Java 应用程序的一个容器

Servlet 的框架组成

Servlet 框架是由两个 Java 包组成:Javax.servletJavax.servlet.http

Servlet 的框架核心Javax.servlet.Servlet 接口

所有的 Servlet 都必须实现这一接口。通常我们也把实现了 Servlet 接口的 java 程序,称之为 Servlet

创建 Servlet 的一般步骤

  1. 新建一个 Servlet(要继承 httpservlet 类-----Servlet 的子类

  2. web.xml 配置

  3. 配置 Servlet 访问路径

    <url-pattern>/*</url-pattern> 
    <url-pattern>/</url-pattern> 
    
    * :可以匹配任意的字符,所以此时可以用任意的 URL 去访问这个Servlet
    / :为当前 Web应用程序的缺省Servlet。
    凡是在 web.xml 文件中找不到匹配的<servlet-mapping>元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其他 Servlet 都不处理的访问请求
    

2. Servlet 的运行方式

Servlet 没有 main 方法,受控于另一个 Java 应用,这个 Java 应用称为 Servlet 容器(Tomcat )

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nbcLuY7j-1648561558495)(link-picture\image-20220115213107312.png)]

Servlet 默认是以多线程模式执行的。

真正写代码的地方就是 Servlet。 http 引擎封装转发请求数据

3. Servlet 的生命周期

Servlet 的生命周期定义了一个 Servlet 如何被加载初始化接受请求响应请求提供服务

代表 Servlet 的生命周期的三个方法

  1. init 方法,负责初始化 Servlet 对象
  2. service 方法,负责响应客户的请求(调用 doGet 或 doPost 等方法)
  3. destroy 方法,当 Servlet 对象退出生命周期时,负责释放占用的资源

Servlet 的运行过程

Servlet 程序是由 web 服务器调用,web 服务器收到客户端的 Servlet 访问请求后:

  1. Web 服务器首先检查是否已经装载并创建了该 Servlet 的实例对象。如果是,则直接执行第4步,否则,执行第2步。
  2. 装载并创建该 Servlet 的一个实例对象。
  3. 调用 Servlet 实例对象的 **init()**方法。
  4. 创建一个用于封装 HTTP 请求消息的 HttpServletRequest 对象和一个代表 HTTP 响应消息的 HttpServletResponse 对象,然后调用 Servlet 的 service()方法并将请求和响应对象作为参数传递进去,service 方法再根据请求方式分别调用 doXXX 方法
  5. Web 应用程序被停止或重新启动之前,Servlet 引擎卸载 Servlet,并在卸载之前调用 Servlet 的 **destroy()**方法。

注意:

  1. 在 Servlet 的整个生命周期内,Servlet 的 init 方法只被调用一次
  2. 而对一个 Servlet 的每次访问请求都导致 Servlet 引擎调用一次 Servlet 的 service 方法。
  3. 对于每次访问请求,Servlet 引擎都会创建一个新的 HttpServletRequest 请求对象和一个新的HttpServletResponse 响应对象,然后将这两个对象作为参数传递给它调用的 Servlet 的 service()方法,service 方法再根据请求方式分别调用 doXXX 方法。

4. Servlet 线程安全

哪些情况下需要考虑线程安全问题???

  1. 访问类的成员变量时
  2. 访问共享资源

多个客户端同时访问一个 Servlet 实例的时候怎么办呢???

Servlet 引擎采用多线程模式运行,它为并发的每个访问请求都是用一个独立的线程来进行响应,但带来了线程安全问题(因为多个线程访问一个 Servlet 实例),就会导致 Servlet 是线程不安全的

两个方法解决这个问题:

  1. SingleThreadMode 接口(已经被废了,虽然解决了安全问题,但带来了很大的性能问题),Servlet 实现了 SingleThreadMode 接口,那么 Servlet 引擎将以单线程模式来调用 Servlet 的 service 方法。

    对于实现了 SingleThreadMode 接口的 Servlet,Servlet 引擎仍然支持对该 Servlet 的多线程并发访问,其采用的方式是产生多个 Servlet 实例对象,并发的每个线程分别调用独立的一个 Servlet 实例对象。

  2. 使用同步代码块(一般情况下不需要去同步,当多线程同时访问一个数据或者临界区时,才需要同步)

在 servlet 中定义成员变量,此时需要考虑线程安全问题,但是一般不在 servlet 中定义成员变量,所以平常写 servlet 的时候不用去考虑线程安全问题

5. 开发架构

JavaBean 中 DAO 设计模式介绍

1. 信息系统的开发架构

客户层—显示层—业务层—数据层—数据库

  1. 客户层:客户层就是客户端,简单的来说就是浏览器
  2. 显示层JSP/Servlet,用于给浏览器显示。
  3. 业务层(service):对于数据层的原子操作进行整合
  4. 数据层(dao):对于数据库进行的原子操作,增加、删除等;

2. DAO 设计模式介绍

DAO:Data Access Object 数据访问对象

DAO 应用在数据层,用于访问数据库,对数据库进行操作的类。

image-20220115222421171

DAO 设计模式带来的好处:

  1. 透明化

    商业对象可以在完全不知道数据源如何具体实现的情况下来使用数据源。访问数据源是透明的,因为实现细节已经被隐藏进了 DAO。

  2. 迁移简单化
    DAO 层的出现,使得应用程序向不同的数据库实现进行迁移变的容易。商业对象可以对底层数据实现一无所知。这样,迁移只涉及到了对 DAO 层的修改。另外,如果使用工厂策略,则使为每一种底层数据实现提供一个具体的工厂实现成为可能。在这种情况下, 迁移到一种不同的数据实现,其实就相当于为这个应用程序再提供一个新的工厂实现。

  3. 减少在商业对象中的编程难度

    由于 DAO 管理着所有的数据访问细节,因而大大简化了在商业对象和其他使用 DAO 的数据客户端里的代码。所有的实现细节相关的代码比如(SQL 语句)都包含在 DAO 而不在商业对象中。这样使得代码变的更加健壮而且大大提高了开发效率。

  4. 将所有的数据访问都单独集中到一层中去

    因为所有的数据访问操作现在都已经被 DAO 所代理,所以这个单独的数据访问层可以被看作可以是将数据访问实现和其余应用程序相互隔离的一层。这样的集中,使得应用程序可以更加容易的来维护和管理。(各层之间的耦合性(联系的紧密程度)降低了,那么当修改某一层时,对其它层的影响也就降低了,所以可以使编写的程序更易于维护)

3. DAO 设计模式的结构

DAO 设计模式一般分为几个类:

  1. DAO 接口:用于声明对于数据库的操作。
  2. DAOImpl:必须实现 DAO 接口,真实实现 DAO 接口的函数,但是不包括数据库的打开和关闭。
  3. DAOFactory:(可选)工厂类,创建 DAO 对象。
public interface BookDAO{
    public boolean insert(Book book) throws Exception ;			// 增加操作
    public boolean update(Book book) throws Exception ;			// 修改操作
    public boolean delete(String id) throws Exception ;			// 删除操作
    public Book queryById(String id) throws Exception ;			// 按 ID 查询操作
    public List queryAll() throws Exception ;					// 查询全部
    public List queryByLike(String condition) throws Exception ;// 模糊查询
}

6.过滤器

1. 过滤器概述

Servlet 过滤是在 2001 年由 Servlet API2.3 版本中提出的。过滤是一种强大的技术,Servlet 开发人员可以用它来生成一系列的 java 类,以一定的顺序执行,响应客户端的请求

开发人员从创建一个或多个实现 javax.servlet.Filter 接口的 java 类开始,这些类可以在 Servlet 请求处理之前采取一些动作,换句话说,它先于与之相关的 servlet 和 JSP 页面运行在服务器上。在请求发送到其目标之前创建一系列的动作(包括完全阻塞请求)。

Filter 也称之为过滤器,对 web 服务器管理的所有 web 资源(如 Jsp,Servlet,静态图片文件或静态 html 文件等)进行拦截,从而实现一些特殊的功能。如实现 URL 级别的权限访问控制过滤敏感词汇压缩响应信息等一些高级功能。通过 Filter 技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。

过滤通俗的说:就是把要的东西留下,把不要的东西过滤掉,挡住。

2. 过滤器的基本原理

过滤器可以对客户的请求进行处理。处理完成后,它会交给下一个过滤器处理,这样,客户的请求在过滤链里逐个处理,直到请求发送到目标(Servlet 或者 JSP 页面或者 HTML 页面等)为止。

Filter 接口中有一个 doFilter 方法,当编写好 Filter,并配置对哪个 web 资源进行拦截后,WEB 服务器每次在调用 web资源的 service 方法之前,都会先调用一下 filter 的 doFilter 方法,因此,在该方法内编写代码可达到如下目的:

  1. 调用目标资源之前,让一段代码执行。
  2. 是否调用目标资源(即是否让用户访问 web 资源)。
  3. 调用目标资源之后,让一段代码执行。

web 服务器在调用 doFilter 方法时,会传递一个 filterChain 对象进来,filterChain 对象是 filter 接口中最重要的一个对象,它也提供了一个 doFilter 方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则 web 服务器就会调用 web资源的 service 方法,即 web 资源就会被访问,否则 web 资源不会被访问。

Filter 链

在一个 web 应用中,可以开发编写多个 Filter,这些 Filter 组合起来称之为一个 Filter 链。

web 服务器根据 Filter 在 web.xml 文件中的注册顺序,决定先调用哪个 Filter,当第一个 Filter 的 doFilter 方法被调用时,web服务器会创建一个代表 Filter 链的 FilterChain 对象传递给该方法。在 doFilter 方法中,开发人员如果调用了 FilterChain 对象的doFilter 方法,则 web 服务器会检查 FilterChain 对象中是否还有 filter,如果有,则调用第 2 个 filter,如果没有,则调用目标资源

3. Filter 的生命周期

Filter 的初始化方法 init 和销毁方法 destroy 在 Filter 的生命周期中仅执行一次

  1. Filter 的创建
    Filter 的创建和销毁由 WEB 服务器负责。web 应用程序启动时,web 服务器将创建 Filter 的实例对象,并调用其 init 方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作,filter 对象只会创建一次,init 方法也只会执行一次。通过 init 方法的参数,可获得代表当前 filter 配置信息的 FilterConfig 对象。

    FilterConfig 对象

    用户在配置 filter 时,可以使用<init-param>为 filter 配置一些初始化参数,当 web 容器实例化 Filter 对象,调用其 init 方法时,会把封装了 filter 初始化参数的 filterConfig 对象传递进来。因此在编写 filter 时,通过 filterConfig 对象的方法,就可获得配置的初始参数:

    String getFilterName();					// 得到 filter 的名称
    String getInitParameter(String name); 	// 返回指定名称的初始化参数的值。如果不存在返回null
    Enumeration getInitParameterNames();	// 返回过滤器的所有初始化参数的名字的枚举集合
    ServletContext getServletContext();		// 返回 Servlet 上下文对象的引用
    
  2. Filter 的销毁
    Web 容器调用 destroy 方法销毁 Filter。destroy 方法在 Filter 的生命周期中仅执行一次。在 destroy 方法中,可以释放过滤器使用的资源。

3. Filter 开发步骤

  1. 编写 java 类实现 Filter 接口,并覆盖其 doFilter 方法。

    public class FilterDemo implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("----过滤器初始化----");
            String filterName = filterConfig.getFilterName(); //得到过滤器的名字
            String initParam1 = filterConfig.getInitParameter("name");//得到初始化参数
            String initParam2 = filterConfig.getInitParameter("like");
            //返回过滤器的所有初始化参数的名字的枚举集合。
            Enumeration<String>initParameterNames=filterConfig.getInitParameterNames();
            while (initParameterNames.hasMoreElements()) {
                String paramName = (String) initParameterNames.nextElement();
                System.out.println(paramName);
            }
        }
        @Override
        public void doFilter(ServletRequest request, 
                             ServletResponse response, 
                             FilterChain chain) throws IOException, ServletException {
            request.setCharacterEncoding("UTF-8"); // 设置request和response的字符集
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/html;charset=UTF-8"); // 设置响应格式
            System.out.println("FilterDemo01 执行前!!!");
            chain.doFilter(request, response); 					//让目标资源执行,放行
            System.out.println("FilterDemo01 执行后!!!");
        }
        @Override
        public void destroy() {
        	System.out.println("----过滤器销毁----");
        }
    }
    
  2. web.xml 文件中使用<filter><filter-mapping>元素对编写的 filter 类进行注册,并设置它所能拦截的资源

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="3.0"
                xmlns="http://java.sun.com/xml/ns/javaee"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">    
        <filter><!-- 对过滤器进行注册 -->
            <filter-name>FilterDemo</filter-name>
            <filter-class>me.gacl.web.filter.FilterDemo</filter-class>
            <init-param>
                <description>配置 FilterDemo 过滤器的初始化参数</description>
                <param-name>name</param-name>
                <param-value>gacl</param-value>
            </init-param>
            <init-param>
                <param-name>like</param-name>
                <param-value>java</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>FilterDemo</filter-name>
            <url-pattern>/*</url-pattern> <!-- “/*”表示拦截所有的请求 -->
            <dispatcher>request</dispatcher><!-- 拦截直接访问页面请求 -->
    		<dispatcher>forward</dispatcher><!-- 拦截转发请求 -->
        </filter-mapping>
    </web-app>
    

    Filter 拦截请求方式

    1. 拦截请求路径:

      <url-pattern>  	设置 filter 所拦截的请求路径(过滤器关联的 URL 样式)
      
    2. 拦截 Servlet 名称

      <servlet-name> 	指定过滤器所拦截的 Servlet 名称
      
    3. 拦截资源被调用的方式

      <dispatcher>
          指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是request,include,forward,error,
          默认request。
          用户可以设置多个 <dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截
      

      <dispatcher> 子元素可以设置的值及其意义:

      1. request:当用户直接访问页面时,web 容器将会调用过滤器。如果目标资源是通过requestdispatcher 的 include()或 forward()方法访问时,那么该过滤器就不会被调用。
      2. include:如果目标资源是通过 **requestdispatcher 的 include()**方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
      3. forward:如果目标资源是通过 **requestdispatcher 的 forward()**方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
      4. error:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用

7. 监听器

1. 监听器概述

监听器:监视,窃听,监听某一个事件的发生,就会触发某一个功能或者方法执行某个操作。

Servlet 监听器用于监听一些重要事件的发生,监听器对象可以在事件发生前发生后做一些必要的处理,激发一些操作,如监听在线用户数量,当增加一个 HttpSession,给在线人数增加 1

监听器实现步骤:

  1. 编写监听器需要实现相应的接口。
  2. 监听器需要在 web.xml 中配置。
  3. 在不修改现有系统的基础上,增加 web 应用程序生命周期事件的跟踪

2. 监听器类别

Servlet API2.3 以后提供了以下监听器接口:

  1. ServletContextListener应用上下文生命周期监听器

    作用域 application,对应内置对象 application,它监听着 ServletContext 这个对象的发生,比如说它创建了,销毁了等

  2. ServletContextAttributeListener应用上下文属性事件监听器(用这个对象的 setAttribute 往里面存属性时就会触发这个监听器)

  3. ServletResquestListener请求生命周期监听器

  4. ServletResquestAttributeListener请求属性事件监听器

  5. HttpSessionListener会话生命周期监听器

  6. HttpSessionActivationListener会话激活(类似于线程的唤醒)和钝化(类似于线程的休眠)事件监听器

  7. HttpSessionAttributeListener会话属性事件监听器

  8. HttpSessionBindingListener会话值绑定事件监听器

常用的监听器的接口

  1. HttpSessionListener:监听 HttpSession 的操作(看 javaee 手册)

    重要方法:

    void sessionCreated(HttpSessionEvent se);	// 当创建一个 session 时 
    void sessionDestroyed(HttpSessionEvent se);	// 当销毁一个 session 时 
    
  2. HttpSessionAttributeListener:监听 HttpSession 中的属性的操作

    重要方法:

    void attributeAdded(HttpSessionBindingEvent se);		// 当在 session 中增加一个属性时 
    void attributeRemoved(HttpSessionBindingEvent se);	// 当在 session 中删除一个属性时 
    void attributeReplaced(HttpSessionBindingEvent se);	// 当在 session 属性被重新设置时 
    

3. 监听网站的在线人数

  1. 创建 Servlet 监听器 OnlineListener,用于监听网站的在线人数

    public class OnlineListener implements HttpSessionListener{
        private int onlineCount; //定义一个代表在线人数的变量
        public OnlineListener(){
        	onlineCount = 0;
        }
        public void sessionCreated(HttpSessionEvent se) { //会话创建时触发
            onlineCount++; //int 自动转化 Integer
            se.getSession().getServletContext().setAttribute("online", onlineCount);
        }
        public void sessionDestroyed(HttpSessionEvent se) { //会话销毁时触发
            onlineCount--;
            se.getSession().getServletContext().setAttribute("online", onlineCount);
        }
    }
    
  2. 在 web.xml 中配置 OnlineListener 监听器

    <listener>
    	<listener-class>cn.com.bochy.listener.OnlineListener</listener-class>
    </listener>
    
  3. 创建 online.jsp 页面

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
        <head> <title>使用监听器监听在线人数</title> </head>
        <body>
        	<h2>当前在线人数:<%=application.getAttribute("online") %></h2>
        </body>
    </html>
    

8. 文件上传

1. MIME

MIME(Multipurpose Internet Mail Extensions):多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。

它是一个互联网标准扩展了电子邮件标准,使其能够支持:

  1. 非 ASCII 字符文本;
  2. 非文本格式附件(二进制、声音、图像等);
  3. 多部分(multiple parts)组成的消息体;
  4. 包含非 ASCII 字符的头信息(Header information)

Tomcat 的安装目录\conf\web.xml 中就定义了大量 MIME 类型 ,可以参考。

MIME 类型 :文本 text/javascript、json 数据、application/xml、xml 数据

2. servlet 中设定 MIME

response.setContentType 设置响应的内容类型

response.setContentType("text/html; charset=utf-8");  // 设置发送到客户端的响应的内容类型
html.setContentType("text/plain; charset=utf-8");

response.setContentType(MIME):作用是使客户端浏览器,区分不同种类的数据,并根据不同的 MIME 调用浏览器内不同的程序嵌入模块来处理相应的数据。例如 web 浏览器就是通过 MIME 类型来判断文件还是 GIF 图片。通过 MIME 类型来处理 json 字符串

response.setContentType(MIME):设置发送到客户端的响应的内容类型,此时响应还没有提交。给出的内容类型可以包括字符编码说明,例如:text/html;charset=UTF-8.

  1. 如果该方法在 getWriter()方法被调用之前设置,那么响应的字符编码将仅从给出的内容类型中设置。
  2. 如果该方法在 getWriter()方法被调用之后或者在被提交之后设置,将不会设置响应的字符编码,在使用 http 协议的情况中,该方法设置 Content-type 实体报头

一般在 Servlet 中,习惯性的会首先设置请求和响应的编码方式以及响应的内容类型

response.setContentType("text/html;charset=UTF-8");
request.setCharacterEncoding("UTF-8");

3. commons-fileupload 组件

commons-fileupload 组件:它是使用最广泛的 java 文件上传组件,大名鼎鼎的 Struts 采用这个包来处理文件上传。

使用时注意:

  1. 上传文件的表单的请求方式一定要是 post(post 安全,没有大小限制,通过流的方式进行传输)

  2. 请求的 MIME(多用途的网际邮件扩充协议)类型enctype="multipart/form-data"

    multipart/form-data 多部件表单数据,之前我们一直在表单里面传的是字符串,现在传的是文件)

核心代码

fileupload 组件里面,用 FileItem 去封装每一个数据类型,在这个组件里面会用到这几个类:

  1. DiskFileItemFactory(磁盘文件项工厂),用来创建文件项,创建之后,需要把这个文件项交给解析请求数据的ServletFileUpload 对象;
  2. 通过 ServletFileUpload 对象去解析请求数据
  3. upload.parseRequest(request);返回文件项列表
DiskFileItemFactory factory = new DiskFileItemFactory(); 	// 创建文件项工厂
ServletFileUpload upload = 
    			new ServletFileUpload(factory); // 创建解析请求数据的 ServletFileUpload 对象
List<FileItem> list = upload.parseRequest(request); // 解析请求数据,返回 FileItem 列表
FileItem item = list.get(i); 						// 获取每一个 FileItem 对象
item.isFormField(); 	// 用于判断 FileItem 类对象封装的数据是属于一个普通表单字段,
						// 还是属于一个文件表单字段,如果是普通表单字段则返回 true,否则返回 false。

4. JavaWeb 开发实战

JSP、EL、JSTL、servlet、DAO 的综合应用

1. 分页查询

  1. 定义保存分页信息的实体类

    public class Page<T> {
        public static final int DEFAULT_SIZE = 5;
        private int all; //总记录数
        private int pageSize; //页大小
        private int curPage; //当前页
        private int totalPage; //总页数
        private int prevPage; //前一页
        private int nextPage; //后一页
        private int startIndex; //起始下标
        private int endIndex; //结束下标
        private List<T> list; //当前页中的记录列表
        public Page() {
        	super();
        }
        public Page(int all, int pageSize) {
        	this(all, pageSize, 1);
        }
        public Page(int all, int pageSize, int curPage) {
            super();
            this.all = all;
            this.pageSize = pageSize;
            this.curPage = curPage;
            this.totalPage = (this.all+(this.pageSize-1)) / this.pageSize;
            this.prevPage = this.curPage >1 ? this.curPage -1:1;
            this.nextPage = 
                this.curPage < this.totalPage ? this.curPage +1 : this.totalPage;
            this.startIndex = (this.curPage-1) * this.pageSize;
            this.endIndex = this.startIndex + this.pageSize;
        }
        //getter/setter
    }
    
  2. Dao 中编写执行分页查询的方法

    public Page<Stu> queryByLikePage(String condition, int pageSize, int pageNo)
    								throws Exception {
        String sql1 = "select count(*) from stu where sname like ?";
        String sql = "select * from stu where sname like ? ";
        Object[] params = new Object[]{'%'+condition+'%'};
        // 1.查出总记录数
        Long all = (Long) JDBCUtil.executeQuerySingle(sql1, params);
        Page page = new Page(all.intValue(), pageSize, pageNo);
        // 增加分页条件
        sql = sql + 
            String.format("limit %d, %d", page.getStartIndex(), page.getPageSize());
        List<Stu> list = new ArrayList<Stu>();  // 2.执行查询并将返回的结果放入到 list 中
        ResultSet rs = JDBCUtil.executeQuery(sql, params);
        while(rs.next()){
            Stu stu = new Stu();
            stu.setSid(rs.getInt("sid"));
            //....
            list.add(stu);
        }
        JDBCUtil.closeAll();
        page.setList(list); 	// 3.将结果集合放入 page 对象中
        return page;
    }
    
  3. Servlet 中获取查询条件,当前页,页大小等信息 ,执行查询

    String queryName = request.getParameter("queryName");	// 获取页面提交的参数
    String queryValue = request.getParameter("queryValue");
    if("get".equalsIgnoreCase(request.getMethod())){ //get 方式的请求,通常是点击浏览器的链接
        if(queryValue != null){
            //链接上参数的编码是 ISO8859-1
            queryValue = new String(queryValue.getBytes("ISO8859-1"),"UTF-8");
        }
    }
    String pageSize = request.getParameter("pageSize"); // 页大小
    if(pageSize == null){
        pageSize = String.valueOf(Page.DEFAULT_SIZE);
    }
    String pageNo = request.getParameter("pageNo");		// 当前页
    if(pageNo == null){
        pageNo = "1";
    }
    //List<Stu> list = new ArrayList<Stu>();
    Page page = null;
    if("c_name".equals(queryName)){
        try {
            page=dao.queryByLikePage(queryValue,
            Integer.parseInt(pageSize),Integer.parseInt(pageNo));
        } catch (Exception e) {
        	e.printStackTrace();
        }
    }
    if(queryValue != null){
        //将经过处理的参数放入 request 中供 jsp 页面读取
    	request.setAttribute("queryValue", queryValue);
    }
    request.setAttribute("page", page);
    request.getRequestDispatcher("/list.jsp").forward(request, response);//转发到 jsp
    
  4. jsp 页面中显示查询结果集合

    <c:forEach var="stu" items="${requestScope.page.list}" varStatus="st">
        <tr>
        	<td>${st.count}</td>
        </tr>
    </c:forEach>
    
  5. jsp 页面中显示首页、上一页、下一页、尾页的等链接

    <a href="${pageContext.request.contextPath}/servlet/SearchStuServlet?
             queryName=${param.queryName}&queryValue=${requestScope.queryValue}
             &pageSize=${page.pageSize}&pageNo=1">首页</a>
    <a href="${pageContext.request.contextPath}/servlet/SearchStuServlet?
             queryName=${param.queryName}&queryValue=${requestScope.queryValue}
             &pageSize=${page.pageSize}&pageNo=${page.prevPage}">上一页</a>
    <a href="${pageContext.request.contextPath}/servlet/SearchStuServlet?
             queryName=${param.queryName}&queryValue=${requestScope.queryValue}
             &pageSize=${page.pageSize}&pageNo=${page.nextPage}">下一页</a>
    <a href="${pageContext.request.contextPath}/servlet/SearchStuServlet?
             queryName=${param.queryName}&queryValue=${requestScope.queryValue}
             &pageSize=${page.pageSize}&pageNo=${page.totalPage}">尾页</a>
    

ll){
//链接上参数的编码是 ISO8859-1
queryValue = new String(queryValue.getBytes(“ISO8859-1”),“UTF-8”);
}
}
String pageSize = request.getParameter(“pageSize”); // 页大小
if(pageSize == null){
pageSize = String.valueOf(Page.DEFAULT_SIZE);
}
String pageNo = request.getParameter(“pageNo”); // 当前页
if(pageNo == null){
pageNo = “1”;
}
//List list = new ArrayList();
Page page = null;
if(“c_name”.equals(queryName)){
try {
page=dao.queryByLikePage(queryValue,
Integer.parseInt(pageSize),Integer.parseInt(pageNo));
} catch (Exception e) {
e.printStackTrace();
}
}
if(queryValue != null){
//将经过处理的参数放入 request 中供 jsp 页面读取
request.setAttribute(“queryValue”, queryValue);
}
request.setAttribute(“page”, page);
request.getRequestDispatcher("/list.jsp").forward(request, response);//转发到 jsp


4. **jsp** 页面中显示查询结果集合

~~~jsp
<c:forEach var="stu" items="${requestScope.page.list}" varStatus="st">
    <tr>
    	<td>${st.count}</td>
    </tr>
</c:forEach>
  1. jsp 页面中显示首页、上一页、下一页、尾页的等链接

    <a href="${pageContext.request.contextPath}/servlet/SearchStuServlet?
             queryName=${param.queryName}&queryValue=${requestScope.queryValue}
             &pageSize=${page.pageSize}&pageNo=1">首页</a>
    <a href="${pageContext.request.contextPath}/servlet/SearchStuServlet?
             queryName=${param.queryName}&queryValue=${requestScope.queryValue}
             &pageSize=${page.pageSize}&pageNo=${page.prevPage}">上一页</a>
    <a href="${pageContext.request.contextPath}/servlet/SearchStuServlet?
             queryName=${param.queryName}&queryValue=${requestScope.queryValue}
             &pageSize=${page.pageSize}&pageNo=${page.nextPage}">下一页</a>
    <a href="${pageContext.request.contextPath}/servlet/SearchStuServlet?
             queryName=${param.queryName}&queryValue=${requestScope.queryValue}
             &pageSize=${page.pageSize}&pageNo=${page.totalPage}">尾页</a>
    
您好!欢迎您对JavaWeb基础学习的兴趣。如果您是初学者,我可以为您提供一些入门的建议和学习资源。以下是一些建议: 1. Java基础知识:在学习JavaWeb之前,首先需要掌握Java基础知识,包括面向对象编程、控制流、集合框架等。您可以通过在线教程、书籍或视频教程学习这些基础知识。 2. HTML和CSS:了解HTML和CSS是非常重要的,因为JavaWeb开发涉及到与前端页面的交互。您可以通过在线教程或相关书籍学习HTML和CSS的基础知识。 3. Servlet:Servlet是JavaWeb的核心技术之一,它用于处理HTTP请求和响应。学习Servlet可以帮助您理解JavaWeb开发的基本原理和流程。您可以通过阅读相关的文档、教程或者参考书籍来学习Servlet。 4. JSP:JSP(JavaServer Pages)是一种用于创建动态Web页面的技术。它可以帮助您将Java代码嵌入到HTML中,从而实现动态内容的生成和展示。学习JSP可以让您更高效地开发JavaWeb应用程序。 5. 数据库知识:JavaWeb应用程序通常需要与数据库进行交互,因此了解数据库的基本知识也是必要的。您可以学习关系型数据库(如MySQL)的基本概念、SQL语句的使用以及Java与数据库的连接。 6. 框架和工具:学习一些常用的JavaWeb框架和工具,例如Spring、Hibernate等,可以提高您的开发效率和代码质量。 除了以上建议,您还可以通过参加培训班、加入开发社区或者实践项目来加强自己的JavaWeb技能。希望以上建议对您有所帮助!如果您有任何进一步的问题,请随时向我提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值