JavaWeb
传输层协议
- TCP:当一台计算机想要与另一台计算机通讯时,两台计算机之间的通信需要畅通且可靠(会进行三次握手,断开也会进行四次挥手),这样才能保证正确收发数据,因此TCP更适合一些可靠的数据传输场景
- UDP:它是一种无连接协议,数据想发就发,而且不会建立可靠传输,也就是说传输过程中有可能会导致部分数据丢失,但是它比TCP传输更加简单高效,适合视频直播之类的
Socket
- 实现两台计算机之间的通信,Socket也叫
套接字
,是操作系统底层提供的一项通信技术,它支持TCP和UDP1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22//服务端
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(1234)){ //将服务端创建在端口1234上
System.out.println("正在等待客户端连接...");
while (true){ //无限循环等待客户端连接
Socket socket = server.accept();
System.out.println("客户端已连接,IP地址为:"+socket.getInetAddress().getHostAddress());
}
}catch (IOException e){
e.printStackTrace();
}
}
//客户端
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 1234)){
System.out.println("已连接到服务端!");
}catch (IOException e){
System.out.println("服务端连接失败!");
e.printStackTrace();
}
}
Socket数据传输
- 通过I/O流进行网络数据传输
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35//服务端
try(ServerSocket server = new ServerSocket(1234)){ //将服务端创建在端口8080上
System.out.println("正在等待客户端连接...");
Socket socket = server.accept();
System.out.println("客户端已连接,IP地址为:"+socket.getInetAddress().getHostAddress());
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); //通过
System.out.print("接收到客户端数据:");
System.out.println(reader.readLine());
OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("已收到!");
writer.flush();
}catch (IOException e){
e.printStackTrace();
}
//客户端
try (Socket socket = new Socket("localhost", 1234);
Scanner scanner = new Scanner(System.in)){
System.out.println("已连接到服务端!");
OutputStream stream = socket.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(stream); //通过转换流来帮助我们快速写入内容
System.out.println("请输入要发送给服务端的内容:");
String text = scanner.nextLine();
writer.write(text+'\n'); //因为对方是readLine()这里加个换行符
writer.flush();
System.out.println("数据已发送:"+text);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("收到服务器返回:"+reader.readLine());
}catch (IOException e){
System.out.println("服务端连接失败!");
e.printStackTrace();
}finally {
System.out.println("客户端断开连接!");
} - 手动关闭单向的流
1
2socket.shutdownOutput(); //关闭输出方向的流
socket.shutdownInput(); //关闭输入方向的流 - 设定IO超时,服务端等待太久自动关闭连接
1
socket.setSoTimeout(3000);
- 如果连接的双方发生意外而通知不到对方,导致一方还持有连接,这样就会占用资源,可以使用setKeepAlive();当客户端连接后,如果设置了KeepAlive为true,当对方没有发送任何数据过来,超过一个时间(看系统内核参数配置),服务端会发送一个ack探测包发到对方,探测双方的TCP/IP连接是否有效
1
socket.setKeepAlive(true);
- TCP在传输过程中,有一个缓冲区用于数据的发送和接收,默认8192;可以手动调整大小优化传输效率
1
2socket.setReceiveBufferSize(25565); //TCP接收缓冲区
socket.setSendBufferSize(25565); //TCP发送缓冲区
浏览器访问Socket服务器
- 浏览器访问服务端,输入
http://localhost:1234
或http://127.0.0.1:1234
连接我们本地开放的服务器1
2
3
4
5
6
7
8
9
10
11
12
13
14try(ServerSocket server = new ServerSocket(1234)){ //将服务端创建在端口1234上
System.out.println("正在等待客户端连接...");
Socket socket = server.accept();
System.out.println("客户端已连接,IP地址为:"+socket.getInetAddress().getHostAddress());
InputStream in = socket.getInputStream(); //通过
System.out.println("接收到客户端数据:");
while (true){
int i = in.read();
if(i == -1) break;
System.out.print((char) i);
}
}catch (Exception e){
e.printStackTrace();
}
JDBC操作数据库
下载安装
- 网址:https://dev.mysql.com/downloads/connector/j/
- 说明1:操作系统选
Platform Independent
,下载ZIP包,并进行解压 - 说明2:接着把
mysql-connector-j-8.0.33.jar
文件丢到项目中,鼠标右键添加为库
JDBC连接数据库
- JDBC连接数据库格式,修改为自己的数据库,表名只要学过MySQL都知道,查看结果会打印出第一列信息
- 连接URL:可以从idea数据库获取
- 用户名和密码:安装MySQL时就会让你创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14//1. 通过DriverManager来获得数据库连接
try (Connection connection = DriverManager.getConnection("连接URL","用户名","密码");
//2. 创建一个用于执行SQL的Statement对象
Statement statement = connection.createStatement()){ //注意前两步都放在try()中,因为在最后需要释放资源!
//3. 执行SQL语句,并得到结果集
ResultSet set = statement.executeQuery("select * from 表名");
//4. 查看结果
while (set.next()){
System.out.println(set.getString(1));
}
}catch (SQLException e){
e.printStackTrace();
}
//5. 释放资源,try-with-resource语法会自动帮助我们close
- DriverManager是管理数据库的驱动,通过调用getConnection()来进行数据库的链接
- Connection是数据库的连接对象,可以通过连接对象来创建一个Statement用于执行SQL语句
- Statement用来执行SQL语句
- executeQuery():执行select语句
- executeUpdate():执行一个DML或是DDL语句,返回一个int类型,表示执行后受影响的行数,判断DML语句是否执行成功
- execute():执行任意的SQL语句,返回boolean表示执行结果是一个ResultSet还是一个int,通过使用getResultSet()或是getUpdateCount()来获取
执行DML操作
- 增删改操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14try(Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/study","root","d6NFV8SYsEWPgTvq");
Statement statement = connection.createStatement()){
//增
statement.executeUpdate("insert into student values(202301, '阿良良木历', '男')");
//删
statement.executeUpdate("delete from student where sid=202306");
//改
statement.executeUpdate("update student set name='忍野咩咩', sex='女' where sid=202302");
}catch (SQLException e) {
e.printStackTrace();
}
执行DQL操作
- 执行DQL操作会返回一个ResultSet对象,我们来看看如何从ResultSet中去获取数据
1
2
3
4
5
6
7
8
9
10ResultSet set = statement.executeQuery("select * from student");
//首先要明确,select返回的数据类似于一个excel表格
while (set.next()) {
//每调用一次next()就会向下移动一行,首次调用会移动到第一行
//移动行数后,就可以通过set中提供的方法,来获取每一列的数据
System.out.println(set.getInt("sid")); //可以指定属性或第n行,行数从1开始
System.out.println(set.getString("name"));
System.out.println(set.getString(3));
}
执行批处理操作
- 执行很多条语句时,不用一次一次地提交,而是一口气全部交给数据库处理,这样会节省很多的时间
1
2
3
4
5
6statement.addBatch("insert into teach values(1, 202301)");
statement.addBatch("insert into teach values(1, 202302)");
statement.addBatch("insert into teach values(1, 202303)");
statement.addBatch("insert into teach values(2, 202304)");
statement.addBatch("insert into teach values(2, 202305)");
statement.executeBatch();
查询结果映射为对象
- 数据转换为一个类来进行操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class Student {
Integer sid;
String name;
String sex;
public Student(Integer sid, String name, String sex) {
this.sid = sid;
this.name = name;
this.sex = sex;
}
public void say() {
System.out.println("我叫:" + name + ",学号为:" + sid + ",性别是:" + sex);
}
}
ResultSet set = statement.executeQuery("select * from student");
while (set.next()) {
Student student = new Student(
set.getInt(1),
set.getString(2),
set.getString(3));
student.say();
}
/*
* 我叫:阿良良木历,学号为:202301,性别是:男
* 我叫:忍野咩咩,学号为:202302,性别是:女
* 我叫:凯露,学号为:202303,性别是:女
* 我叫:灵梦,学号为:202304,性别是:女
* 我叫:帕帕子,学号为:202305,性别是:男
* */ - 利用反射机制来将查询结果映射为对象,使用反射的好处是,无论什么类型都可以通过我们的方法来进行实体类型映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private static <T> T convert(ResultSet set, Class<T> clazz){
try {
Constructor<T> constructor = clazz.getConstructor(clazz.getConstructors()[0].getParameterTypes()); //默认获取第一个构造方法
Class<?>[] param = constructor.getParameterTypes(); //获取参数列表
Object[] object = new Object[param.length]; //存放参数
for (int i = 0; i < param.length; i++) { //是从1开始的
object[i] = set.getObject(i+1);
if(object[i].getClass() != param[i])
throw new SQLException("错误的类型转换:"+object[i].getClass()+" -> "+param[i]);
}
return constructor.newInstance(object);
} catch (ReflectiveOperationException | SQLException e) {
e.printStackTrace();
return null;
}
} - 通过反射来将查询结果转换为一个对象
1
2
3
4while (set.next()) {
Student student = convert(set, Student.class);
if(student != null) student.say();
}
SQL注入攻击
- SQL注入攻击指的是用户可以通过输入内容直接破坏SQL语句
- PreparedStatement可以防止用户输入一些SQL语句关键字,使用
?
作为占位符,它会预编译一个SQL语句,直接将我们的内容进行替换的方式来填写数据;简单来说,不管用户如何输入SQL语句,预编译阶段会进行转义,最终编译成一串字符1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17try (Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/study","root","d6NFV8SYsEWPgTvq");
PreparedStatement statement = connection.prepareStatement("select * from user where username= ? and password=?;");
Scanner scanner = new Scanner(System.in)){
statement.setString(1, scanner.nextLine());
statement.setString(2, scanner.nextLine());
ResultSet res = statement.executeQuery();
while (res.next()){
String username = res.getString(1);
System.out.println(username+" 登陆成功!");
}
}catch (SQLException e) {
e.printStackTrace();
}
管理事务
- 事务可以应用于银行业务等,此类地方需要严格审核数据,一旦出错可以立即取消,避免出现严重的问题
1
2
3
4
5
6
7
8
9
10
11
12
13try (Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/study","root","d6NFV8SYsEWPgTvq");
Statement statement = connection.createStatement()){
connection.setAutoCommit(false); //关闭自动提交,现在将变为我们手动提交
statement.executeUpdate("insert into user values ('a', 1234)");
statement.executeUpdate("insert into user values ('b', 1234)");
statement.executeUpdate("insert into user values ('c', 1234)");
connection.commit(); //如果前面任何操作出现异常,将不会执行commit(),之前的操作也就不会生效
}catch (SQLException e){
e.printStackTrace();
} - 设置回滚点以及使用回滚点,相当于游戏🎮的存档读档
1
2
3
4
5
6
7connection.setAutoCommit(false);
statement.executeUpdate("insert into user values ('a', 1234)");
Savepoint savepoint = connection.setSavepoint(); //设置回滚点
statement.executeUpdate("insert into user values ('b', 1234)");
statement.executeUpdate("insert into user values ('c', 1234)");
connection.rollback(savepoint); //回到回滚点,撤销前面的操作
connection.commit();
Lombok注解开发
下载安装
- 网址:https://projectlombok.org/download
- 说明1:点击download下载,接着把
lombok.jar
文件丢到项目中,鼠标右键添加为库 - 说明2:idea自带lombok插件,不需要我们自己去安装,最后重启一下idea
- 说明3:首次使用要idea开启注释处理器;打开设置-构建执行部署-编译器-注解处理器-启动注解处理
常用注解
- @Data代表以下注解
1
2
3
4
5- @Setter与@Getter:生成set/get方法,静态与final字段无法生成set方法
- @Accessors:控制生成Getter和Setter的样式
- @ToString:生成预设的toString方法
- @EqualsAndHashCode:生成比较和哈希值方法
- @RequiredArgsConstructor:生成参数只包含final或标记为@NonNull的成员
- chain开启链式调用,fluent开启名字优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Student {
private String name;
private int age;
}
Student student = new Student();
student
.setName("幽蓝")
.setAge(9);
System.out.println(student.getName() + student.getAge());
//======================================================
student
.name("幽蓝")
.age(9);
System.out.println(student.name() + student.age()); - includeFieldNames省略字段,exclude不需要,of只需要,Include方法可以排序、取别名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Student {
private String name;
private int age;
private String state;
}
Student student = new Student("幽蓝", 9, "睡觉");
System.out.println(student); //默认Student(name=幽蓝, age=9, state=睡觉),关闭后Student(幽蓝, 9, 睡觉)
//======================================================
Student student = new Student("幽蓝", 9, "睡觉");
System.out.println(student); //Student(state=睡觉)
//======================================================
Student student = new Student("幽蓝", 9, "睡觉");
System.out.println(student); //Student(name=幽蓝, age=9)
//======================================================
public class Student {
0, name = "你的名字") .Include(rank =
private String name;
2) .Include(rank =
private int age;
1) .Include(rank =
private String state;
}
Student student = new Student();
System.out.println(student); //Student(age=0, state=null, 你的名字=null)
其他注解
- @AllArgsConstructor和@NoArgsConstructor:生成全参构造和无参构造
- @Value:它与@Data类似,但是并不会生成setter并且成员属性都是final类型
- @SneakyThrows:生成try-catch代码块
1
2
3
4
5
public static void main(String[] args) {
InputStream inputStream = new FileInputStream("lombok.jar");
inputStream.close();
} - @Cleanup:作用与局部变量,在最后自动调用其close()方法(可以自由更换)
1
2
3
4
5
public static void main(String[] args) {
InputStream inputStream = new FileInputStream("lombok.jar");
} - @Builder:生成建造者模式
- @Builder.Default:指定默认值
- @Builder.ObtainVia:指定默认值的获取方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Student {
private String name;
private int age;
private String state;
}
Student student = Student
.builder()
.name("幽蓝")
.age(9)
.state("跑步🏃")
.build();
System.out.println(student.toString()); //Student(name=幽蓝, age=9, state=跑步🏃)
//======================================================
.Default
private int age = 10;
Student student = Student
.builder()
.name("幽蓝")
.state("跑步🏃")
.build();
System.out.println(student.toString()); //Student(name=幽蓝, age=10, state=跑步🏃)
Mybatis操作数据库框架
XML语言
- 创建不解析区域,里面的内容不会解析成标签,功能挺好的省去写一堆转义符
1
2
3<test>
<name><![CDATA[我看你<><><>是一点都不懂哦>>>]]></name>
</test>
下载安装
- 网址:https://github.com/mybatis/mybatis-3/releases
- 说明1:翻墙打开网站,点最新的下载,比如
mybatis-3.5.13.zip
- 说明2:老规矩解压,再把jar文件丢到项目中,再添加为库
初次使用
- 根目录创建
mybatis-config.xml
配置文件,填写mysql驱动,以及数据库的相关信息1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study"/>
<property name="username" value="root"/>
<property name="password" value="d6NFV8SYsEWPgTvq"/>
</dataSource>
</environment>
</environments>
</configuration> - 注解用来简化try-catch语句,运行不报错表示配置成功
SqlSessionFactory
创建多个新的会话;SqlSession
对象负责接收会话,相当于不同的地方登陆一个账号去访问数据库,跟JDBC中的Statement
对象一样,会话之间相互隔离,没有任何关联;SqlSession
定义了大量的数据库操作方法,只需要通过一个对象就能完成数据库的交互,极大的简化流程1
2
3
4
5
6
public static void main(String[] args) throws FileNotFoundException {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("mybatis-config.xml"));
SqlSession session = sqlSessionFactory.openSession(true);
}
初次使用2
- 指定位置
com.test.entity
下创建Student类,使用lombok创建方法1
2
3
4
5
6
7
public class Student {
int sid; //名称最好和数据库字段名称保持一致,不然可能会映射失败导致查询结果丢失
String name;
String sex;
} - 根目录创建
TestMapper.xml
作为映射器,namespace就是命名空间,每个Mapper都是唯一的,因此需要用一个命名空间来区分,它还可以用来绑定一个接口。我们在里面写入了一个select标签,表示添加一个select操作,同时id作为操作的名称,resultType指定为我们刚刚定义的实体类,表示将数据库结果映射为Student
类,然后就在标签中写入我们的查询语句1
2
3
4
5
6
7
8
9
<mapper namespace="TestMapper">
<select id="selectStudent" resultType="com.test.entity.Student">
select * from student
</select>
</mapper> - 配置文件中添加mapper映射器,告诉mybatis这里定义了一个mapper读取一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study"/>
<property name="username" value="root"/>
<property name="password" value="d6NFV8SYsEWPgTvq"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper url="file:TestMapper.xml"/>
</mappers>
</configuration> - main方法使用定义好的Mapper
1
2
3
4
5
6
7
8
public static void main(String[] args) throws FileNotFoundException {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("mybatis-config.xml"));
SqlSession session = sqlSessionFactory.openSession(true);
List<Student> list = session.selectList("selectStudent");
list.forEach(System.out::println);
}
配置Mybatis
- 可以给类型起一个别名,以简化Mapper的编写
1
2
3
4<!-- 需要在environments的上方 -->
<typeAliases>
<typeAlias type="com.test.entity.Student" alias="papazi"/>
</typeAliases> - 现在Mapper可以直接使用别名了
1
2
3
4
5<mapper namespace="com.test.mapper.TestMapper">
<select id="selectStudent" resultType="papazi">
select * from student
</select>
</mapper> - 也可以让Mybatis去扫描一个包,并将包下的所有类自动起别名(别名为首字母小写的类名)
1
2
3<typeAliases>
<package name="com.test.entity"/>
</typeAliases> - 不使用实体类,可以将属性映射为Map,就会以键值对的形式存放这些结果
1
2
3<select id="selectStudent" resultType="Map">
select * from student
</select>1
2
3public interface TestMapper {
List<Map> selectStudent();
} - 设定一个
resultType
属性,让Mybatis知道查询结果需要映射为哪个实体类,要求字段名称保持一致。如果我们不希望按照这样的规则来映射呢?可以自定义resultMap
来设定映射规则,通过指定映射规则,现在名称和性别一栏就发生了交换,因为我们将映射字段进行了交换1
2
3
4
5<resultMap id="Test" type="Student">
<result column="sid" property="sid"/>
<result column="sex" property="name"/>
<result column="name" property="sex"/>
</resultMap> - 如果一个类中存在多个构造方法,这时就需要使用
constructor
标签来指定构造方法;指定构造方法后,若此字段被填入了构造方法作为参数,将不会通过反射给字段单独赋值,而构造方法中没有传入的字段,依然会被反射赋值1
2
3
4
5
6<resultMap id="test" type="Student">
<constructor>
<arg column="sid" javaType="Integer"/>
<arg column="name" javaType="String"/>
</constructor>
</resultMap> - 如果数据库存在一个带下划线的字段,通过设置让其映射为以驼峰命名的字段,比如
my_test
映射为myTest
;如果不设置,默认为不开启,也就是默认需要名称保持一致1
2
3<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
增删改查
- 通过maven生成的项目结构,配置文件的路径也需要跟着修改,比如resource属性指向
resources
文件夹,用来管理配置文件,IO读取的文件则是以src文件作为根路径 - mybatis-config配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<configuration>
<typeAliases>
<package name="com.test.entity"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study"/>
<property name="username" value="root"/>
<property name="password" value="d6NFV8SYsEWPgTvq"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/TestMapper.xml"/>
</mappers>
</configuration> - TestMapper.xml映射器
通过使用
#{xxx}
或是${xxx}
来填入我们给定的属性,实际上Mybatis本质也是通过PreparedStatement
首先进行一次预编译,有效地防止SQL注入问题,但是如果使用${xxx}
就不再是通过预编译,而是直接传值,因此我们一般都使用#{xxx}
来进行操作1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<mapper namespace="com.test.TestMapper">
<select id="selectStudent" resultType="student">
select * from student
</select>
<select id="getStudentBySid" resultType="student">
select * from student where sid = #{sid}
</select>
<insert id="addStudent">
insert into student(sid, name, sex) values(#{sid}, #{name}, #{sex})
</insert>
<delete id="deleteStudent">
delete from student where sid = #{sid}
</delete>
<update id="updateStudent">
update student set name = #{newName} where sid = #{sid}
</update>
</mapper> - TestMapper接口
1
2
3
4
5
6
7
8
9
10
11
12
13package com.test;
import com.test.entity.Student;
import java.util.List;
public interface TestMapper {
List<Student> selectStudent();
Student getStudentBySid(int sid);
void addStudent(Student student);
void deleteStudent(int sid);
void updateStudent(int sid, String newName);
} - Student类
1
2
3
4
5
6
7
8
9
10
11
12
13package com.test.entity;
import lombok.Data;
import lombok.experimental.Accessors;
public class Student {
int sid;
String name;
String sex;
} - MyBatisUtil类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29package com.test;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class MyBatisUtil {
//在类加载时就进行创建
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("src/main/resources/mybatis-config.xml"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取一个新的会话
* @param autoCommit 是否开启自动提交(跟JDBC是一样的,如果不自动提交,则会变成事务操作)
* @return SqlSession对象
*/
public static SqlSession getSession(boolean autoCommit){
return sqlSessionFactory.openSession(autoCommit);
}
} - Main主程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.test;
import com.test.entity.Student;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class Main {
public static void main(String[] args) {
SqlSession session = MyBatisUtil.getSession(true);
List<Student> list = session.selectList("selectStudent");
list.forEach(System.out::println); //遍历集合全部学生信息
TestMapper mapper = session.getMapper(TestMapper.class);
System.out.println(mapper.getStudentBySid(202303)); //获取指定sid信息
mapper.addStudent(new Student().setName("阿虚").setSex("男").setSid(202306)); //添加学生信息
mapper.deleteStudent(202306); //删除指定sid的全部数据
}
}
复杂查询
一对多查询;映射为Teacher对象时,同时将教授的所有学生映射为List列表,这时需要进行复杂查询,通过
resultMap
来自定义映射规则1
2
3
4
5
6
7
8
9
public class Teacher {
int tid;
String name;
List<Student> studentList;
}
//main方法
System.out.println(mapper.getTeacherByTid(1));1
2
3
4
5
6
7
8
9
10
11
12
13
14<select id="getTeacherByTid" resultMap="asTeacher">
select *, teacher.name as tname from student inner join teach on student.sid = teach.sid
inner join teacher on teach.tid = teacher.tid where teach.tid = #{tid}
</select>
<resultMap id="asTeacher" type="Teacher">
<id column="tid" property="tid"/>
<result column="tname" property="name"/>
<collection property="studentList" ofType="Student">
<id column="sid" property="sid"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
</collection>
</resultMap>查询结果是一个多表联查的结果,联查的数据就是我们需要的映射数据(比如这里是一个老师有N个学生,联查的结果也是这一个老师对应N个学生的N条记录)
其中id标签用于在多条记录中辨别是否为同一个对象的数据,比如上面的查询语句得到的结果中,tid这一行始终为1,因此所有的记录都应该是
tid=1
的教师的数据,而不应该变为多个教师的数据,如果不加id进行约束,那么会被识别成多个教师的数据通过使用collection来表示将得到的所有结果合并为一个集合,比如上面的数据中每个学生都有单独的一条记录,因此tid相同的全部学生的记录就可以最后合并为一个List,得到最终的映射结果
多对一查询;查询到一个Student对象时都带上它的老师,同样的,我们也可以使用
resultMap
来实现(先修改一下老师的类定义,不然会很麻烦)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Student {
private int sid;
private String name;
private String sex;
private Teacher teacher;
}
public class Teacher {
int tid;
String name;
}
//TestMapper接口
public interface TestMapper {
List<Student> selectStudent(int tid);
省略......
}
//main方法
mapper.selectStudent(1).forEach(System.out::println);1
2
3
4
5
6
7
8
9
10
11
12
13
14<select id="selectStudent" resultMap="test2">
select *, teacher.name as tname from student left join teach on student.sid = teach.sid
left join teacher on teach.tid = teacher.tid
</select>
<resultMap id="test2" type="Student">
<id column="sid" property="sid"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
<association property="teacher" javaType="Teacher">
<id column="tid" property="tid"/>
<result column="tname" property="name"/>
</association>
</resultMap>通过使用association进行关联,形成多对一的关系,实际上和一对多是同理的,都是对查询结果的一种处理方式罢了
事务操作
- 获取
SqlSession
关闭自动提交来开启事务模式,和JDBC其实都差不多1
2
3
4
5
6
7
8public static void main(String[] args) {
SqlSession session = MyBatisUtil.getSession(false); //参数false,自动提交已关闭
TestMapper mapper = session.getMapper(TestMapper.class);
mapper.addStudent(new Student().setSid(202310).setName("小光").setSex("男"));
session.rollback(); //回滚
session.commit(); //提交
}
动态SQL
- 动态SQL是MyBatis的强大特性之一;如果你使用过JDBC或其它类似的框架,你应该能理解根据不同条件拼接SQL语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态SQL,可以彻底摆脱这种痛苦
- if语句:试着进行算术运算
sid % 2 ==0
,然而并不生效;换成比较运算符sid == 202302
,这样才生效1
2
3
4
5
6<select id="getStudentBySid" resultType="student">
select * from student where sid = #{sid}
<if test="sid % 2 == 0">
and sex = '女'
</if>
</select> - choose、when、otherwise语句,不想使用所有的条件,而是从多个条件中选择一个使用;otherwise表示其他条件都不满足,最终进入这个条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14<select id="getStudentBySid" resultType="student">
select * from student where sid = #{sid}
<choose>
<when test="sid == 202301">
and sex = '女'
</when>
<when test="sid == 202302">
and sex = '男'
</when>
<otherwise>
and sex = '女'
</otherwise>
</choose>
</select>
缓存机制
- Mybatis内置了一个缓存机制,我们查询时,如果缓存中存在数据,那么我们就可以直接从缓存中获取,而不是再去向数据库进行请求
- Mybatis存在一、二级缓存;二级缓存默认是关闭状态,开启二级缓存需要在映射器XML文件中添加
<cache/>
; FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的- FIFO:先进先出,按对象进入缓存的顺序来移除它们
1
2
3
4
5
6
7
8
9
10
11<!--mybatis-config配置文件开启缓存-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!--TestMapper配置文件添加缓存参数-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
- FIFO:先进先出,按对象进入缓存的顺序来移除它们
- 第一个会话在进行读操作,完成后会结束会话,而第二个操作重新创建了一个新的会话,再次执行了同样的查询,我们发现得到的依然是缓存的结果
1
2
3
4
5
6
7
8
9
10
11Student student;
try (SqlSession sqlSession = MyBatisUtil.getSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
student = testMapper.getStudentBySid(202301);
}
try (SqlSession sqlSession2 = MyBatisUtil.getSession(true)){
TestMapper testMapper2 = sqlSession2.getMapper(TestMapper.class);
Student student2 = testMapper2.getStudentBySid(202301);
System.out.println(student2 == student);
} - 如果不希望某个方法开启缓存,可以添加useCache属性用来关闭缓存
1
2
3<select id="getStudentBySid" resultType="Student" useCache="false">
select * from student where sid = #{sid}
</select> - 如果你希望执行一次清一次缓存,可以添加flushCache属性
1
2
3<select id="getStudentBySid" resultType="Student" flushCache="true">
select * from student where sid = #{sid}
</select>取顺序:二级缓存 => 一级缓存 => 数据库
- 多个CPU在操作自己的缓存时,可能会出现各自的缓存内容不同步的问题;可以使用Redis、Ehcache、Memcache等缓存框架,能够很好地解决缓存一致性问题
注解开发
- 优点是不用再去写配置文件了,统一使用注解形式来开发
- 项目结构继续大改,
resources
文件夹依旧存放配置文件,不同的是mapper
文件夹移动到com.test
下面 - mybatis配置文件改用package标签,扫描
com.test.mapper
路径下的全部注解映射器1
2
3
4<mappers>
<!--<mapper class="com.test.mapper.TestMapper"/>-->
<package name="com.test.mapper"/>
</mappers> - TestMapper接口,先写方法后写注解sql语句,反过来写一直红标😮💨
1
2
3
4
5
6
7public interface TestMapper {
void addStudent(Student student);
Student getStudentBySid(int sid);
} - main方法使用方式不变,可以给sid设置自动递增,sex默认男,这样就能偷懒了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Student {
private int sid;
private String name;
private String sex = "男";
}
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtil.getSession(true);
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
mapper.addStudent(new Student().setName("阿虚"));
mapper.addStudent(new Student().setName("长门有希").setSex("女"));
} - 使用
@CacheNamespace
注解定义在接口上,通过使用@Options
来控制单个操作的缓存启用1
2
3
4
5
6
public interface MyMapper {
List<Student> getAllStudent();
动态代理机制
- 好比我开了个大棚,里面栽种的西瓜,那么西瓜成熟了是不是得去卖掉赚钱,而我们的西瓜非常多,一个人肯定卖不过来,肯定就要去多找几个开水果摊的帮我们卖,这就是一种代理。实际上是由水果摊老板在帮我们卖瓜,我们只告诉老板卖多少钱,而至于怎么卖的是由水果摊老板决定的
- 首先定义一个接口用于规范行为
1
2
3
4public interface Shopper {
//卖瓜行为
void saleWatermelon(String customer);
} - 实现一下卖瓜行为,也就是我们要告诉老板卖多少钱,这里就直接写成功出售
1
2
3
4
5
6
7public class ShopperImpl implements Shopper{
//卖瓜行为的实现
public void saleWatermelon(String customer) {
System.out.println("成功出售西瓜给 ===> "+customer);
}
} - 最后老板代理后肯定要用自己的方式去出售这些西瓜,成交之后再按照我们告诉老板的价格进行出售
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class ShopperProxy implements Shopper{
private final Shopper impl;
public ShopperProxy(Shopper impl){
this.impl = impl;
}
//代理卖瓜行为
public void saleWatermelon(String customer) {
//首先进行 代理商讨价还价行为
System.out.println(customer + ":哥们,这瓜多少钱一斤啊?");
System.out.println("老板:两块钱一斤。");
System.out.println(customer + ":你这瓜皮子是金子做的,还是瓜粒子是金子做的?");
System.out.println("老板:你瞅瞅现在哪有瓜啊,这都是大棚的瓜,你嫌贵我还嫌贵呢。");
System.out.println(customer + ":给我挑一个。");
impl.saleWatermelon(customer); //讨价还价成功,进行我们告诉代理商的卖瓜行为
}
}这样的操作称为静态代理,也就是说我们需要提前知道接口的定义并进行实现才可以完成代理,而Mybatis这样的是无法预知代理接口的,我们就需要用到动态代理;JDK提供的反射框架就为我们很好地解决了动态代理的问题
- 通过实现InvocationHandler来成为一个动态代理,我们发现它提供了一个invoke方法,用于调用被代理对象的方法并完成我们的代理工作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class ShopperProxy implements InvocationHandler {
Object target;
public ShopperProxy(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String customer = (String) args[0];
System.out.println(customer + ":哥们,这瓜多少钱一斤啊?");
System.out.println("老板:两块钱一斤。");
System.out.println(customer + ":你这瓜皮子是金子做的,还是瓜粒子是金子做的?");
System.out.println("老板:你瞅瞅现在哪有瓜啊,这都是大棚的瓜,你嫌贵我还嫌贵呢。");
System.out.println(customer + ":行,给我挑一个。");
return method.invoke(target, args);
}
} - 现在就可以通过
Proxy.newProxyInstance
来生成一个动态代理类1
2
3
4
5
6
7public static void main(String[] args) {
Shopper impl = new ShopperImpl();
Shopper shopper = (Shopper) Proxy.newProxyInstance(impl.getClass().getClassLoader(),
impl.getClass().getInterfaces(), new ShopperProxy(impl));
shopper.saleWatermelon("小强");
System.out.println(shopper.getClass());
}
JUnit单元测试
- 名称单元测试框架,用来评估某个模块或是功能的耗时和性能,快速排查导致程序运行缓慢的问题
下载安装
- 网址:https://repo1.maven.org/maven2/junit/junit/4.13.2/
- 网址:https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.1/
- 说明1:翻墙打开网站,点击
junit-4.13.2.jar
,另一个网址点击hamcrest-core-1.1.jar
- 说明2:两个jar包丢到项目中,添加为库
尝试JUnit
- 断言工具类,能测试结果是否符合预期
1
2
3
4
5
6
7public class TestMain {
public void method(){
System.out.println("我是测试案例!");
Assert.assertEquals(1, 2); //参数1是期盼值,参数2是实际测试结果值
}
} - 测试一个案例,比如我们想查看冒泡排序的编写是否正确
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class MainTest {
public void method(){
int[] arr = {0, 4, 5, 2, 6, 9, 3, 1, 7, 8};
//错误的冒泡排序
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if(arr[j] > arr[j + 1]){
int tmp = arr[j];
arr[j] = arr[j+1];
// arr[j+1] = tmp; //解除注释,再跑一次看看
}
}
}
System.out.println(Arrays.toString(arr)); //[0, 1, 1, 1, 1, 1, 1, 1, 7, 8]
Assert.assertArrayEquals(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, arr);
}
} - 测试从数据库中取数据是否为我们预期的数据
1
2
3
4
5
6
7
8
9
10
11public class MainTest {
public void method(){
try (SqlSession sqlSession = MyBatisUtil.getSession(true)){
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
Student student = mapper.getStudentBySidAndSex(202301, "男");
Assert.assertEquals(new Student().setName("阿良良木历").setSex("男").setSid(202301), student);
}
}
} - 如果测试之前需要做一些前置操作,可通过
@Before
注解在测试用例开始前执行前置操作1
2
3
4
5
6
public void before(){
System.out.println("测试前置正在初始化...");
前置操作......
System.out.println("测试初始化完成,正在开始测试案例...");
}
JUL日志
- JDK提供了一个日志框架,位于
java.util.logging
包下,可以使用此框架来实现日志的规范化打印1
2
3
4
5
6
7
8public class Main {
public static void main(String[] args) {
// 首先获取日志打印器
Logger logger = Logger.getLogger(Main.class.getName());
// 调用info来输出一个普通的信息,直接填写字符串即可
logger.info("我是普通的日志");
}
} - 日志打印结果
1
2十一月 15, 2021 12:55:37 下午 com.test.Main main
信息: 我是普通的日志
日志级别
- 日志分为7个级别,详细信息可以在Level类中查看
- SEVERE(最高值):一般用于代表严重错误
- WARNING:一般用于表示某些警告,但是不足以判断为错误
- INFO(默认级别):常规消息
- CONFIG
- FINE
- FINER
- FINEST(最低值)
- 通过
log
方法设定日志的输出级别1
2
3
4
5
6
7public static void main(String[] args) {
Logger logger = Logger.getLogger(Main.class.getName());
logger.log(Level.SEVERE, "严重的错误", new IOException("我就是错误"));
logger.log(Level.WARNING, "警告的内容");
logger.log(Level.INFO, "普通的信息");
logger.log(Level.CONFIG, "级别低于普通信息");
} - 低于默认级别的日志信息,无法输出到控制台,我们可以通过设置来修改日志的打印级别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public static void main(String[] args) {
Logger logger = Logger.getLogger(Main.class.getName());
//修改日志级别
logger.setLevel(Level.CONFIG);
//不使用父日志处理器
logger.setUseParentHandlers(false);
//使用自定义日志处理器
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.CONFIG);
logger.addHandler(handler);
logger.log(Level.SEVERE, "严重的错误", new IOException("我就是错误"));
logger.log(Level.WARNING, "警告的内容");
logger.log(Level.INFO, "普通的信息");
logger.log(Level.CONFIG, "级别低于普通信息");
} - 每个
Logger
都有一个父日志打印器,我们可以通过getParent()
1
2
3
4public static void main(String[] args) throws IOException {
Logger logger = Logger.getLogger(Main.class.getName());
System.out.println(logger.getParent().getClass());
}结果是
java.util.logging.LogManager$RootLogger
这个类,默认使用的是ConsoleHandler,且日志级别为INFO;每一个日志打印器都会使用父类的处理器,因此我们需要在之前关闭父类,然后使用自己的处理器 - 使用文件处理器来处理日志信息;参数true,表示开启追加模式,向尾部添加内容
1
2
3
4//添加输出到本地文件
FileHandler fileHandler = new FileHandler("test.log", true);
fileHandler.setLevel(Level.WARNING);
logger.addHandler(fileHandler); - 配置打印样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public static void main(String[] args) throws IOException {
Logger logger = Logger.getLogger(Main.class.getName());
logger.setUseParentHandlers(false);
//为了让颜色变回普通的颜色,通过代码块在初始化时将输出流设定为System.out
ConsoleHandler handler = new ConsoleHandler(){{
setOutputStream(System.out);
}};
//创建匿名内部类实现自定义的格式
handler.setFormatter(new Formatter() {
public String format(LogRecord record) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String time = format.format(new Date(record.getMillis())); //格式化日志时间
String level = record.getLevel().getName(); // 获取日志级别名称
// String level = record.getLevel().getLocalizedName(); // 获取本地化名称(语言跟随系统)
String thread = String.format("%10s", Thread.currentThread().getName()); //线程名称(做了格式化处理,留出10格空间)
long threadID = record.getThreadID(); //线程ID
String className = String.format("%-20s", record.getSourceClassName()); //发送日志的类名
String msg = record.getMessage(); //日志消息
//\033[33m作为颜色代码,30~37都有对应的颜色,38是没有颜色,IDEA能显示,但是某些地方可能不支持
return "\033[38m" + time + " \033[33m" + level + " \033[35m" + threadID
+ "\033[38m --- [" + thread + "] \033[36m" + className + "\033[38m : " + msg + "\n";
}
});
logger.addHandler(handler);
logger.info("我是测试消息1...");
logger.log(Level.INFO, "我是测试消息2...");
logger.log(Level.WARNING, "我是测试消息3...");
} - 设置过滤器,可以过滤掉不想要的日志信息
1
2
3
4
5
6
7
8
9
10public static void main(String[] args) throws IOException {
Logger logger = Logger.getLogger(Main.class.getName());
//自定义过滤规则
logger.setFilter(record -> !record.getMessage().contains("普通"));
logger.log(Level.SEVERE, "严重的错误", new IOException("我就是错误"));
logger.log(Level.WARNING, "警告的内容");
logger.log(Level.INFO, "普通的信息");
}
Properties
- properties文件是Java的一种配置文件,以更简单的方式读取配置文件,格式为
配置项=配置值
1
2name=Test
desc=Description - 通过
Properties
类读取为一个类似于Map一样的对象;Properties类继承自Hashtable,而Hashtable是实现Map的接口,Properties本质上就是一个Map一样的结构,它会把所有的配置项映射为一个Map,这样就可以快速地读取配置的值1
2
3
4
5
6
7
8
9public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.load(new FileInputStream("src/main/resources/logging.properties"));
System.out.println(properties); //{name=Test, desc=Description}
System.out.println(properties.getProperty("name")); //Test
System.out.println(properties.getProperty("yyds", "找不到key")); //找不到key
properties.put("幽蓝", "9"); //{name=Test, 幽蓝=9, desc=Description}
}
日志配置文件
- 配置默认值,也可以修改
ConsoleHandler
的默认配置1
2
3
4
5
6
7
8
9
10
11# RootLogger 的默认处理器为
handlers= java.util.logging.ConsoleHandler
# RootLogger 的默认的日志级别
.level= ALL
# 指定默认日志级别
java.util.logging.ConsoleHandler.level = ALL
# 指定默认日志消息格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定默认的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8 - 试试配置文件效果
1
2
3
4
5
6
7
8
9public static void main(String[] args) throws IOException {
//获取日志管理器
LogManager manager = LogManager.getLogManager();
//读取我们自己的配置文件
manager.readConfiguration(new FileInputStream("src/main/resources/logging.properties"));
//再获取日志打印器
Logger logger = Logger.getLogger(Main.class.getName());
logger.log(Level.CONFIG, "我是一条日志信息"); //通过自定义配置文件,我们发现默认级别不再是INFO了
}
lombok开启日志
- 真方便呢,太适合我了
1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) throws IOException {
System.out.println(log.getName());
log.info("我是日志信息");
}
}
mybatis日志系统
- Mybatis日志系统记录了所有的数据库操作,默认不开启
- 日志系统输出Mybatis的日志信息;配置为JDK_LOGGING表示使用JUL进行日志打印
logImpl
包括很多种配置项,包括 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING1
<setting name="logImpl" value="JDK_LOGGING" />
- 因为Mybatis的日志级别都比较低,因此需要设置下
logging.properties
的日志级别1
2
3handlers= java.util.logging.ConsoleHandler
.level= ALL
java.util.logging.ConsoleHandler.level = ALL - 创建一个格式化类,用来修改日志的打印格式
1
2
3
4
5
6
7
8public class FormatterTest extends Formatter {
public String format(LogRecord record) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String time = format.format(new Date(record.getMillis())); //格式化日志时间
return time + " : " + record.getMessage() + "\n";
}
} - 再把格式化类,添加到JUL配置文件中,修改下默认的格式化实现
1
2
3
4handlers= java.util.logging.ConsoleHandler
.level= ALL
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = com.test.FormatterTest - 测试用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MainTest {
private SqlSessionFactory sqlSessionFactory;
public void before(){
try {
sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(new FileInputStream("src/main/resources/mybatis-config.xml"));
LogManager manager = LogManager.getLogManager();
manager.readConfiguration(new FileInputStream("src/main/resources/logging.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
public void test(){
try(SqlSession sqlSession = sqlSessionFactory.openSession(true)){
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
log.info(mapper.getStudentBySidAndSex(202301, "男").toString());
log.info(mapper.getStudentBySidAndSex(202301, "男").toString());
}
}
}
Maven项目管理
项目结构
- 部分文件说明
- java:创建包,写代码的地方
- resources:存放静态资源,比如图片、配置文件,项目打包时会将资源文件一起打包到Jar中
- test:存放测试文件,不会跟着项目一起打包
- .gitignore:过滤文件,指定哪些文件不上传到仓库
- pom.xml:maven的配置文件
- 说说pom.xml配置文件;groupId、artifactId、version这三个元素合在一起,作为区别项目的坐标。通过Maven导入其他的依赖,只需要填写这三个基本元素就可以了,而无需再下载Jar文件,转为Maven自动下载依赖并导入
- groupId:组号
- artifactId:项目名
- version:版本号,SNAPSHOT代表快照,表示处于开发中,正式版只带版本号
1
2
3<groupId>org.example</groupId>
<artifactId>java-test</artifactId>
<version>1.0-SNAPSHOT</version>
- properties中一般都是一些变量和选项的配置,前两个是JDK版本,后一个是编码格式
1
2
3
4
5<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
依赖导入
- 网址:https://mvnrepository.com
- 说明1:搜索lombok,点最新版,会出现依赖坐标,复制到pom.xml配置文件,最后刷新下maven
- 说明2:网站打不开,谷歌搜某个项目的Maven依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<dependencies>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies> - 项目依赖一般是存储在中央仓库中,也有可能存储在一些其他的远程仓库(私服);maven第一次导入依赖需要联网,然后从远程仓库进行下载,我们会发现本地存在一个
.m2
文件夹,这就是maven本地仓库文件夹,可以在项目结构
中查看详细的路径 - 下次导入依赖时,如果maven发现本地仓库中已经存在某个依赖,就不会再去远程仓库下载
- 由于远程仓库在国外,下载依赖时会出现卡顿,建议配置下远程仓库地址,打开IDEA的安装目录,找到
安装根目录/plugins/maven/lib/maven3/conf
文件夹,找到settings.xml
文件,添加以下内容:1
2
3
4
5
6<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror> - 这样默认的远程仓库地址(国外),就配置为国内的阿里云仓库地址了(依赖的下载速度就会快起来了)
- 如果需要的依赖没有上传的远程仓库,只有一个Jar,添加system值,表示不从远程仓库获取,而是导入本地Jar包,在添加一个systemPath来指定jar文件的位置
1
2
3
4
5
6
7<dependency>
<groupId>javax.jntm</groupId>
<artifactId>diyulan</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>C://学习资料/4K高清无码/test.jar</systemPath>
</dependency>
依赖作用域
- 依赖其他属性
- type:依赖类型,对于项目坐标定义的packaging。大部分情况下,该元素不必声明,默认值为jar
- scope:依赖范围
- optional:标记依赖是否可选
- exclusions:排除传递性依赖(一个项目可能依赖于其他项目,就像我们的项目,如果别人要用我们的项目作为依赖,那么就需要一起下载我们项目的依赖,如Lombok)
- scope属性依赖范围
- compile :默认参数,编译、运行、测试均有效
- provided :编译、测试有效,运行无效;比如Lombok只在编译阶段使用它,运行时不需要此依赖
- runtime :运行、测试有效,编译无效;比如JDBC的实现用到JDK指定的接口,实际上在运行时是不用自带JDK的依赖,因此只保留我们自己写的内容即可。
- test :只在测试时有效;比如在测试阶段使用JUnit
可选依赖
- optional标签表示此依赖是可选的,默认true不导入
1
<optional>true</optional>
排除依赖
- 字面意思,用来排除不需要的依赖,通常用来降低版本或使用其他版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</exclusion>
<!--还能往下添加排除-->
<exclusion>
<groupId>...</groupId>
<artifactId>...</artifactId>
</exclusion>
</exclusions>
</dependency>
继承关系
- 新建一个模块,打开pom.xml文件;parent节点表示是个子项目,子项目直接继承父项目的groupId
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>java-test</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>maven-test</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project> - 谈谈继承关系,父项目的dependencies标签,默认全部继承,除了optional标签外;父项目的dependencyManagement标签,子项目需要指定哪些依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<!-- 以下的依赖全部被继承 -->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
...
</dependencies>
<!-- 子项目需要指定哪些依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
...
</dependencies>
</dependencyManagement> - 父项目将所有的依赖进行集中管理,子项目需要什么再拿什么即可,同时子项目无需指定版本,所有的版本全部由父项目决定,子项目只需要使用即可
1
2
3
4
5
6
7<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
常用命令
- IDEA右上角Maven板块中,每个Maven项目都有一个生命周期,实际上这些是Maven的一些插件,每个插件都有各自的功能
- clean:执行后会清理整个
target
文件夹,在之后编写SpringBoot项目时可以解决一些缓存没更新的问题 - validate:可以验证项目的可用性
- compile:可以将项目编译为.class文件
- install:可以将当前项目安装到本地仓库,以供其他项目导入作为依赖使用
- verify:可以按顺序执行每个默认生命周期阶段(
validate
,compile
,package
等)
- clean:执行后会清理整个
- test命令,可以一键测试所有位于test目录下的测试案例,注意以下要求
- 测试类的名称必须是以
Test
结尾,比如MainTest
- 测试方法上必须标注
@Test
注解1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class MainTest {
//因为配置文件位于内部,我们需要使用Resources类的getResourceAsStream来获取内部的资源文件
private static SqlSessionFactory factory;
//在JUnit5中@Before被废弃,它被细分了:
// 一次性开启所有测试案例只会执行一次 (方法必须是static)
// @BeforeEach 一次性开启所有测试案例每个案例开始之前都会执行一次
public static void before(){
factory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream("mybatis.xml"));
}
//自定义测试名称
//自动执行多次测试
public void test(){
try (SqlSession sqlSession = factory.openSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
System.out.println(testMapper.getStudentBySid(1));
}
}
}
- 测试类的名称必须是以
- package命令,用来打包生成jar文件;如果需要打包成可执行文件,可以通过插件实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.test.Main</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build> - deploy命令,用于发布项目到本地仓库和远程仓库,一般情况下用不到
- site命令,用于生成当前项目的发布站点
Tomcat服务器
下载安装
- 网址:https://tomcat.apache.org/download-10.cgi
- 说明1:下载栏目点
10.1
,mac系统选Core版,下载tar.gz
并解压,建议放在用户名下的资源库 - 说明2:tomcat的bin目录下输入指令
1
2
3
4
5执行权限修改,提示输入管理员密码
sudo chmod +x *.sh
启动tomcat
sudo sh startup.sh - 说明3:打开浏览器,输入
http://localhost:8080
,如果能看到Apache Tomcat,表示成功运行tomcat - tomcat相关操作
1
2
3
4
5
6
7
8
9启动tomcat
sh startup.sh
停止tomcat
sh shutdown.sh
重启
sh shutdown.sh
sh startup.sh - 8080端被其他进程占用问题
1
2
3
4
5查询8080端口被哪个进程占用了
sudo lsof -P -i tcp:8080 | grep -i "listen"
关闭进程,PID改为进程号
sudo kill -9 PID
IDEA部署tomcat
- 新建
Jakarta EE
,模版选Web应用程序
,应用程序服务器选择tomcat目录,版本Jakarta EE10
,添加的依赖项Servlet
- 创建项目后,运行-编辑配置
- 打开浏览器用谷歌,执行更新操作选
重启服务器
,切换出IDE选更新类和资源
; - Url可以修改:
http://localhost:8080/yyds/
- 部署-应用程序上下文:
/yyds
- 启动运行后,自动弹出hello world页面,表示部署成功
- 打开浏览器用谷歌,执行更新操作选
- maven按钮有个package命令,可以将项目打包成war包,接着放到tomcat目录下的webapps文件夹内,就可以直接运行我Java编写的Web应用程序了,访问路径为文件的名称
Servlet动态网页响应
- 通过实现Servlet来进行动态网页响应,使用Servlet,不再是直接由Tomcat服务器发送我们编写好的静态网页内容(HTML文件),而是由我们通过Java代码进行动态拼接的结果,它能够很好地实现动态网页的返回
- Servlet通常用于HTTP协议通信
创建Servlet
- 只需要实现
Servlet
类即可,并添加注解@WebServlet
来进行注册1
2
3
4
public class TestServlet implements Servlet {
...实现接口方法
} - 输入
http://localhost:8080/yyds/test
, 能弹出空白页面表示成功 - 路径src/main/webapp,可以放个index.html进去,重启服务器时就会弹出这个页面
生命周期
- 启动一次服务器,然后访问我们定义的页面,然后再关闭服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class TestServlet implements Servlet {
public TestServlet(){
System.out.println("我是构造方法!");
}
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("我是init");
}
public ServletConfig getServletConfig() {
System.out.println("我是getServletConfig");
return null;
}
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("我是service");
}
public String getServletInfo() {
System.out.println("我是getServletInfo");
return null;
}
public void destroy() {
System.out.println("我是destroy");
}
}我是构造方法!
我是init
我是service
我是service(出现两次是因为浏览器请求了2次,是因为有一次是请求favicon.ico,浏览器通病)我是destroy
- Servlet的生命周期
- 首先执行构造方法完成 Servlet 初始化
- Servlet 初始化后调用init()方法
- Servlet 调用service()方法来处理客户端的请求
- Servlet 销毁前调用destroy()方法
- 最后,Servlet是由JVM的垃圾回收器进行垃圾回收的
- service方法中有两个参数,ServletRequest和ServletResponse,用户发起的HTTP请求,就被Tomcat服务器封装为了一个ServletRequest对象,我们得到是Tomcat服务器帮助我们创建的一个实现类,HTTP请求报文中的所有内容,都可以从ServletRequest对象中获取,同理ServletResponse就是我们需要返回给浏览器的HTTP响应报文实体类封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//首先将其转换为HttpServletRequest(继承自ServletRequest,一般是此接口实现)
HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println(request.getProtocol()); //获取协议版本
System.out.println(request.getRemoteAddr()); //获取访问者的IP地址
System.out.println(request.getMethod()); //获取请求方法
//获取头部信息
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()){
String name = enumeration.nextElement();
System.out.println(name + ": " + request.getHeader(name));
}
//转换为HttpServletResponse(同上)
HttpServletResponse response = (HttpServletResponse) servletResponse;
//设定内容类型以及编码格式(普通HTML文本使用text/html)
response.setContentType("text/html;charset=UTF-8");
//获取Writer直接写入内容
response.getWriter().write("<h1>椰奶yyds!</h1>");
//所有内容写入完成之后,再发送给浏览器
}
HttpServlet
- Servlet有一个直接实现抽象类GenericServlet,这个类完善了配置文件读取和Servlet信息相关的的操作,但是依然没有去实现service方法,仅仅是用于完善一个Servlet的基本操作。HttpServlet遵循HTTP协议的一种Servlet,继承自GenericServlet,它根据HTTP协议的规则,完善了service方法
- 简单的说,只需要继承HttpServlet来编写我们的Servlet就可以了,它提前实现了一些操作,能帮我们省去很多的时间
1
2
3
4
5
6
7
8
public class TestServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("<h1>恭喜你解锁了全新玩法</h1>");
}
}
@WebServlet注解
- @WebServlet注解用来快速注册一个Servlet,name属性就是Servlet名称,而urlPatterns和value实际上是同样功能,就是代表当前Servlet的访问路径
- 所有匹配
/test/随便什么
的路径名称,都可以访问此Servlet1
- 获取任何以js结尾的文件
1
- 如果没有找到匹配当前访问路径的Servlet,那么就会使用此Servlet进行处理;另外,此路径和Tomcat默认为我们提供的Servlet冲突,直接替换掉默认的
1
- 配置多个访问路径
1
- loadOnStartup属性,默认值为-1,表示不在启动时加载;修改为大于等于0的数来开启启动时加载,数字的大小决定了此Servlet的启动优先级
1
POST请求完成登陆
- 前端页面,点击登录按钮,会自动向后台发送一个POST请求,请求地址为当前地址+login
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>登录系统</h1>
<hr>
<form action="login" method="post">
<div>
<label>
<input type="text" placeholder="用户名" id="username" name="username">
</label>
</div>
<div>
<label>
<input type="text" placeholder="密码" id="password" name="password">
</label>
</div>
<div>
<input type="submit" value="登录" class="sub">
</div>
</form>
</body>
</html> - maven配置文件添加mybatis、mysql、lombok包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35<dependencies>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies> - mybatis配置文件,格式跟以前一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study"/>
<property name="username" value="root"/>
<property name="password" value="d6NFV8SYsEWPgTvq"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.example.mapper"/>
</mappers>
</configuration> - 创建一个实体类
1
2
3
4
5
6
public class User {
private int id;
private String username;
private String password;
} - mapper来进行用户信息查询
1
2
3
4public interface UserMapper {
User getUser(; String username, String password)
} - 用户在前端发送账号密码请求,后端收到后,向数据库发送校验信息,数据库返回校验结果,后端再向前端发送校验成功或失败信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class LoginServlet extends HttpServlet {
SqlSessionFactory factory;
public void init() throws ServletException {
//初始化一次mybatis
factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//首先设置一下响应类型
resp.setContentType("text/html;charset=UTF-8");
//获取POST请求携带的表单数据
Map<String, String[]> map = req.getParameterMap();
//判断表单是否完整
if(map.containsKey("username") && map.containsKey("password")) {
String username = req.getParameter("username");
String password = req.getParameter("password");
try(SqlSession sqlSession = factory.openSession(true)) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUser(username, password);
if (user != null) {
resp.getWriter().write("用户:" + username + ",登录成功!");
}else {
resp.getWriter().write("密码不正确或此用户不存在");
}
}
}else {
resp.getWriter().write("错误,您的表单数据不完整!");
}
}
}
上传和下载文件
- maven配置文件,添加一个
commons-io
包,作用是快速地编写IO代码1
2
3
4
5<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.7</version>
</dependency> - 首先将image.png放入到resource文件夹中,前端页面添加下载与上传功能;
enctype="multipart/form-data"
表示此表单用于文件传输1
2
3
4
5
6
7
8
9<a href="file" download="reimu.png">点我下载高清资源</a>
<form method="post" action="file" enctype="multipart/form-data">
<div>
<input type="file" name="test-file">
</div>
<div>
<button>上传文件</button>
</div>
</form> - @MultipartConfig注解表示此Servlet用于处理文件上传请求;上传路径修改成自己本地的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FileServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("image/png");
OutputStream outputStream = resp.getOutputStream();
InputStream inputStream = Resources.getResourceAsStream("reimu.png");
//直接使用copy方法完成转换
IOUtils.copy(inputStream, outputStream);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try(FileOutputStream stream = new FileOutputStream("/Users/ulan/Downloads/marisa.png")){
Part part = req.getPart("test-file");
IOUtils.copy(part.getInputStream(), stream);
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("文件上传成功!");
}
}
}
XHR请求数据
- 实现点击按钮刷新当前时间的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<div id="time"></div>
<br>
<button onclick="updateTime()">更新数据</button>
<script>
updateTime()
function updateTime() {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
document.getElementById("time").innerText = xhr.responseText
}
};
xhr.open('GET', 'time', true);
xhr.send();
}
</script> - 用于处理时间更新请求
1
2
3
4
5
6
7
8
9
10
11
public class TimeServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String date = dateFormat.format(new Date());
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write(date);
}
}
重定向与请求转发
- sendRedirect调用后,状态码设置为302,并添加Location属性表示重定向到哪一个网址
1
resp.sendRedirect("time");
- 自定义状态码与重定向到小破站
1
2resp.setStatus(302);
resp.setHeader("Location", "https://www.bilibili.com"); - 请求转发是指无法处理或转交给其他Servlet来处理;注意,路径规则需要填写Servlet上指明的路径,只能转发到内部的Servlet,不能转发给其他站点或是其他Web应用程序
1
req.getRequestDispatcher("/time").forward(req, resp);
- 如果是在doPost方法内进行转发,接收的Servlet没有doPost方法,就会出现405页面,原因是请求参数也一起被传递,因此写一个doPost方法就能解决问题
1
2
3
4
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
} - 请求转发setAttribute方法可以携带数据,请求转发后,可以获取到数据
1
2req.setAttribute("test", "我是请求转发前的数据");
req.getRequestDispatcher("/time").forward(req, resp);1
System.out.println(req.getAttribute("test"));
- 重定向与请求转发的区别
- 请求转发是一次请求,重定向是两次请求
- 请求转发地址栏不会发生改变, 重定向地址栏会发生改变
- 请求转发可以共享请求参数 ,重定向之后,就获取不了共享参数了
- 请求转发只能转发给内部的Servlet
ServletContext对象
- ServletContext全局唯一属于整个Web应用程序,通过
getServletContext()
来获取对象1
2
3ServletContext context = getServletContext();
context.setAttribute("test", "我是重定向之前的数据");
resp.sendRedirect("time");1
System.out.println(getServletContext().getAttribute("test"));
- 也可以请求转发
1
context.getRequestDispatcher("/time").forward(req, resp);
初始化参数
- 类似于初始化配置一些值,比如数据库连接相关信息,通过初始化参数来给予Servlet或是一些其他的配置项
1
2
3 - 它以键值对形式保存,通过getInitParameter方法获取
1
System.out.println(getInitParameter("test"));
- web.xml内,还能定义全局初始化参数
1
2
3
4<context-param>
<param-name>幽蓝</param-name>
<param-value>我是全局初始化参数</param-value>
</context-param> - 使用ServletContext来读取全局初始化参数
1
2ServletContext context = getServletContext();
System.out.println(context.getInitParameter("幽蓝"));
Cookie
- 添加cookie数据,并重定向到time页面
1
2
3Cookie cookie = new Cookie("test", "yyds");
resp.addCookie(cookie);
resp.sendRedirect("time"); - 重定向后会携带cookie数据,通过getCookies获取数据并打印到控制台
1
2
3for (Cookie cookie : req.getCookies()) {
System.out.println(cookie.getName() + ": " + cookie.getValue());
} - cookie信息
- name:Cookie的名称,Cookie一旦创建,名称便不可更改
- value:Cookie的值,如果值为Unicode字符,需要为字符编码。如果为二进制数据,则需要使用BASE64编码
- maxAge:Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为-1
- secure:该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false
- path:Cookie的使用路径。如果设置为”/sessionWeb/“,则只有contextPath为”/sessionWeb”的程序可以访问该Cookie。如果设置为”/“,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为”/“
- domain:可以访问该Cookie的域名。如果设置为”.google.com”,则所有以”google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为”.”
- comment:该Cookie的用处说明,浏览器显示Cookie信息的时候显示该说明。
- version:Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2109规范
最关键的其实是name、value、maxAge、domain属性
- 修改一下maxAge,再来看看失效时间
1
cookie.setMaxAge(20);
Session
- Session会给浏览器设定一个叫做
JSESSIONID
的Cookie,值是一个随机的排列组合,而此Cookie就对应了你属于哪一个对话,只要我们的浏览器携带此Cookie访问服务器,服务器就会通过Cookie的值进行辨别,得到对应的Session对象,因此,这样就可以追踪到底是哪一个浏览器在访问服务器 - 用户登录成功后,再将用户对象添加到Session中,只要是此用户发起的请求,我们都可以从
HttpSession
中读取到存储在会话中的数据1
2HttpSession session = req.getSession();
session.setAttribute("user", user); - 如果用户没有登录就去访问首页,那么我们将发送一个重定向请求,告诉用户需要进行登录才可以访问
1
2
3
4
5
6HttpSession session = req.getSession();
User user = (User) session.getAttribute("user");
if(user == null) {
resp.sendRedirect("login");
return;
} - 设置Session过期时间
1
2
3<session-config>
<session-timeout>1</session-timeout>
</session-config> - 立即失效
1
session.invalidate();
Filter
- 过滤器相当于在所有访问前加了一堵墙,来自浏览器的所有访问请求都会首先经过过滤器,只有过滤器允许通过的请求,才可以顺利地到达对应的Servlet,而过滤器不允许的通过的请求,我们可以自由地进行控制是否进行重定向或是请求转发。并且过滤器可以添加很多个,就相当于添加了很多堵墙
- 实现Filter接口,并添加
@WebFilter
注解1
2
3
4
5
6
7//路径的匹配规则和Servlet一致,这里表示匹配所有请求
public class TestFilter implements Filter {
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
}
} - 允许经过此过滤器
1
filterChain.doFilter(servletRequest, servletResponse);
- 用户在未登录情况下,只允许静态资源和登陆页面请求通过,登陆之后畅行无阻
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MainFilter extends HttpFilter {
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String url = req.getRequestURL().toString();
//判断是否为静态资源
if(!url.endsWith(".js") && !url.endsWith(".css") && !url.endsWith(".png")){
HttpSession session = req.getSession();
User user = (User) session.getAttribute("user");
//判断是否未登陆
if(user == null && !url.endsWith("login")){
res.sendRedirect("login");
return;
}
}
//交给过滤链处理
chain.doFilter(req, res);
}
}
Listener
- 在应用程序加载的时候,或是Session创建的时候,亦或是在Request对象创建的时候进行一些操作,可以使用监听器来实现
1
2
3
4
5
6
7
public class TestListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent se) {
System.out.println("有一个Session被创建了");
}
}