java框架学习
springboot+springmvc
前端学习
html
特点
事例
css
三种引入方式
css选择器
javascript
基础语法
输出语句的三种方式
变量
1.可以重复定义;2.作用域是全局
变量的数据类型
函数
数组对象
String对象
Json对象
现在常用于数据载体,在网络进行数据传递
Bom对象
window对象
DOM
将标记语言各个组成部分封装成对应对象
基本上包括两个操作
- 通过上面的元素对象方法获取到标签对应的元素对象
- 查询各个对象的方法,完成对应的修改状态的操作
事件监听
事件绑定
Vue
v-if是没有满足条件,则浏览器不会进行渲染,而v-show当没有满足条件时,浏览器会渲染,但是会隐藏该元素,不进行显示
生命周期
在对应的生命周期,就会自动调用该生命周期里面的方法
Ajax
在服务器端和前端进行数据交互,并且是异步的方式,可以只更新部分页面
同步的话当进行服务器端的数据请求的时候,必须要等到服务器响应返回之后才能进行其他操作,比如点击超链接,在进入新网址的过程就是同步的操作
原生Ajax
但原生的Ajax操作比较繁琐,所以现在用的比较多的是它的升级版,比如axios
Axios
简化上面的操作
前后端分离开发
一个功能叫做一个接口,通过接口文档来规范前后端开发的要求
接口文档管理平台比如
YAPI
前端工程化
环境准备
脚手架 vue-cli
对于第二种方法 图形化界面
vue项目结构
vue项目的默认首页
vue项目中每个vue文件的结构如下
前端组件
使用elementUI的步骤
这个引入ElementUI组件库是在main.js中引入
然后编写好vue文件之后在app.vue中进入引入,就可以显示
案例:根据页面完成员工管理页面开发,通过Axios完成数据的异步加载
对于第一步第二步都可以在elementUI里面直接找到 进行复制添加
对于第三步,首先进行安装引入
Vue路由
首先配置路由信息
然后在对应菜单位置加入router-link
接着在APP.vue中加入router-view 进行显示
最后要设置一个根路径 对其进行重定向
vue项目打包部署
打包
部署
Maven
Maven安装
在idea中进行maven的配置
创建maven项目
Maven中依赖具有传递性
Maven依赖也可以设置有效范围
生命周期
Web
创建SpringBoot项目
定义一个请求处理类
让这个类成为请求处理类,主要是因为下面两个注解,@RestController和@RequestMapping
1
2
3
4
5
6
7
8
@RestController
public class hellocontroller {
@RequestMapping(value = "/hello",method = RequestMethod.GET)
public String hello(String name){
return "hello "+name;
}
}
http协议
http请求数据格式
http响应数据格式
对于响应行、响应头各个参数的意义
遇到报错的时候,可以在开发者工具看看状态码来定位错误的位置
http协议的解析
serverSocket可以协议解析
服务器端对于http协议的解析很复杂,需要考虑各种情况,但因为http格式的固定的,所以有很多已经封装好的http解析程序,包括tomcat,jetty等
Tomcat
Tomcat是一款流行的开源Web服务器和Servlet容器,它提供了Java Servlet和JavaServer Pages(JSP)运行环境。
Tomcat允许开发人员编写Java Web应用程序并将其部署到服务器上,以便客户端可以访问这些Web应用程序。
springBoot内嵌了tomcat,所以不需要再下载安装tomcat,直接使用springboot即可
前后端请求
springboot底层有一个DispatcherServlet,可以和tomcat对接,tomcat从浏览器获取的http信息之后会被DispatcherSerlet封装城HttpServletRequest对象,然后发送给对应的controller类,同理响应也是通过它
请求过程中的常见参数及封装
对于简单的参数,服务器端获取可以通过HttpServletRequest对象来获取,然后使用该参数
这种方式比较繁琐,并且需要手动的进行类型转化,所以一般不用,springboot给了一种更简单的方式,直接定义变量
请求名可以和参数名不一致,使用注解@RequestParam完成映射
该注解有required属性,可以设置false,表示该参数可以选择传递也可以不传
对于pojo类型的参数
如果参数太多,可以考虑将这些参数封装到实体类当中,即pojo对象,然后进行传递,比如:
1
2
3
4
5
6
7
8
9
@PostMapping("/user")
public String save(user user){
int i=usermapper.insert(user);
if(i>0){
return "success";
}else {
return "fail";
}
}
对于数组类型的参数
比如前端遇到一个多选项,这个时候就可以使用数组的方式进行数据的传递
也可以使用集合的方式,不过默认是数组,所以需要@RequestParam进行绑定,@RequestParam也可以用于设置默认值
1
2
3
4
5
@RequestMapping ("/listParam")
public String listParam(@RequestParam List<String> hobby) {
System.out.printIn(hobby) ;
return "OK";
}
对于日期类型的参数
对于json类型的参数
json格式的参数只能使用post方式进行请求,在postman中Body请求体中设置参数,选择raw,json格式,然后输入json中各个参数的值
需要使用到注解@RequestBody
,主要用于post请求,将json的请求体转化为对象,它是 Spring 框架中的一个注解,它用于将 HTTP 请求的内容体(body)部分绑定到方法的参数上,将json格式的数据封装到pojo对象上
对于路径类型的参数
前后端响应
后端数据进行响应的时候,其实依赖于一个注解@ResponseBody,该注解的作用是将方法返回值直接响应,如果返回对象是实体或者集合,就自动转化为json格式进行响应
一般直接用@RestController=@Controller+@ResponseBody
为了方便管理,一般都使用统一的响应结果类,包含响应码,返回数据等
案例
编写其中的Controller类
分层解耦
如果加载数据,处理处理,响应都写在一个方法里,复用性维护性差,所以考虑分层
分层
三层架构
mapper层进行数据访问,service层进行逻辑处理,controller层进行接受请求,数据响应
对于mapper层这种,可能存在获取数据的方式有多种,所以对于每一种数据单独创建一个interface接口,然后再创建对应的impl实现类进行实现
解耦
上面的写法耦合度很高,比如说上面的service层修改了类名,或者要换一个实现类,就还要改controller层里的代码,这样代码维护困难
解耦的操作是定义一个容器,比如将创建好的service对象存放在容器里,controller层通过容器来调用该对象,进行对应的操作
对于这个容器怎样存储对象,和怎么从容器中提取对象来进行操作,spring提供了相应的操作,控制反转和依赖注入
在springboot中这两个操作都可以通过注解完成
控制反转:通过@Component注解,将对应实现类交与IOC容器进行管理
依赖注入:通过@Autowired注解,将容器里的对象注入运行
所以对于service层和mapper层定义接口也是有必要的,修改的是实现类,通过依赖注入,直接调用接口的名
当要切换实现类时,直接删除对应的component注解,从容器中剔除即可
IOC详解
对不同的层使用不同的component注解,方便管理,对于不属于这三层的一些工具类可以使用component注解进行ioc管理
对于spring怎么去找这些component注解,spring提供了一个叫@ComponentScan的注解,该注解可以去扫描一些包的位置,去找到这些bean对象
Springboot底层中默认还有该注解,扫描的是当前包及其子包,如果在其他位置时,就需要在项目入口文件中添加@ComponentScan这个注解,将所有的bean对象的位置包含进去
DI详解
如果存在同一个类型的多个bean对象,就会出错,不知道该自动注入哪一个bean,但可以通过下面注解进行解决
MySQL
语法
DDL
创建表时要注意数据类型,字段一共有三种数据类型
修改表操作
DML
DQL
分组查询
分页查询
多表设计
一对多的表结构
当多表之间存在联系的时候,如果删除一个表的字段,就要考虑对另一个表的相应数据进行修改,这个时候就可以使用外键进行关联
上述外键属于物理外键,存在下面的缺点
- 影响增删改查效率
- 仅适用于单一的数据库,不适用于分布式,集群的场景
- 容易引发死锁等
一对一的表结构
多对多的表结构
对于多对多的结构,不能直接使用外键,因为外键是两张表一对一或者一对多的关系,所以对于多对多的结构,可以添加一个中间表,成为两个一对多
创建表的步骤
-
了解需求,分析各个模块涉及到的表结构,及表之间的关系
-
根据要求,分析具体的字段及约束
多表查询
当直接select * from table1,table2的时候,查询的数据的数量是num(table1)*num(table2),是两个表进行笛卡尔积的数量,所以真正需要的数据需要where条件,从这笛卡尔积之后的数据中进行筛选,拿到真实的数据
多表查询分为两种,一种是连接查询,一种是子查询
内连接查询
外连接查询
都是从笛卡尔积中进行查找,只是有对应的偏重
左外连接就表示完全包含左表的数据,对应右表的字段要是没有就null,右外连接对应
子查询
行子查询
表子查询
事务
将多个步骤进行绑定,要成功全部成功,要失败都失败
事例:
事务的四大特性
索引
当数据库的数据量比较大的时候,普通查询速度会很慢,这个时候可以使用索引的方式来进行数据查询
利用类似于二叉搜索树的数据结构来进行搜索,所以很高效
但使用普通二叉搜索树或者红黑树也有缺点,就是因为只有两个子节点,所以当数据量过大时,树的高度过大,也会导致索引速度变慢
使用b+树
索引的操作
mybatis
用于简化JDBC的开发
操作步骤
在编写mapper里面的注解的语句的时候,是没有错误提示的,但是可以进行配置
JDBC
使用java语言操作关系型数据库的一套API,是sun公司官方定义的一套操作数据库的规范,只提供接口,各个数据库厂商需要去实现这些接口,提供数据库驱动的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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.sql.*;
public class JdbcDemo {
public static void main(String[] args) throws SQLException {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 加载MySQL驱动程序
Class.forName("com.mysql.jdbc.Driver");
// 连接到数据库
String url = "jdbc:mysql://localhost:3306/mydatabase";
String user = "root";
String password = "mypassword";
conn = DriverManager.getConnection(url, user, password);
// 执行查询
stmt = conn.createStatement();
String sql = "SELECT * FROM mytable";
rs = stmt.executeQuery(sql);
// 处理结果集
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.println("ID: " + id + ", Name: " + name + ", Age: " + age);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
// 关闭连接和资源
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
}
}
}
数据库连接池
如果没有数据库连接池,每次要操作数据库的时候,都要建立链接,然后释放连接,有了数据库连接池,会提前初始化一部分,达到连接的复用
Lombok
帮助简化pojo类编写的简化,这样pojo类就只需要声明变量和注解就可以了,需要提前引入依赖
原理就是在编译的时候,根据注解生成对应的方法
使用的时候,需要lombok的插件,不过一般安装idea的时候都自动装好了
mybatis操作
基于注解编写sql语句
操作事例
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
@Component
@Mapper
public interface UserMapper {
//查询所有用户
@Select("SELECT * FROM user")
List<User> findAll();
//根据id查询用户
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(Long id);
//新增用户
@Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
void save(User user);
//修改用户
@Update("UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}")
void update(User user);
//删除用户
@Delete("DELETE FROM user WHERE id = #{id}")
void delete(Long id);
//条件查询
@Select("SELECT * FROM user WHERE age > #{age}")
List<User> findByAgeGreaterThan(@Param("age") int age);
//条件查询 使用了concat,因为#{}预编译就会变成?,两边不能有''
@Select("SELECT * FROM user WHERE name LIKE CONCAT('%',#{keyword},'%')") List<User> findByNameContaining(@Param("keyword") String keyword);
}
预编译SQL的优势
- 提高性能:由于预编译 SQL 将 SQL 语句编译为可重复使用的执行计划,避免了每次执行 SQL 都需要进行编译的开销。这样可以减少数据库的工作量,提高查询效率,尤其在频繁执行相同 SQL 的情况下,性能提升更为明显。 提前编译好sql语句到缓存,每次只需要把数据放进去即可
- 安全性增强:预编译 SQL 使用占位符来表示参数,而不是直接将参数值嵌入到 SQL 语句中。这样可以有效防止 SQL 注入攻击,因为参数值不会被解释成 SQL 片段。比如在输入密码的时候输入 后面的句子 or ‘1’=‘1’ 这样会把这个拼接到sql语句上,这样就直接能通过
- 代码可读性和维护性:使用预编译 SQL 可以将 SQL 语句与代码分离,使代码更加清晰易读。同时,当需要修改 SQL 语句时,只需修改 SQL 语句本身,而不需要修改代码。
在mybatis中#{id}就是预编译sql,${id}就是直接拼接
新增数据的时候可能会涉及到的主键返回
当进行数据查询时,返回的查询数据只会是返回pojo变量名和数据库字段名相同的部分,其他的就不会封装
解决方法
名称问题
基于xml文件编写sql语句
动态SQL—-if标签
动态SQL—-批量删除
动态SQL—-sql语句的复用
Web综合
首先进行环境搭建
Rest风格
分页查询
像这种需要返回多种不同类型的数据,可以将该数据封装成一个对象,再进行返回
使用PageHelper插件可以简化分页查询操作
使用动态查询语句的时候,比如说name传递过来,不添加名字的话,String类型会自动给name赋值一个“ ”,这样的话就会出问题,所以写动态查询语句的时候,还需要判断name!=” “
文件上传
文件上传是指讲图片,视频等上传到服务器上,实现这个功能前端需要用到表单功能,方式是post,enctype的格式是“multipart/form-data”
后端用MultipartFile类型来进行接受
本地存储
通过 photo.transferTo(file)存储文件
缺点:存储本地占内存不好,而且前端还不好访问
云存储
阿里云oss
参数配置化
将参数都加到properties文件中,然后用value注解在java类中进行调用
yml文件配置
对于参数配置化,使用@Value注解缺点在于没一个都要加的话,过于繁琐,可以使用另一种方式
使用的步骤是,首先可以将要使用的变量定义一个类,然后使用@Component注解将其进行控制元转,加入bean对象,然后使用@ConfigurationProperties注解将配置文件中的参数进行引入,然后再在工具类中对该类进行注入,就可以直接使用这些变量
可以引入依赖,这样写yml文件时,就可以有提示
系统登录
登陆校验
当进行页面请求的时候,首先要判断员工是否登陆
会话技术
通过会话技术来进行服务器端和客户端数据请求的判断
基于cookie的会话追踪的方式
基于session的会话追踪的方式
Session是一种服务器端的数据存储技术,依赖于Cookie技术。它由服务器创建,默认存储时间为30分钟,解决了同一个用户不同请求的数据共享问题。每个用户独立拥有一个session,只要不关闭浏览器,并且session不失效,同一用户的任意请求获取的都是同一个session。Session的作用是解决一个用户的不同请求的数据共享问题。
Session的优点是相比传统的cookie存储所有用户信息来讲,session的数据存储服务器端,用户数据更安全。每次请求不需要在请求体或者请求接口中携带用户信息给服务端。
在使用Session时,有一些Session使用需要注意:
- Session的生命周期管理:Session会占用服务器内存,因此要确保在不需要时及时销毁Session,以减轻服务器的负担。
- Session的大小:不要在Session中存储大型对象或大量数据,以免占用过多的服务器内存。
- 安全性:Session中可能包含敏感信息,因此要确保Session数据的安全传输和存储。
- 分布式环境:如果您的应用程序在多台服务器上运行,要确保Session数据能够在这些服务器之间共享。
- Session超时:设置合理的Session超时时间,以确保用户不会在长时间不活动后被自动注销。
ession机制很好的解决了Cookie的不足,但是当访问应用的用户很多时,服务器上就会创建非常多的Session对象,如果不对这些Session对象进行处理,那么在Session失效之前,这些Session一直都会在服务器的内存中存在。那么就出现了Session活化和钝化的机制。Session钝化是指Session在一段时间内没有被使用时,会将当前存在的Session对象序列化到磁盘上,而不再占用内存空间。Session活化是指Session被钝化后,服务器再次调用Session对象时,将Session对象由磁盘中加载到内存中使用。
cookie和session的区别
Session和Cookie都是用于存储和跟踪用户信息的技术,但它们的工作方式和存储位置有所不同。
-
存储位置:Cookie保存在客户端(即用户的浏览器),而Session保存在服务器端。
-
存储数据类型:Cookie只能保存ASCII字符,而Session可以保存任意类型的数据,通常用于保存用户的登录状态、购物车信息等。
-
有效期:Cookie的有效期可以设置很长,甚至永久,而Session的有效期通常较短,用户关闭浏览器或者Session超时后就会失效。
-
安全性:由于Cookie保存在客户端,因此安全性较差,可能被用户篡改或者窃取。而Session保存在服务器端,安全性较好。
-
存储大小:单个Cookie的存储大小不能超过4KB,而Session可以存储的数据远大于Cookie。
Session和Cookie之间的关系通常是:当用户第一次访问服务器时,服务器会创建一个Session,并将Session的ID通过Cookie发送给用户的浏览器。当用户再次访问服务器时,浏览器会将Cookie中的Session ID发送给服务器,服务器根据Session ID找到对应的Session,从而实现用户的状态跟踪。
基于token的会话追踪的方式
JWT令牌方式
- 结构: JWT由三个部分组成,由点分隔:标头(Header)、负载(Payload)和签名(Signature)。
- 标头: 包含令牌类型(JWT)和所使用的签名算法。
- 负载: 包含声明。声明是关于实体(通常是用户)和其他数据的陈述。
- 签名: 通过对标头、负载和密钥的签名生成,确保令牌的完整性。
- 用途: 用户登录后,服务器生成JWT,然后将其发送到客户端。客户端在后续请求的标头中包含JWT以访问受保护的资源。
- 声明: JWT可以包含标准声明(例如”iss”表示颁发者和”exp”表示过期时间)或自定义声明。
- 身份验证: 服务器使用密钥验证JWT的签名,以确保其完整性。它可以信任令牌中的信息而无需查询数据库。
- 无状态性: JWT是无状态的,意味着所有必要的信息都包含在令牌本身中。这减少了需要服务器端存储和身份验证期间的数据库查询的需求。
jwt的生成
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
package com.example.sp_demo1.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class Jwtutils {
//设置7天过期
private static long expire=604800;
//32位秘钥
private static String serect="abcdefghabcdefghabcdefghabcdefgh";
//生成token
public static String generateToken(String username){
Date now=new Date();
Date expirtion=new Date(now.getTime()+1000*expire);
return Jwts.builder()
.setHeaderParam("type","JWT")
.setSubject(username)
.setIssuedAt(now) //设置生成时间
.setExpiration(expirtion) //设置过期时间
.signWith(SignatureAlgorithm.HS512,serect) //指定一下加密算法 签名算法
.compact(); //合成
}
//解析token 即进行验证
public static Claims getClaimsByToken(String token){
return Jwts.parser()
.setSigningKey(serect) //通过密钥进行解析
.parseClaimsJws(token)
.getBody();
}
}
相较于session和cookie的优势
- 跨域通信: JWT 可以轻松在不同域之间传递信息,而使用传统的 Cookie 和 Session 方案则需要处理跨域问题。
- 前后端分离: JWT 适用于前后端分离的架构,因为它们是无状态的,不需要在服务器端存储会话信息。相比之下,传统的 Cookie 和 Session 方案需要服务器端存储会话状态,对后端造成一定的负担。
- 轻量级: JWT 的数据结构相对较小,因为它是基于 JSON 的文本格式,传输和处理都更为轻量。而传统的 Cookie 数据会被存储在客户端,并且在每次请求时都会被发送到服务器,可能包含大量不必要的信息。
- 无状态性: JWT 是无状态的,包含了足够的信息在令牌本身,不需要在服务器端保持状态。Cookie 和 Session 方案需要服务器保持状态,通常通过存储在服务器上的会话信息实现。
- 灵活性: JWT 允许开发者定义自己的声明,因此具有更大的灵活性。相比之下,Cookie 和 Session 方案通常有固定的结构和功能。
- 安全性: 当使用 HTTPS 时,JWT 可以提供足够的安全性。然而,需要谨慎处理令牌的签名和过期时间以防止滥用。 Cookie 和 Session 方案也需要一些额外的安全考虑,例如防止会话劫持和 CSRF 攻击。
使用jwt令牌
在loginController中,当判断数据库中有该用户时,就根据该用户的信息生成对应的jwt令牌,然后返回给前端,前端存储后,之后的每一次请求的时候,前端都会将jwt令牌取出来,加在请求头header中,携带给服务端,请求头的名称为token,
服务器端就会每一次统一拦截,进行jwt的校验,有下面两种方式:
Filter过滤器
- 过滤器接口: 在Java Servlet中,过滤器是实现了
javax.servlet.Filter
接口的Java类。该接口定义了三个主要方法:init
(初始化),doFilter
(实际的过滤逻辑),和destroy
(销毁)。 - 过滤器链: 多个过滤器可以按照一定的顺序链接形成过滤器链。每个过滤器都有机会在请求被传递到目标资源之前或之后执行自定义的逻辑。
- 过滤器的应用场景:
- 日志记录: 记录请求和响应的信息,用于调试和分析。
- 身份验证和授权: 检查用户是否有访问特定资源的权限。
- 数据压缩: 在服务器端压缩响应数据,减少传输时间。
- 字符编码: 转换请求和响应的字符编码,确保一致性。
- 防范XSS攻击: 过滤请求中的恶意脚本,提高安全性。
- 过滤器生命周期:
- 初始化(init): 在容器启动时调用,用于初始化过滤器的配置。
- 过滤(doFilter): 对请求进行处理,并将请求传递到下一个过滤器或目标资源。
- 销毁(destroy): 在容器关闭时调用,用于释放资源。
- 配置过滤器: 过滤器可以通过在
web.xml
文件中配置或使用注解方式配置,指定它们的拦截路径和顺序。
快速入门
例子
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
import javax.servlet.*;
import java.io.IOException;
import java.util.Date;
@WebFilter(urlPatterns="/*")
public class LogFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
// 初始化代码,可获取配置参数等
System.out.println("LogFilter initialized");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 过滤逻辑,在请求前后执行
long startTime = System.currentTimeMillis();
System.out.println("Request received at " + new Date(startTime));
// 将请求传递给下一个过滤器或目标资源 往下放行,让其进行对应请求
chain.doFilter(request, response);
// 请求处理完成后的操作
long endTime = System.currentTimeMillis();
System.out.println("Request processed at " + new Date(endTime));
System.out.println("Request processing time: " + (endTime - startTime) + " milliseconds");
}
public void destroy() {
// 销毁代码,释放资源
System.out.println("LogFilter destroyed");
}
}
因为filter是javaweb的方式,所以要在启动类上加上注解@ServletComponmentScan
过滤器链
过滤器之间的优先级根据过滤器类名决定
滤波器登录校验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/secured/*")
public class JwtAuthenticationFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
// 初始化代码,可获取配置参数等
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 获取 Authorization 头
String token = httpRequest.getHeader("Authorization");
// 校验 JWT
if (token != null && token.startsWith("Bearer ")) {
try {
// 去除 "Bearer " 前缀
String jwt = token.substring(7);
// 验证 JWT 签名
Jws<Claims> claims = Jwts.parser()
.setSigningKey("yourSecretKey") // 替换成你的密钥
.parseClaimsJws(jwt);
// 如果验证通过,将用户信息放入请求
request.setAttribute("user", claims.getBody().getSubject());
chain.doFilter(request, response);
} catch (Exception e) {
// 验证失败,重定向到登录页面或返回错误信息
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.getWriter().write("Unauthorized: " + e.getMessage());
}
} else {
// 未提供有效的 JWT,重定向到登录页面或返回错误信息
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.getWriter().write("Unauthorized: Missing or invalid token");
}
}
public void destroy() {
// 销毁代码,释放资源
}
}
Interceptor拦截器
操作步骤
代码实例:
拦截器类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
// 在请求处理之前调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url=request.getRequestURI().toString();
//如果访问登陆界面,直接放行
if(url.contains("login")){
return true;
}
//如果不是访问登陆界面,则需要判断token
String token=request.getHeader("token");
//判断是否存在 如果不存在返回报错
if(!StringUtils.hasLength(token)){
Result result=Result.error("not_login");
String notlogin=JSONObject.toJSONString(result);
response.getWriter().write(notlogin);
return false;
}
//进行token的校验
try {
Jwtutils.generateToken(token);
}catch (Exception e){
e.printStackTrace();
Result result=Result.error("not_login");
String notlogin=JSONObject.toJSONString(result);
response.getWriter().write(notlogin);
return false;
}
//校验成功放行
return true;
}
}
// 在请求处理之后调用,但在视图渲染之前
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("Interceptor: PostHandle");
// 在这里可以修改ModelAndView等
}
// 在整个请求完成后调用,也就是视图渲染完毕时
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("Interceptor: AfterCompletion");
// 在这里可以进行一些资源清理工作
}
}
配置类,将其注册到 Spring 的配置中,并配置要拦截的路径。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器,并指定拦截路径
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/secured/**");
}
}
拦截路径的配置
filter和interceptor的区别
- 作用范围:
- Filter: 操作在 Servlet 容器级别,可以拦截所有请求和响应。Filter 配置在
web.xml
中,对整个应用生效。 - Interceptor: 操作在 Spring MVC 的控制器层面,只能拦截 Spring MVC 的请求。Interceptor 配置在 Spring 的上下文配置中,对 Spring MVC 控制器生效。
- Filter: 操作在 Servlet 容器级别,可以拦截所有请求和响应。Filter 配置在
- 依赖关系:
- Filter: 是 Servlet 规范中的一部分,独立于应用框架。它不依赖于 Spring 或其他框架。
- Interceptor: 是 Spring MVC 的一部分,依赖于 Spring 框架。
- 执行时机:
- Filter: 在请求进入 Servlet 容器后,但在进入 Servlet 之前执行。在请求离开 Servlet 容器时,但在返回给客户端之前执行。
- Interceptor: 在请求进入 Spring MVC 控制器前、控制器处理请求后视图渲染前,以及整个请求完成后三个时机执行。
- 配置方式:
- Filter: 通过在
web.xml
文件中配置<filter>
和<filter-mapping>
来注册 Filter。 - Interceptor: 通过实现
HandlerInterceptor
接口并配置在 Spring 配置类中注册。
- Filter: 通过在
- 对应用的影响:
- Filter: 更通用,可以处理一些与框架无关的全局逻辑,如字符编码、安全性等。
- Interceptor: 更专注于处理与框架相关的业务逻辑,如权限控制、日志记录等。
- 异常处理:
- Filter: 需要通过
FilterChain
的doFilter
方法传递异常。 - Interceptor: 有专门的
afterCompletion
方法处理异常。
- Filter: 需要通过
在实际开发中,通常使用 Filter
处理与框架无关的全局逻辑,而使用 Interceptor
处理与框架相关的业务逻辑。选择使用哪种机制取决于需求和应用的架构。
异常处理
定义全局异常处理器
事务管理
添加事务管理的开关
事务的回滚
默认是只有运行时异常才会进行回滚,不过也可以进行设置
事务的属性
事务的传播行为
AOP
AOP(Aspect-Oriented Programming)是一种编程范式,用于增强代码的模块性和可维护性。AOP 主要关注横切关注点(cross-cutting concerns),即那些存在于应用程序多个模块中的关注点,例如日志、事务、安全性等。AOP 通过将这些关注点从主业务逻辑中分离出来,实现了更好的代码结构和可维护性。
利用动态代理的技术,对spring中的方法进行编程
- 切面(Aspect): 切面是横切关注点的模块化单元。它包含了一组通知和切点。通知定义了在何时、何地执行横切逻辑,而切点定义了何处应用这些通知。
- 通知(Advice): 通知是切面的具体行为。它定义了在切点何时、何地执行的代码逻辑。主要有以下几种类型:
- 前置通知(Before): 在目标方法执行前执行。
- 后置通知(After): 在目标方法执行后执行,无论方法是否抛出异常。
- 返回通知(AfterReturning): 在目标方法正常返回后执行。
- 异常通知(AfterThrowing): 在目标方法抛出异常后执行。
- 环绕通知(Around): 在目标方法执行前后都可以执行自定义逻辑,可完全控制目标方法的执行。
- 切点(Pointcut): 匹配连接点的条件,切点定义了通知应该被执行的地方,通常使用表达式匹配方法或类。
- 连接点(Join Point): 可以被AOP控制的方法,在程序执行过程中能够插入切面的点,通常是方法调用或异常抛出时。
- 织入(Weaving): 织入是将切面应用到目标对象并创建新的代理对象的过程。织入可以在编译时、类加载时、运行时进行。
Spring AOP 是基于代理的 AOP 实现,通过 JDK 动态代理和 CGLIB 实现代理。Spring AOP 不需要修改源代码,通过配置即可实现横切关注点。
AOP操作
AOP对目标对象进行增强之后,进行注入的就是新的增强后的对象,然后进行调用使用
通知执行的顺序
也是和类名的字母排序有关系,也可以加@Order注解来定义顺序
切入点表达式
切入点表达式有两种
execution
对于不同的方法,如果想用一个切入点,可以使用或来添加
annotation
主要应用于无规则的一些方法,即有一些方法不好找共同点
自定义注解
连接点
在通知中获取目标对象的各种信息
案例:
对系统crud方法加入日志输入数据库
-
首先在数据库中创建这样一个表
-
创建这个表对应的mapper,写好insert方法
-
因为只对crud四个方法aop操作,所以使用annotation注解,所以创建这样一个类
-
因为要记录开始时间,结束时间,所以用around注解
-
接着编写方法
Spingboot原理
配置文件优先级
也可以在java系统属性和命令行参数的方式进行配置
优先级:命令行参数》java系统属性〉配置文件
bean对象
获取bean对象
bean的作用域
通过在controller类等类上通过@Scope注解设置作用域
第三方bean
springboot两大优势:起步依赖,自动配置
起步依赖
依靠maven的依赖传递,所以只需要一个起步依赖
自动配置
原理
1
2
3
4
5
6
7
8
@SpringBootApplication--》@EnableAutoConfiguration--》@AutoConfigurationPackage--》@Import({AutoConfigurationImportSelector.class})--》public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
这里引入@Conditional注解,来判断哪些对象需要被注册为ioc的bean对象,一共有三种方式
案例:
自定义starter
需求:
多环境开发
也可以
可以在命令行中进行配置
当maven和boot中的环境冲突时,可以以maven为主导,然后在maven中进行配置
还要对资源文件进行解析,不然无法传递
Maven高级
分模块设计
将项目分为若干个模块,分不同人管理,想要用另一个模块,就倒入另一个模块的依赖即可
继承与聚合
继承关系的实现
一般打包方式是jar,内涵tomcat
版本锁定
统一版本在父工程中配置编写
聚合
当模块都进行分开设计之后,我要对其中的主模块进行打包的时候,就需要先将它所依赖的其他模块都给install,放到自己的本地仓库之后,才能进行打包,这是很复杂繁琐的一个工程,聚合就是用于解决这个问题的
然后在父工程进行package就可以了
私服
资源的上传下载
SSM
IOC
创建Java类
- 编写业务类: 创建你需要在IoC容器中管理的Java类。例如,创建一个简单的服务类。
编写XML配置文件
-
创建XML文件: 在项目中创建一个XML文件(通常命名为
applicationContext.xml
)。 -
配置Bean: 在XML文件中,定义你的业务类作为bean。例如:
1 2
xmlCopy code <bean id="myService" class="com.example.MyService"/>
初始化Spring容器
-
加载配置文件: 在你的应用程序入口(如main方法)中,使用
ClassPathXmlApplicationContext
来加载XML配置文件。1 2
javaCopy code ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
使用IoC容器
-
获取Bean: 使用容器获取bean的实例。例如:
1 2
javaCopy code MyService service = context.getBean("myService", MyService.class);
实例化bean的方法:
第一种 构造方法
必须得是无参的方法,不然就会报错
第二种 静态工厂
第三种 实例工厂
对于第三种方式,进行了优化,得到了下面这种常用的方法FactoryBean的方式
bean的生命周期:
或者使用接口来配置
关闭容器用的两个操作:
- close()
- registerShutdownHook()
区别:
-
close(): 需要程序员显式调用,立即关闭容器。
-
registerShutdownHook(): 在JVM关闭时自动调用,容器会在JVM关闭时优雅地关闭。
registerShutdownHook()
方法同样定义在ConfigurableApplicationContext
接口中。这个方法用于注册一个JVM shutdown hook。当JVM尝试关闭时(例如,用户按下Ctrl+C),这个hook会被调用,从而优雅地关闭Spring容器。
选择使用哪种方法取决于你的具体需求和应用程序的上下文。如果需要在应用程序结束时立即释放资源,可以使用 close()
。如果希望在JVM关闭时自动清理资源,可以使用 registerShutdownHook()
。
DI
依赖注入也有2种方式
-
第一种方式使用setter的方式
当有一些变量的时候,可以用value进行引入
-
利用构造的方法进行注入
和setter的区别就是换成了构造器,标签名字换了
依赖的自动注入
类里面的方法一样,set还是一样的写,只是在xml上面的编写上有区别,可以不用写property,按类型或者名字自动装配
管理第三方数据源
然后在方法中用ApplicationContext context = new ClassPathXmlApplicationContext(“applicationContext.xml”);提取
将数据源对象封装到properties中
容器
容器初始化的方式
ApplicationContext是后来生成的,最开始生成容器的方式是BeanFactory,下面是BeanFactory加载文件的方式
总结
注解
也可以省略在配置文件中的配置,通过编写一个配置类
不过这样的话加载配置文件的方式也发生的变化,这个时候加载的就是这个配置类文件了
对于加载properties文件中的变量,对于springboot来说,可以直接在类中使用@value就可以调用,对于一般的maven项目需要在配置类中加下面这个注解
@Bean注解用于修饰方法,返回bean对象,加入容器中
也可以用componentscan注解来把要使用的类扫描到,加入容器中
spring整合mybatis
springboot整合mybatis,只需要添加注解mapper,然后编写properties文件,domain文件,和dao接口即可
不用写配置文件,而是通过注解的方式进行编写
AOP
Aop的核心就是代理
使用aop首先需要在配置类中加载aop注解@EnableAspectJAutoProxy
事务
在配置类中加上注解@EnableTransactionManagement
SpringMVC
对于mvc架构来说,是自动拦截请求,然后调用bean对象,所以要提前配置spring,告诉tomcat
后面用下面这种方式,简化
当访问静态页面资源的时候,这上面是“/”,代表了拦截所有路径,所以静态页面资源会被springmvc拦截,这是不行的,所以要另外配置一个配置类,对其进行放行
定义好这个配置类之后,需要在springmvcConfig中进行扫描,因为Servlet这个配置类中定义springConfig和SpringMvcConfig两个配置类,对于其他配置类需要让spring知道的话,就需要在这两个配置类中进行包扫描
整体流程
对于springmvc来说,因为功能的不同,controller层的bean由springmvc管理,其他包的bean由spring管理
所以有两个包扫描的配置类
因为mybatis的自动代理来创建的dao层实现对象,所以dao层没有实现对象,所以实际上可以不用写dao层的扫描,但是为了通用性更高,还是加上dao层的扫描
controller层数据传递的细节
对于controller层的请求,前端传递参数过来,controller层的方法可以直接形参写pojo来接受,可以自动转化
如果发送的是json格式的数据,就在参数加注解@requestBody,并且需要在SpringMvc中加入下面注解
在springmvc中也可以响应页面,在springboot中一般就响应数据,前面一般都是异步请求,页面的跳转交给前端的router实现
对于responseBody的实现是由下面实现的:
整合
-
配置类
配置类最开始是ServletConfig配置类,web容器会加载springconfig和SpringMvcConfig,SpringMvcConfig是根路径容器,两个里面的bean是不在一个容器的,但SpringMvcConfig容器可以调用springconfig容器的bean,反之则不行,其实是可以定义一个配置类把这两个放一起。
这两个配置类又会分别进行包扫描,加载不同的bean来进行使用
-
首先配置springconfig配置类
-
接着配置JdbcConfig和MyBatisConfig两个配置类,也需要写jdbc.properties文件,编写用户名密码等
-
接着编写ServletConfig配置类和SpringMvcConfig配置类
-
-
接着就可以写controller,service,dao,domain层
一般是先创建domain层对象,然后创建dao接口,写接口方法,不用实现层,用的是mybatis自动代理实现的方法,所以也不需要resporsity注解来加入容器,然后就编写service接口,对service接口通过自动注入dao的bean对象进行方法实现,然后通过自动注入service的bean对象编写controller层
-
事务处理
在SpringConfig中加注解,接着在jdbcConfig中加入下面代码
最后在要加入事务的service接口上加入注解@Transactional
-
编写统一结果类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
package com.kilogod.code.common.res; import lombok.Data; import org.apache.commons.lang3.StringUtils; import java.io.Serializable; /** * @Author dyc * @Desc 通用返回结果类 */ @Data public class ResultData<T> implements Serializable { private static final long serialVersionUID = 1L; /** * 状态 */ private String status; /** * 提示信息 */ private String msg; /** * 返回的数据 */ private T data; public ResultData() { this.status = ResultCode.SUCCESS_STATUS; this.setMsg(ResultStatus.getMessage(ResultCode.SUCCESS_STATUS)); } public void setError() { this.setStatus(ResultCode.SYSTEM_ERROR); this.setMsg(ResultStatus.getMessage(ResultCode.SYSTEM_ERROR)); } public void setError(String status) { if (StringUtils.isBlank(status)) { this.setError(); } else { this.setMsg(ResultStatus.getMessage(status)); this.setStatus(status); } } public void setErrorMsg(String message) { this.setMsg(message); this.setStatus(ResultCode.SYSTEM_ERROR); } }
-
异常处理,统一写到表现层
异常分类
自定义异常编码
在业务层中异常的出现进行包装
编写异常处理器
-
加上前端页面,设置拦截请求
在springmvcConfig中进行包扫描
-
设置拦截器,参考springboot,拦截器链的执行顺序和添加拦截器的顺序有关
mybatisplus
只需要在dao的接口extends basemapper即可,就不需要写方法和实现
分页查询
-
设置分页查询拦截器
-
查询对象
条件查询
条件查询中null值处理
如果只想要表中的一部分字段,即查询投影
关于pojo对象和表结构不同的情况
逻辑删除
有些数据没必要真的删除,可以设置一个字段表示删除即可
乐观锁
处理并发问题