项目工程
一、环境搭建
准备数据库表(dept、emp)
创建springboot工程,引入对应的起步依赖(web、mybatis、mysql驱动、lombok)
配置文件application.properties中引入maybatis的配置信息,准备对应的实体类
准备对应的Mapper、Service(接口、实现类)、Controller基础结构
application.properties文件配置:
spring.application.name=Spring1 #驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.url=jdbc:mysql://localhost:3306/mybatis #连接数据库的用户名 spring.datasource.username=root #连接数据库的密码 spring.datasource.password=123456 #配置mybatis日志,指定输出到控制台 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #开启mybatis驼峰命名自动映射开关(例:a_column --->aColumn) mybatis.configuration.map-underscore-to-camel-case=true el-case=true导入结构
controller(实体类)
mapper(接口)
pojo(实体类)
service(接口)
配置xml映射文件,编写SQL语句(在resources文件夹下新建)
和Mapper接口名一样,在resources下创建包时不能用 “.” 来分层,只能用 “/”,但是显现出的效果相同
约束直接从官方文档拷贝
在Mybatis中文网进入“入门”,找到配置SQL语句的XML文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="Mapper接口的全类名"> </mapper>二、开发规范
Restful特点
url定位资源
HTTP动词描述操作
Rest是风格,是约定方式,约定不是规定,可以打破
描述模块的功能通常使用复数,也就是加s的格式来描述,表示此类资源,而非单个资源。如:user、emps等
前后端交互统一响应结果Result:(放在pojo包下)
@Data @NoArgsConstructor @AllArgsConstructor public class Result{ private Integer code; ==响应码,1代标成功,0代表失败 private String msg; ==响应信息,描述字符串 private Object data; ==返回的数据 public static Result success(){ ==增删改 响应成功 return new Result(1,"success",null); } public static Result success(Object data){ ==查询 响应成功 return new Result(1,"success",data); } public static Result error(String msg){ ==响应失败 return new Result(0,msg,null); } }三、前后端联调
将资料中的文件压缩包拷贝到一个没有中文不带空格的目录下解压
打开nginx.exe ,启动前端服务器(http://localhost:90/)
nginx会根据配置信息转到8080端口
若tomcat端口号不是8080的,去nginx的config配置中把端口号改成要使用的端口号,并重新启动nginx
四、部门管理
(一)查询部门信息
在controller层下的DeptController类下编写
@Slf4j //省略Logger对象 @RestController //交给ioc容器管理,成为bean对象 public class DeptController { //@RequestMapping("/depts") //通过其value属性来指定请求路径,method属性来指定请求方式,下面为简化版 //有Get,Post,Put,Delete @GetMapping("/depts") public Result list() { log.info("查询全部数据");//输出日志 //调用Service查询数据 List<Dept> deptList = deptService.list(); return Result.success(deptList);//传递返回的值 } } ====以下为SLf4j的源码==== Example: @Slf4j public class LogExample { } will generate: public class LogExample { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class); }在Service层的DeptService接口下创建 list() 方法,并在具体的实现类中重写 list() 方法,创建mapper对象并调用其方法、
接口
/** * 部门管理 */ public interface DeptService { /* * 查询全部部门数据*/ List<Dept> list(); } //实现类
@Service public class DeptServiceImpl implements DeptService { @Autowired private DeptMapper deptMapper;//调用Dao层从数据库获取数据 @Override public List<Dept> list() { return deptMapper.list(); } }在Dao层实现从数据库获取数据,用注解方式进行调用
/** * 部门管理 */ @Mapper public interface DeptMapper { /* * 查询全部部门方法 * */ @Select("select * from dept") List<Dept> list(); }(二)删除部门
controller层:
/** * 删除部门 * @return */ // @DeleteMapping("/depts/{id}") @DeleteMapping("/{id}") public Result delete(@PathVariable Integer id){ //使用@PathVariable注解去获取@RequestMapping中{}中传进来的值,并绑定到处理方法定一的形参上 //应用时,在@RequestMapping请求路径中,将需要传递的参数用花括号{}括起来, //然后通过@PathVariable("参数名称")获取URL中对应的参数值。 log.info("根据id删除部门:{}",id);//一个大括号就是一个参数占位符 deptService.delete(id); //调用Service删除部门 return Result.success(); }service层
接口
/** * 根据id删除部门 * * @param id */ void delete(Integer id); //实现类
@Override public void delete(Integer id) { deptMapper.delete(id); }dao层
@Delete("delete from mybatis.dept where id = #{id}") void delete(Integer id);PS:postman测试时记得把请求方式换成DELETE
(三)新增部门
controller层
/** * 新增部门 */ @PostMapping("/depts") public Result addDept(@RequestBody Dept dept){ log.info("新增部门:{}",dept); //调用Service层新增 deptService.addDept(dept); return Result.success(); }service层
接口Dept addDept(Dept dept);实现类
@Override public Dept addDept(Dept dept) { dept.setCreateTime(LocalDateTime.now()); dept.setUpdateTime(LocalDateTime.now());//补全数据类型 return deptMapper.addDept(dept); }dao层
/** * 新增部门 * @param dept * @return */ @Insert("INSERT INTO dept (name, create_time, update_time) VALUES (#{name},#{createTime},#{updateTime})") Dept addDept(Dept dept);
五、员工管理
基本数据封装对象:PageBean
package com.itheima.pojo; import java.util.List; public class PageBean { private Long total; private List rows; public Long getTotal() { return total; } public void setTotal(Long total) { this.total = total; } public List getRows() { return rows; } public void setRows(List rows) { this.rows = rows; } }补充:
@RequestParam,@PathVariable注解的区别:
@RequestParam:
从前端获取参数,其中的 defaultValue 可设置默认值@PathVariable:
通过请求路径指定参数,其中 defaultValue() 设置默认值请求路径:/depts/{id}@DateTimeFormat:
其中的pattern可以设置日期的格式,如@DateTimeFormat (pattern = "yyyy-MM-dd")@RequestBody:
@RequsetParam 可以接受post请求,但是请求的contentType 为 form-data格式,而@RequestBody接受的请求方式application/json;charset=UTF-8
(一)分页查询员工列表
1、原始方法
controller层
@Slf4j //@RequestMapping("/emps") @RestController public class EmpController { @Autowired private EmpService empService; @GetMapping("/emps") public Result empSelect(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10")Integer pageSize){ //设置默认值 // if(page==null)page = 1; // if(pageSize==null)pageSize=10; log.info("员工信息分页查询第{}页{}条",page,pageSize); PageBean bean = empService.empSelect(page,pageSize); return Result.success(bean); } }service层(在service层完成对数据的封装)
接口
public interface EmpService { PageBean empSelect(Integer page, Integer pageSize); }实体类
public class EmpServiceImpl implements EmpService { @Autowired private EmpMapper empMapper; @Override public PageBean empSelect(Integer page, Integer pageSize) { PageBean bean = new PageBean(); bean.setRows(empMapper.empSelect(page,pageSize)); //把从Dao层获取的数据封装到整合对象PageBean里 bean.setTotal(empMapper.empTotal()); return bean; } }
dao层
@Mapper public interface EmpMapper { @Select("select *from emp limit #{page},#{pageSize}") public List<Emp> empSelect(Integer page, Integer pageSize); @Select("select count(*) from emp") public Long empTotal(); }2、PageHelper插件
配置pom.xml文件
<!--PageHelper分页插件--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.6</version> </dependency>EmpMapper
/** * 员工信息查询 * @return */ @Select("select * from emp") public List<Emp> list();EmpServicelmp
@Override public PageBean empSelect(Integer page, Integer pageSize) { //1.设置分页参数 PageHelper.startPage(page,pageSize); //2.执行查询 List<Emp> empList = empMapper.list(); Page<Emp> p =(Page<Emp>) empList;//强制转换成Page对象 //3.封装pagebean对象 PageBean bean = new PageBean(p.getTotal(),p.getResult()); return bean; }
3、条件分页查询(动态SQL)
SQL语句:
select * from emp where name like concat('%',?,'%') and gender = ? and entrydate between ? and ? order by update_time desc;controller层
(补全参数)
public class EmpController { @Autowired private EmpService empService; @GetMapping("/emps") public Result empSelect(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10")Integer pageSize, String name, Short gender, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){ log.info("员工信息分页查询第{}页{}条",page,pageSize); PageBean bean = empService.empSelect(page,pageSize); return Result.success(bean); } }service层
(改造empSelect方法)
接口
public interface EmpService { PageBean empSelect(Integer page, Integer pageSize,String name, Short gender, LocalDate begin, LocalDate end); }实现类
@Service public class EmpServiceImpl implements EmpService { @Autowired private EmpMapper empMapper; @Override public PageBean empSelect(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end) { //1.设置分页参数 PageHelper.startPage(page,pageSize); //2.执行查询 List<Emp> empList = empMapper.list(name,gender,begin ,end); Page<Emp> p =(Page<Emp>) empList;//强制转换成Page对象 //3.封装pagebean对象 PageBean bean = new PageBean(p.getTotal(),p.getResult()); return bean; } }dao层
(改造list方法)
mapper
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);EmpMapper映射文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.mapper.EmpMapper"> <select id="list" resultType="com.itheima.pojo.Emp"> select * from emp <where> <if test="name!=null"> name like concat('%',#{name},'%') </if> <if test="gender!=null"> and gender = #{gender} </if> <if test="begin!=null and end!=null"> and entrydate between #{begin} and #{end} </if> </where> order by update_time desc </select> </mapper>(二)批量删除员工数据
contraller
@DeleteMapping("/emps/{ids}") public Result Delete(@PathVariable ArrayList<Integer> ids){ log.info("批量删除,ids:{}",ids); empService.Delete(ids); return Result.success(); }service
接口
void Delete(ArrayList ids);实现类
@Override public void Delete(ArrayList ids) { empMapper.Delete(ids); }
dao
mapper
void Delete(ArrayList ids);xml映射文件
<!-- 批量删除操作--> <delete id="Delete"> delete from emp where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
(三)新增员工数据
controller
@PostMapping("/emps") public Result save(@RequestBody Emp emp){ log.info("新增员工,{}",emp); empService.Insert(emp); return Result.success(); }service
接口
void Insert(Emp emp);实现类(补全参数)
@Override public void Insert(Emp emp) { emp.setCreateTime(LocalDateTime.now());//设置创建时间参数 emp.setUpdateTime(LocalDateTime.now());//设置修改时间参数 empMapper.Insert(emp); }
dao
@Insert("Insert Into emp (username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " + "VALUES (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})") void Insert(Emp emp);(四)文件上传
将本地图片、视频、音频上传到服务器供其他用户浏览或下载
1.本地存储
代码实现:
- 在服务器本地磁盘上创建images目录,用来存储上传的文件(例:E盘创建images目录)
- 使用MultipartFile类提供的API方法,把临时文件转存到本地磁盘目录下
MultipartFile 常见方法:
- String getOriginalFilename(); //获取原始文件名
- void transferTo(File dest); //将接收的文件转存到磁盘文件中
- long getSize(); //获取文件的大小,单位:字节
- byte[] getBytes(); //获取文件内容的字节数组
- InputStream getInputStream(); //获取接收到的文件内容的输入流
把前端文件复制到resources文件夹下的static中
controller
package com.itheima.controller; import com.itheima.pojo.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @Slf4j //日志注解 @RestController public class UploadController { @PostMapping("/upload") public Result upload(String username, Integer age, MultipartFile image) throws IOException { //也可以用@RequestParm注解指定对应参数,如:@RequestParm("image") MultipartFile file log.info("文件上传,{},{},{}",username,age,image); //1.获取原始文件名 String imageName = image.getOriginalFilename(); //2.构成新文件名 int index = imageName.lastIndexOf('.'); UUID uuid = UUID.randomUUID(); String newName = uuid.toString()+imageName.substring(index); //3.将文件存储在服务器的磁盘目录中:E:\javaprogram\SaveImages image.transferTo(new File("E:\\javaprogram\\SaveImages\\"+newName));//该方法将接收到的文件转存到指定磁盘目录下 log.info("新文件名,{}",newName); return Result.success(); } }其中,文件默认不超过1M
在properties文件中修改限制
#配置单个文件最大上传大小 spring.servlet.multipart.max-file-size = 10MB #配置单个请求最大上传大小(一次请求能上传的总文件大小) sprin.servlet.multipart.max-file-size = 100MB2.阿里云存储OSS
第三方服务思路
SDK:Software Development Kit,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等
Bucket:存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间。
- 开通OSS服务之后,就可以进入到阿里云对象存储的控制台
- 点击左侧的 “Bucket列表”,创建一个Bucket
AccessKey:
AccessKey ID:
AccessKey Secret:
入门程序
引入依赖项
<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.17.4</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency> <!-- no more than 2.3.3--> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.3.3</version> </dependency>上传文件流
以下代码用于将文件流上传到目标存储空间examplebucket中exampledir目录下的exampleobject.txt文件。
1.修改Endpoint
2.填写bucketName
3.指定保存至阿里云时的名称 objectName
4.指定将要上传的文件的本地路径 filePath
5.配置id和secret
import com.aliyun.oss.ClientException; import com.aliyun.oss.OSS; import com.aliyun.oss.common.auth.*; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; import com.aliyun.oss.model.PutObjectRequest; import com.aliyun.oss.model.PutObjectResult; import java.io.File; public class Demo { public static void main(String[] args) throws Exception { // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。 String endpoint = "https://oss-cn-hangzhou.aliyuncs.com"; // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。 EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider(); // 填写Bucket名称,例如examplebucket。 String bucketName = "examplebucket"; // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。 String objectName = "exampledir/exampleobject.txt"; // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。 // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。 String filePath= "D:\\localpath\\examplefile.txt"; // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider); try { // 创建PutObjectRequest对象。 PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath)); // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。 // ObjectMetadata metadata = new ObjectMetadata(); // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString()); // metadata.setObjectAcl(CannedAccessControlList.Private); // putObjectRequest.setMetadata(metadata); // 上传文件。 PutObjectResult result = ossClient.putObject(putObjectRequest); } catch (OSSException oe) { System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason."); System.out.println("Error Message:" + oe.getErrorMessage()); System.out.println("Error Code:" + oe.getErrorCode()); System.out.println("Request ID:" + oe.getRequestId()); System.out.println("Host ID:" + oe.getHostId()); } catch (ClientException ce) { System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network."); System.out.println("Error Message:" + ce.getMessage()); } finally { if (ossClient != null) { ossClient.shutdown(); } } } }由于oss进行了更新,所以Id和Secret的配置参照该文章:
后端之路——阿里云OSS云存储-CSDN博客
集成
UploadController
1.接收上传的图片
2.存储图片(OSS)
3.返回访问图片的url
引入阿里云OSS工具类(示例代码改)
新建utils包package com.itheima.utils; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.common.auth.CredentialsProviderFactory; import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.util.UUID; /** * 阿里云 OSS 工具类 */ @Component //解释:utils不属于三层,交给IOC容器管理直接加@Component public class AliOSSUtils { private String endpoint = "https://oss-cn-beijing.aliyuncs.com"; private String bucketName = "bucket122333"; /** * 实现上传图片到OSS */ public String upload(MultipartFile file) throws Exception { // 获取上传的文件的输入流 InputStream inputStream = file.getInputStream(); // 避免文件覆盖 String originalFilename = file.getOriginalFilename(); String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); //上传文件到 OSS EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider(); OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider); ossClient.putObject(bucketName, fileName, inputStream); //文件访问路径 String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName; // 关闭ossClient ossClient.shutdown(); return url;// 把上传到oss的路径返回 } }上传图片接口开发
controller
package com.itheima.controller; import com.itheima.pojo.Result; import com.itheima.utils.AliOSSUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @Slf4j //日志注解 @RestController public class UploadController { @Autowired private AliOSSUtils aliOSSUtils; // 阿里云OSS存储 @PostMapping("/upload") public Result upload(MultipartFile image) throws Exception { log.info("文件上传,文件名{}",image.getOriginalFilename()); //调用阿里云OSS工具类进行文件上传 String url = aliOSSUtils.upload(image); log.info("文件上传成功,文件url{}",url); return Result.success(url); } }(五)修改员工数据
1.回显
controller
@GetMapping("/{id}") public Result search(@PathVariable Integer id){ log.info("根据id查询员工,id:{}",id); Emp emp = empService.Search(id); return Result.success(emp); }service
接口
Emp Search(Integer id);实现类
@Override public Emp Search(Integer id) { return empMapper.Search(id); }
dao
@Select("select * from emp where id = #{id}") Emp Search(Integer id);2.修改数据
controller
@PutMapping public Result update(@RequestBody Emp emp){ log.info("根据id修改员工数据,id:{}",emp.getId()); empService.update(emp); return Result.success(); }service
接口
void update(Emp emp);实现类
@Override public void update(Emp emp) { emp.setUpdateTime(LocalDateTime.now()); empMapper.update(emp); }
dao
mapper
void update(Emp emp);映射文件
<update id="update"> update emp <set> <if test="username!=null and username!=''"> username =#{username}, </if> <if test="password != null and password != ''"> password = #{password}, </if> <if test="name != null and name != ''"> name = #{name}, </if> <if test="gender != null"> gender = #{gender}, </if> <if test="image != null and image != ''"> image = #{image}, </if> <if test="job != null"> job = #{job}, </if> <if test="entrydate != null"> entrydate = #{entrydate}, </if> <if test="deptId != null"> dept_id = #{deptId}, </if> <if test="updateTime != null"> update_time = #{updateTime} </if> </set> where id = #{id} </update>
六、配置文件
(一)参数配置化(properties)
问题分析:AliOSSUtils文件下的第三方信息硬编码在代码中,不便于维护和管理
private String endpoint = "https://oss-cn-beijing.aliyuncs.com"; //地址 private String bucketName = "bucket122333"; //库名问题解决:在application.properties文件中配制阿里云信息
#自定义阿里云配置信息 aliyun.oss.endpoint = https://oss-cn-beijing.aliyuncs.com aliyun.oss.bucketName = bucket122333@Value 注解:用于外部配置的属性注入,具体用法如下
@Value("${配置文件中的key}")@Component //解释:utils不属于三层,交给IOC容器管理直接加@Component public class AliOSSUtils { @Value("${aliyun.oss.endpoint}") private String endpoint = "https://oss-cn-beijing.aliyuncs.com"; @Value("${aliyun.oss.bucketName}") private String bucketName = "bucket122333";(二)yml配置文件
SpringBoot提供了多种属性配置方式
常见配置文件格式:
application.properties
server.port = 8080 server.address = 127.0.0.1application.yml / yaml(冒号后要加上空格,每一级用两个空格缩进)
server: port: 8080 address: 127.0.0.1xml
<server> <port>8080</port> <address>127.0.0.1</address> </server>
yml基本语法
大小写分明
数值前必须有空格作为分隔符
使用缩进表示层级关系,缩进时不允许使用tab键,只能用空格
缩进的数目不重要,只要相同层级的元素左侧对其即可
#表示注释
yml和properties对比
spring: application: name: Spring1 #数据库连接信息 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis username: root password: 123456 #密码无法连接数据库时用单引号引起来 #文件上传配置 servlet: multipart: max-file-size: 10MB max-request-size: 100MB #Mybatis配置 mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true #阿里云OSS配置 aliyun: oss: bucketName: bucket122333 endpoint: https://oss-cn-beijing.aliyuncs.comspring.application.name=Spring1 #驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.url=jdbc:mysql://localhost:3306/mybatis #连接数据库的用户名 spring.datasource.username=root #连接数据库的密码 spring.datasource.password=123456 #配置mybatis日志,指定输出到控制台 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #开启mybatis驼峰命名自动映射开关(例:a_column --->aColumn) mybatis.configuration.map-underscore-to-camel-case=true #配置单个文件最大上传大小 spring.servlet.multipart.max-file-size = 10MB #配置单个请求最大上传大小(一次请求能上传的总文件大小) spring.servlet.multipart.max-request-size = 100MB #配置阿里云OSS aliyun.oss.endpoint = https://oss-cn-beijing.aliyuncs.com aliyun.oss.bucketName = bucket122333(三)@ConfigurationProperties
可以将配置文件中配置项的值自动注入到对象的属性中(取代@Value)
前提条件:
需要注入的属性有GET和SET方法(@Data)
属性名要一致,前缀名一致
需要该类由IOC容器管理(@Component)
@Component //解释:utils不属于三层,交给IOC容器管理直接加@Component @Data @ConfigurationProperties(prefix = "aliyun.oss" ) public class AliOSSUtils { // @Value("${aliyun.oss.endpoint}") private String endpoint ; // @Value("${aliyun.oss.bucketName}") private String bucketName ;可选操作:解决报红
引入configuration依赖,自动识别被@ConfigurationProperties注解标识的bean对象,在配置文件中配置时自动提示与该bean对象属性名相对应的配置项名字
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency>如图所示
@ConfigurationProperties 和 @Value 的对比
@Value注解只能一个一个的进行外部属性的注入
@ConfigurationProperties可以批量的将外部的属性配置注入到bean对象的属性中
七、基础登录取功能
(一)登录基本操作
controller(其中判断用户名和密码是否正确的
操作放在该层)
@Slf4j @RestController public class LoginController { @Autowired private EmpService empService; @PostMapping public Result login(@RequestBody Emp emp){ log.info("员工登录:{}",emp); Emp e = empService.login(emp); if(e!=null){ return Result.success(); }else { return Result.error("用户名或密码错误"); } } }service
接口
Emp login(Emp emp);实现类
/** * 员工登录 */ @Override public Emp login(Emp emp) { return empMapper.getByUsernameAndPassword(emp); }
dao
/** * 根据用户名和密码查询员工 * @param emp * @return */ @Select("select * from emp where username = #{username} and password = #{password}") Emp getByUsernameAndPassword(Emp emp);(二)会话技术
会话:用户打开浏览器,访问web服务器资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据
会话跟踪方案:
1.客户端会话跟踪技术:Cookie
2.服务端会话跟踪技术:Session
3.令牌技术
(1)Cookie
1.Cookie:HTTP请求头包含存储先前通过与所述服务器发送的HTTP cookies Set-Cookie 头
2.Set-Cookie:所述Set-Cookie HTTP响应头被用于从服务器向代理发送 cookie
存储在浏览器本地,请求之后返回cookie值,下次访问时从浏览器本地发送cookie
(2)Sessions
存储在服务器端,请求后从服务端查询是否存在相应值,但是服务器集群无法使用
(3)JWT令牌(主流)
全称:JSON Web Token
定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的
组成
第一部分:Header(头),记录令牌类型、签名算法等。例如:{“alg”:“HS256” , “type”:“JWT”}
第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{“id”:“1” , “username”:“Tom”}
第三部分:Signature(签名),防止Token被纂改、确保安全性。将header、payload,并加入指定密钥,通过指定签名算法计算而来
使用场景:登录验证
登录成功后生成令牌
后续每个请求都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,通过后再处理
配置xml文件
<!--JWT令牌--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>在测试类中添加测试方法生成令牌
(要是报错ClassNotFoundException,添加依赖jaxb-api)
<!--jaxb-api--> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency>- 生成令牌
@Test //生成JWT令牌 public void testGenJwt(){ Map<String, Object> claims = new HashMap<>(); claims.put("id",1); claims.put("name","tom"); String jwt = Jwts.builder() .signWith(SignatureAlgorithm.HS256, "itheima")//用于设置数字签名的算法,例如:HS256(256位密钥) .setClaims(claims)//自定义内容(载荷) .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期为1小时 .compact();//获取成字符串 System.out.println(jwt); }- 解析令牌
@Test public void testParseJWT(){ Map<String, Object> claims = Jwts.parser() .setSigningKey("itheima")//指定签名密钥 .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTcyOTQzMTk0MX0.uZw_C-9qhHFakqoLyG3Jh-MRQrwSmHCN2kfgQBdUhSY")//解析生成的jwt令牌(2024/10/20过期) .getBody(); System.out.println(claims); }
- jwt校验时使用的签名密钥,必须和生成jwt令牌时使用的密钥是配套的
- 如果jwt令牌解析校验时报错,则说明jwt令牌被篡改或失效了,令牌非法
(三)使用JWT令牌完成登录校验
思路
- 令牌生成:登录成功后,生成JWT令牌,并返回给前端
- 令牌校验:在请求到达服务端后,对令牌进行统一拦截、校验
1.引入JWT令牌工具类
package com.itheima.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; import java.util.Map; public class JwtUtils { private static String signKey = "itheima"; private static Long expire = 43200000L; /** * 生成JWT令牌 * @param claims JWT第二部分负载 payload 中存储的内容 * @return */ public static String generateJwt(Map<String, Object> claims){ String jwt = Jwts.builder() .addClaims(claims) .signWith(SignatureAlgorithm.HS256, signKey) .setExpiration(new Date(System.currentTimeMillis() + expire)) .compact(); return jwt; } /** * 解析JWT令牌 * @param jwt JWT令牌 * @return JWT第二部分负载 payload 中存储的内容 */ public static Claims parseJWT(String jwt){ Claims claims = Jwts.parser() .setSigningKey(signKey) .parseClaimsJws(jwt) .getBody(); return claims; } }2.修改登录controller代码
@Slf4j @RestController public class LoginController { @Autowired private EmpService empService; @PostMapping("/login") public Result login(@RequestBody Emp emp){ log.info("员工登录:{}",emp); Emp e = empService.login(emp); if(e!=null){ //登录成功,生成令牌,下发令牌 Map<String,Object> jwt = new HashMap<>(); //在jwt令牌生成中加上ID,用户名,姓名 jwt.put("ID",e.getId()); jwt.put("username",e.getUsername()); jwt.put("Name",e.getName()); String s = JwtUtils.generateJwt(jwt); return Result.success(s); }else { //登录失败,返回 return Result.error("用户名或密码错误"); } } }(四)统一拦截
(1)Filter过滤器
- 概念:是javaweb三大组件(Servlet、Filter、Listener)之一
- 过滤器可以把对资源的请求拦截下来
基本操作:
定义Filter:定义一个类实现Filter接口
PS:导入的Filter是javax.servlet.Filter
配置Filter:在Filter类上加上@WebFilter注解,配置拦截资源的路径(urlPatterns),启动类加上@ServletComponentScan开启Servlet组件支持
定义和配置Filter:
package com.itheima.Filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter(urlPatterns = "/*") //拦截所有请求 public class DemoFilter implements Filter { @Override //初始化方法,只调用一次,默认实现 public void init(FilterConfig filterConfig) throws ServletException { //System.out.println("init初始化方法执行了"); Filter.super.init(filterConfig); } @Override //拦截到请求都会调用 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("拦截到了请求"); } @Override //销毁方法,只调用一次,默认实现 public void destroy() { //System.out.println("destroy销毁方法执行了"); Filter.super.destroy(); } }配置启动类:
@ServletComponentScan //配置Servlet组件支持 @SpringBootApplication public class Spring1Application { public static void main(String[] args) { SpringApplication.run(Spring1Application.class, args); } }
过滤器执行流程:
- 放行前逻辑
- 放行(放行后访问对应资源)
- 放行后逻辑(回到Filter但是执行放行之后的代码)
Filter的拦截路径:可以根据需求配置不同的拦截资源路径
拦截路径 urlPatterns值 含义 拦截具体路径 /login 只有访问/login路径时,才会被拦截 目录拦截 /emps/* 访问/emps下的所有资源时,会被拦截 拦截所有 /* 访问所有资源,都会被拦截
过滤器链:一个web应用中可以配置多个过滤器,形成一个过滤器链。
访问流程:filter1放行前–》放行–》filter2放行前–》放行–》访问资源–》filter2放行后–》filter1放行后
abc过滤器(" /* ")
@WebFilter(urlPatterns = "/*") public class AbcFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("abc拦截到了请求"); //放行 filterChain.doFilter(servletRequest,servletResponse); System.out.println("abc放行后执行逻辑"); } }demo过滤器(" /login ")
@WebFilter(urlPatterns = "/login")//拦截所有请求 public class DemoFilter implements Filter { @Override //拦截到请求都会调用 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("demo拦截到了请求"); //放行 filterChain.doFilter(servletRequest,servletResponse); System.out.println("demoF拦截请求放行"); } }顺序:注解配置的Filter,优先级按照过滤器类名(字符串)的自然排序
登录校验功能实现:
流程图:
步骤:
- 获取url
- 判断请求url中是否包含login,如果包含,放行
- 获取请求头中令牌
- 判断令牌是否存在,如果不存在返回错误结果
- 解析token,如果解析失败返回错误结果
- 放行
阿里巴巴fastjosn:手动把Result转换成json数据(版本号改成2.0.51可用)
<!--阿里巴巴fastjosn--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </dependency>代码实现:
@Slf4j @WebFilter(urlPatterns = "/*") public class LoginCheckFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest;//强转为http获取请求参数 HttpServletResponse resp = (HttpServletResponse) servletResponse;//强转为http获取响应参数 //> 1- 获取url String url = req.getRequestURL().toString(); log.info("请求的url:{}",url); //> 2- 判断请求url中是否包含login,如果包含,放行 if(url.contains("login")){ log.info("登录操作,放行"); filterChain.doFilter(servletRequest,servletResponse); return; } //> 3- 获取请求头(token)中令牌 见api文件 String jwt = req.getHeader("token"); //> 4- 判断令牌是否存在,如果不存在返回错误结果 if(!StringUtils.hasLength(jwt)){ log.info("令牌为空,失败"); Result notLogin = Result.error("NOT_LOGIN"); //手动转化 对象--JSON-------》阿里fastJOSN // 这里的JSONObject是com.alibaba.fastjson.JSONObject String notl = JSONObject.toJSONString(notLogin); resp.getWriter().write(notl);//直接响应给浏览器 return; } //> 5- 解析token,如果解析失败返回错误结果 try { JwtUtils.parseJWT(jwt); } catch (Exception e) { //解析失败 e.printStackTrace(); log.info("jwt令牌解析失败"); Result notLogin = Result.error("NOT_LOGIN"); String notl = JSONObject.toJSONString(notLogin); resp.getWriter().write(notl);//直接响应给浏览器 return; } //> 6- 放行 log.info("令牌有效,放行"); filterChain.doFilter(servletRequest,servletResponse); } }(2)Interceptor拦截器
基本操作:
定义拦截器:定义一个类实现Interceptor接口
注册拦截器
定义拦截器
@Component //交给ioc容器管理 public class LoginCheckInterceptor implements HandlerInterceptor { @Override //目标资源方法运行前运行,返回true:放行 false:不放行 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle........"); return true; } @Override //目标资源方法运行后运行 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle///////"); } @Override //视图渲染完毕后运行,最后运行 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion........"); } }注册拦截器(新建配置类包)
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginCheckInterceptor interceptor; @Override //注册拦截器 public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor).addPathPatterns("/**").excludePathPatterns("/login") //需要拦截的资源 //不需要拦截的资源 } }| 拦截路径 | 含义 | 举例 |
|---|---|---|
| /* | 一级路径 | 能匹配/depts,/emps,/login,不能匹配/depts/1 |
| /** | 任意级路径 | 能匹配任意级路径 |
| /depts/* | /depts下的一级路径 | 能匹配/depts/1,不能匹配/depts/1/2,/depts |
| /depts/** | /depts下的任意级路径 | 能匹配/depts/1,/depts/1/2,/depts,不能匹配/emps/1 |
拦截器、过滤器执行流程:
拦截器和过滤器之间的区别:
- 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口
- 拦截范围不同:过滤器Filter会拦截所有资源,而Interceptor只会拦截Spring环境中的资源
八、异常处理
异常抛出过程:
全局异常处理器
设置全局异常处理器
/** * 全局异常处理器 */ @RestControllerAdvice //@RestControllerAdvice = @ControllerAdvice + @ResponseBody public class GlobalExceptionHandler { @ExceptionHandler(Exception.class)//捕获所有异常 public Result ex(Exception ex){ ex.printStackTrace(); return Result.error("对不起,操作失败,请联系管理员"); } }
九、事务管理
(一)回顾事务
概念:事务是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败
操作:
- 开启事务(一组操作开始前,开启事务):start transaction / begin ;
- 提交事务(这组操作全部成功后,提交事务):commit ;
- 回滚事务(中间出现任何异常,回滚事务):rollback ;
完善删除部门操作
@Override public void delete(Integer id) { //1.删除部门 deptMapper.delete(id); //2.根据部门id删除部门下的员工信息 empMapper.DeleteByDeptId(id); }/** * 根据部门id删除部门下员工 * @param id */ @Delete("delete from emp where dept_id = #{id}") void DeleteByDeptId(Integer id); }(二)Spring事务管理
- 事务注解:@Transactional
- 位置:业务(service)层的方法上、类上、接口上
- 作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
执行多次数据访问操作的方法上添加@Transactional注解
开启事务管理日志:
logging: level: org.springframework.jdbc.support.JdbcTransactionManager:debug@Transactional//事务注解 @Override public void delete(Integer id) { //1.删除部门 deptMapper.delete(id); int i = 1/0;//模拟异常 //2.根据部门id删除部门下的员工信息 empMapper.DeleteByDeptId(id); }(三)事务进阶
1.rollbackFor
默认情况下,只有出现RuntimeException才会回滚异常。rollbackFor属性用于控制出现何种异常,回滚事务
设置@Transactional的rollbackFor值
//出现任何异常都回滚 @Transactional(rollbackFor = Exception.class)//事务注解 @Override public void delete(Integer id) { //1.删除部门 deptMapper.delete(id); // int i = 1/0;//模拟异常 //2.根据部门id删除部门下的员工信息 empMapper.DeleteByDeptId(id); }2.propagation(事务传播行为)
事务的传播行为:指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制
例如
//a方法 @Transactional public void a(){ //... userService.b(); //... }//b方法 @Transactional public void b(){ //... }此时的b方法是否需要开启一个新的事务?
事务的常见的传播行为
| 属性值 | 含义 |
|---|---|
| REQUIRED | 【默认值】需要事务,有则加入,无则创建新事务 |
| REQUIRES_NEW | 需要新事务,无论有无,总是创建新事务 |
| SUPPORTS | 支持事务,有则加入,无则在无事务的状态中运行 |
| NOT_SUPPORTED | 不支持事务,在无事务的状态下运行,如果当前存在已有事务,则挂起当前事务 |
| MANDATORY | 必须有事务,否则抛出异常 |
| NEVER | 必须没事务,否则抛出异常 |
补充代码:
日志类
package com.itheima.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDateTime; @Data @NoArgsConstructor @AllArgsConstructor public class DeptLog { private Integer id; private LocalDateTime createTime; private String description; }日志表的创建
create table dept_log( id int auto_increment comment '主键ID' primary key, create_time datetime null comment '操作时间', description varchar(300) null comment '操作描述' )comment '部门操作日志表';DeptLogService 接口
package com.itheima.service; import com.itheima.pojo.DeptLog; public interface DeptLogService { void insert(DeptLog deptLog); }DeptLogServiceImpl 实体类
package com.itheima.service.impl; import com.itheima.mapper.DeptLogMapper; import com.itheima.pojo.DeptLog; import com.itheima.service.DeptLogService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service public class DeptLogServiceImpl implements DeptLogService { @Autowired private DeptLogMapper deptLogMapper; @Transactional//(propagation = Propagation.REQUIRES_NEW) @Override public void insert(DeptLog deptLog) { deptLogMapper.insert(deptLog); } }DeptLogMapper
package com.itheima.mapper; import com.itheima.pojo.DeptLog; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; @Mapper public interface DeptLogMapper { @Insert("insert into dept_log(create_time,description) values(#{createTime},#{description})") void insert(DeptLog log); }
解散部门:
需求:解散部门时,无论是成功还是失败,都要记录操作日志
步骤:
- 解散部门:删除部门、删除部门下的员工
- 记录日志到数据库表中
(1)记录日志
@Transactional(rollbackFor = Exception.class)//事务注解 @Override public void delete(Integer id) { //1.删除部门 deptMapper.delete(id); // int i = 1/0;//模拟异常 //2.根据部门id删除部门下的员工信息 empMapper.DeleteByDeptId(id); //记录解散部门的操作 DeptLog deptLog = new DeptLog(); deptLog.setCreateTime(LocalDateTime.now()); deptLog.setDescription("执行了解散部门的操作,此次解散的是"+id+"号部门"); deptLogService.insert(deptLog); }(2)根据需求优化代码(try-catch-finally)
@Transactional(rollbackFor = Exception.class)//事务注解 @Override public void delete(Integer id) { //1.删除部门 try { deptMapper.delete(id); // int i = 1/0;//模拟异常 //2.根据部门id删除部门下的员工信息 empMapper.DeleteByDeptId(id); } finally { //记录解散部门的操作 DeptLog deptLog = new DeptLog(); deptLog.setCreateTime(LocalDateTime.now()); deptLog.setDescription("执行了解散部门的操作,此次解散的是" + id + "号部门"); deptLogService.insert(deptLog); } }由于默认值使得insert方法中的事务传播行为是 加入已有事务,所以当删除部门报错后,记录日志的操作会被一同回滚。
(3)修改 DeptLogServiceImpl 类下的 insert 方法的事务传播行为 (**REQUIRES_NEW**新建一个事务)
@Transactional(propagation = Propagation.REQUIRES_NEW) @Override public void insert(DeptLog deptLog) { deptLogMapper.insert(deptLog); }十、AOP基础
概念:面向切面编程、面向方向编程,其实就是面向特定方法编程。
场景:案例部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时
- 获取方法开始时间
- 运行原始方法
- 获取方法运行结束时间,计算执行耗时
实现:动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的都太代理机制,对特定的方法进行编程。
(一)快速入门:
统计各个业务层方法执行耗时
导入依赖:在pom.xml中导入AOP的依赖
<!--AOP依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>编写AOP程序:针对于特定方法根据业务需要进行编程
package com.itheima.aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Slf4j @Component @Aspect public class TimeAspect { @Around("execution(* com.itheima.service.*.*(..))") //切入点表达式 public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable{ //1.记录开始时间 long begin = System.currentTimeMillis(); //2.调用原始方法运行 Object result = joinPoint.proceed(); //3.记录结束时间,计算方法执行耗时 long end = System.currentTimeMillis(); log.info(joinPoint.getSignature()+"方法执行耗时:{}ms",end - begin);//getSignature可以拿到方法名 return result; } }AOP应用场景:记录操作日志、权限控制、事务管理等
优势:代码无侵入、减少重复代码、提高开发效率、维护方便
(二)核心概念
连接点:JoinPoint,可以呗AOP控制的方法(暗含方法执行时的相关信息)
通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)
切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
目标对象:Target,通知所应用的对象
执行过程:程序运行时会自动为目标对象生成一个代理对象,在代理对象中对目标对象的功能进行增强(在不修改源代码的前提下增加了功能),即通知内的代码进行拼接,最后注入的时候不再注入目标对象而是代理对象,调用方法的时候调用的则是代理对象的方法
(三)通知类型以及顺序
各种通知类型
- @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
- @Before:前置通知,此注解标注的通知方法在目标方法前被执行
- @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
- @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
- @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
正常情况下采用
@Before("execution(路径)") public void before(JoinPoint joinPoint){ log.info("before ..."); }公共表达式@Pointcut
其中,Spring中提供了@Pointcut注解,即公共切入点表达式,用新构造的空方法替代execution,如下所示
@Slf4j @Component @Aspect public class MyAspect1 { //切入点方法(公共的切入点表达式) @Pointcut("execution(* com.itheima.service.*.*(..))") private void pt(){ } //前置通知(引用切入点) @Before("pt()") public void before(JoinPoint joinPoint){ log.info("before ..."); } //环绕通知 @Around("pt()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { log.info("around before ..."); //调用目标对象的原始方法执行 Object result = proceedingJoinPoint.proceed(); //原始方法在执行时:发生异常 //后续代码不在执行 log.info("around after ..."); return result; } //后置通知 @After("pt()") public void after(JoinPoint joinPoint){ log.info("after ..."); } //返回后通知(程序在正常执行的情况下,会执行的后置通知) @AfterReturning("pt()") public void afterReturning(JoinPoint joinPoint){ log.info("afterReturning ..."); } //异常通知(程序在出现异常的情况下,执行的后置通知) @AfterThrowing("pt()") public void afterThrowing(JoinPoint joinPoint){ log.info("afterThrowing ..."); } }也可以修改公共切入点表达式方法的属性(如private改成public),便可以在其他aop类下使用该表达式的路径
通知顺序
默认情况下,当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行,并且按照切面类类名的字母顺序,before字母排名靠前的先执行,after字母排名靠后的先执行
Spring提供了@Order( )注解加在切面类上来控制顺序
- 目标方法前的通知:数字小的先执行
- 目标方法后的通知:数字大的先执行
(四)切入点表达式
常见形式:
(1)execution(…):
根据方法的签名来匹配
格式:execution(访问修饰符 返回值 包名.类名.方法名(方法参数) throws 异常)
- 访问修饰符(可省略)
- 包名.类名(可省略,但是不建议省略)
- throws 异常(可省略,注意是方法上声明抛出的异常,不是实际抛出的异常)
@Before("execution(public void com.itherma.service.impl.DeptServiceImpl.delete(java.lang.Integer))") public void before(JoinPoint joinPoint){ }通配符号:
*:表示单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,亦可以通配包、类、方法名的一部分
execution(* com.*.service.*.update*(*))示例:
execution(* com.itheima.service.*Service.delete*(*))表示的是在com.itheima.service下的任意返回值的以Service结尾的包下的以delete开头的内含一个任意类型参数的方法
…:表示多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
execution(* com.itheima..DeptService.*(..))
同时匹配两个方法:
这里需要匹配的是DeptService下的两个方法
List<Dept> list(); 和 void delete(Integer id);
@Pointcut("execution(* com.itheima.service.DeptService.list()) ||" + "execution(* com.itheima.service.DeptService.delete())") private void pointcut(){ }(2)@annotation(…):
根据注释匹配,用于匹配标识有特定注解的方法
@Before("@annotation(com.itheima.anno.Log)") public void before(){ }步骤:
自定义注解Log
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)//作用于方法上 public @interface MyLog { }在对应方法上加上自定义注解(起到标识作用)
@Log @Override public List<Dept> list() { List<Dept> deptList = deptMapper.list(); return deptList; } @Log @Override public void delete(Integer id) { deptMapper.delete(id); }改写切入点表达式
匹配DeptServiceImpl中的 list() 和 delete(Integer id)方法 //@Pointcut("execution(* com.itheima.service.DeptService.list()) || execution(* com.itheima.service.DeptService.delete(java.lang.Integer))") @Pointcut("@annotation(com.itheima.aop.Log)") private void pt(){}
注解和反射(补充内容,之前学得不深入)
基础格式:
@元注解 public @interface Test{ 定义内容 }(一)元注解负责解释自定义注解,决定了我们自定义注解的应用范围,java定义了4个标准的meta-annotation类型
@Target({ElementType.xxx, ElementType.xxx})
表示自定义注解的作用范围
- TYPE表示当前自定义注解可以添加在类,接口,枚举类上
- FIELD表示可以添加在字段属性上
- METHOD表示可以添加在方法上
- PARAMETER表示可以添加到参数上
- CONSTRUCTOR表示可以添加到构造器上
- …
@Retention(RetentionPolicy.xxx)
表示自定义注解的生命周期
- SOURCE表示只存在于源代码Java文件中
- CLASS表示会在class文件中保留但是在jvm加载运行的时候消失
- RUNTIME表示存在于运行阶段(通常使用的自定义注解使用的都是RUNTIME)
@Documented
说明该注解将被包含在javadoc中
@Inherited
表示子类可以继承父类中的该注解
(二)定义内容
- 其中的每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型
- 可以通过default来声明参数的默认值
- 如果只有一个成员参数一般参数名为value
//自定义注解 @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation{ //注解的参数:参数类型 + 参数名(); String name() default ""; int age() default 0; int id() default -1; //-1表示不存在 }//使用该注解的类/方法 public class test{ //注解可以显示赋值,若没有默认值,我们就必须给注解赋值 @MyAnnotation(name = "小明") public void test(){ } }(三)反射(获取注解的途径)
Java反射机制的反射类:Class,Method,Field,Constructor
常用方法getClass,getinterfaces,getMethods,getDeclaredField,getDeclaredConstructor,getAnnotarion,Class.forName(“字符串”),newInstance
用反射操作
public class T { private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public T() { } public T(int age) { this.age = age; } public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { Class c1 = Class.forName("com.test53_Annotation.T"); //通过无参构造器(本质)创建对象 T my1 =(T) c1.newInstance(); System.out.println(my1); //通过反射获取构造器并创建对象 Constructor constructor= c1.getDeclaredConstructor(int.class); T my2 = (T) constructor.newInstance(10); System.out.println(my2); //通过反射获取方法 T my3 =(T) c1.newInstance(); Method MYmethod = c1.getMethod("setAge", int.class); //invoke:激活 //给方法传递(对象,“方法的值”)没有值填null MYmethod.invoke(my3,10086); System.out.println(my3.getAge()); //通过反射操作属性 T my4 = (T)c1.newInstance(); Field MYage = c1.getDeclaredField("age"); MYage.setAccessible(true);//关闭权限检测,可以访问private修饰的字段,其他class,method同理 MYage.set(my4,999); System.out.println(my4.age); } }通过反射获取注解信息
import lombok.Data; import java.lang.annotation.*; import java.lang.reflect.Field; public class Test1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { //获取类的对象 Class c1 = Class.forName("com.test53_Annotation.Student"); Annotation[] annotations = c1.getAnnotations(); //通过反射获取注解 for (Annotation a:annotations ) { System.out.println(a); } //通过反射获取Table注解的值 Table table = (Table) c1.getAnnotation(Table.class); System.out.println(table.value()); //通过反射获取Field注解的值 Field f1 = c1.getDeclaredField("name"); f1.setAccessible(true);//修改权限 MField mf = f1.getAnnotation(MField.class); System.out.println(mf.columnName()+" "+mf.type()+" "+mf.length()); } } @Data @Table(value = "db_mybatis") class Student{ @MField(columnName = "id",type = "int",length = 10) private int id; @MField(columnName = "age",type = "int",length = 3) private int age; @MField(columnName = "name",type = "varchar",length = 5) private String name; public Student(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } public Student(){ } } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Table{ String value();//表名 } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface MField { String columnName();//列名 String type();//类型 int length();//长度 }(五)连接点
在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名,方法名,方法参数等
- 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint
- 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类(导入包的时候注意是org.aspectj.lang下的JoinPoint)
//公共切入点表达式 @Pointcut("execution(* com.itheima.service.DeptService.*(..))") private void pt(){} @Before("pt()") public void before(JoinPoint joinPoint){ log.info("MyAspect8 ... before ..."); } @Around("pt()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { log.info("MyAspect8 around before ..."); //1. 获取 目标对象的类名 . String className = joinPoint.getTarget().getClass().getName();//Target:目标 log.info("目标对象的类名:{}", className); //2. 获取 目标方法的方法名 . String methodName = joinPoint.getSignature().getName();//Signature:全称是Method Signature,方法签名,包括方法名称、参数列表等 log.info("目标方法的方法名: {}",methodName); //3. 获取 目标方法运行时传入的参数 . Object[] args = joinPoint.getArgs();//Args:Arguments的缩写,方法参数 log.info("目标方法运行时传入的参数: {}", Arrays.toString(args)); //4. 放行 目标方法执行 . Object result = joinPoint.proceed(); //5. 获取 目标方法运行的返回值 . log.info("目标方法运行的返回值: {}",result); log.info("MyAspect8 around after ..."); return result; }(六)AOP记录操作日志
将案例中增删改相关接口的操作日志记录到数据库中
包括操作人,操作时间,执行方法的全类名,执行方法名,方法运行时参数,返回值,方法执行时长
具体步骤:
创建自定义注解类 @Log
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { }创建AOP切面类
@Component @Slf4j @Aspect public class LogAspect { @Autowired private HttpServletRequest httpServletRequest; //HTTPServletRequest是Tomcat收到请求后创建的, // 然后传给Spring的Servlet @Autowired private OperateLogMapper operateLogMapper; @Pointcut("@annotation(com.itheima.anno.Log)")//注解配置切入点表达式 public void pt(){ } @Around("pt()")//使用环绕通知 public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable { //获取操作人id,即获取jwt令牌 String jwt = httpServletRequest.getHeader("token"); Claims claims = JwtUtils.parseJWT(jwt); Integer operateUser =(Integer) claims.get("ID"); //获取操作时间 LocalDateTime operateTime = LocalDateTime.now(); //操作类名 String className = joinPoint.getTarget().getClass().getName(); //操作方法名 String methodName = joinPoint.getSignature().getName(); //操作方法参数 Object[] args = joinPoint.getArgs(); String methodParams = Arrays.toString(args);//把集合元素变成字符串 //操作耗时 //记录开始时间 long begin = System.currentTimeMillis(); //调用原始方法运行 Object result = joinPoint.proceed(); //方法返回值 String returnValue = JSONObject.toJSONString(result); //记录结束时间,计算方法执行耗时 long end = System.currentTimeMillis(); Long costTime = end - begin; //记录操作日志 OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime); operateLogMapper.insert(operateLog); log.info("AOP记录操作日志:{}",operateLog); return result; } }在对应增删改方法上加上@Log注解
十一、SpringBoot底层原理讲解
(一)配置优先级
springboot中提供了properties,yaml,yml三种配置文件格式,当三个文件都配置了同一个属性时,优先级properties>yml>yaml
springboot除了支持配置文件属性配置,还支持Java系统属性和命令行参数的方式进行属性配置
java系统属性
-Dserver.port = 9000命令行参数(优先级最高)
--server.port = 10010
(二)bean的管理
主动获取bean对象
根据name获取bean对象
Object getBean(String name)根据类型获取bean
<T> T getBean(Class<T> requiredType)根据name获取bean(带类型转换)
<T> TgetBean(String name, Class<T> requiredType)在测试类中自动注入ApplicationContext对象,即IOC容器对象,调用其getBean方法获取bean对象
bean的作用域
可以通过@Scope注解来进行作用域的配置
@Scope("prototype") @RestController @RequestMapping("/depts") public class DeptController{ }默认初始化时期是在容器启动的时候,可以使用@Lazy注解来延迟初始化(延迟到第一次使用时)
Spring支持五种作用域,后三种在web环境下才生效:
作用域 说明 singleton 容器内同名称的bean只有一个实例(单例)(默认 prototype 每次使用bean时都会创建新的实例(非单例) request 每个请求范围中都会创建新的实例 session 每个会话范围内都会创建新的实例 application 每个应用范围内会创建新的实例
(三)第三方bean
因为第三方文件是只读文件,所以不能直接在类上加上@Component注解,非自定义注解就需要用@Bean注解
在启动类中的方法加上@Bean注解将方法返回值交给IOC容器管理,成为IOC容器的bean对象(不推荐)
@SpringBootApplication public class SpringbootApplication{ @Bean //此注解会将方法返回值交给IOC容器管理,成为IOC容器的bean对象 public SAXReader saxReader(){ //声明第三方bean return new SAXReader(); } }通过@Configuration注解声明一个配置类(CommonConfig),实现对这些bean进行集中管理(推荐)
@Configuration public class CommonConfig{ @Bean //此注解会将方法返回值交给IOC容器管理,成为IOC容器的bean对象 public SAXReader saxReader(){ //声明第三方bean return new SAXReader(); } }如果第三方bean需要依赖其他bean对象,可以在bean定义方法中设置形参进行依赖注入
@Configuration public class CommonConfig{ @Bean //此注解会将方法返回值交给IOC容器管理,成为IOC容器的bean对象 //可以通过@Bean注解的name/value属性指定bean的名称 public SAXReader saxReader(DeptService deptService){ //注入对应方法形参,spring容器会进行自动装配 System.out.println(deptService); return new SAXReader(); } }
(四)SpringBoot原理
spring基本框架是Spring Framework,但是其配置、依赖繁琐,所以官方推出了springboot框架来简化基于Spring框架的开发。
SpringBoot提供了起步依赖,自动配置功能则简化了框架在使用时bean的声明、bean的配置
1. 起步依赖
而在SpringBoot依赖中只需要引入一个依赖
原理就是maven的依赖传递
2. 自动配置
概念:SpringBoot的自动配置就是当Spring容器启动后,一些配置类、bean对象就自动存入到 IOC 容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作
方案一:@ComponentScan 组件扫描(引入第三方依赖)
@ComponentScan({"com.example","com.itheima"}) @SpringBootApplication public class SpringbootWebConfigApplication{ }但是使用繁琐,性能低
方案二:@Import 导入,使用@Import导入的类会被Spring加载到IOC容器中,导入形式有以下几种
- 导入 普通类
- 导入 配置类
- 导入ImportSelector 接口实现类
- @EnableXxxx注解,封装@Import注解(springboot所采用的方式)
//普通类 @Import({TokenParser.class})//第三方类 @SpringBootApplication public class SpringbootWebConfigApplication{ }//配置类 @Import({HeaderConfig.class})//第三方配置类 @SpringBootApplication public class SpringbootWebConfigApplication{ }//导入ImportSelector接口实现类 @Import({MyImportSelector.class}) @SpringBootApplication public class SpringbootWebConfigApplication{ } //实现类定义 public class MyImportSelector implements ImportSelector{ public String[] selectImports(AnnotationMetadata importingClassMetadata){//类中需要实现的方法 return new String[] {"com.example.HeaderComfig"};//返回包含需要导入的类名的字符串数组 } }//第三方自定义注解,一般第三方都会配置好,在@Import注解里声明需要导入的类 @Retention(RetenrionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(MyimportSelector.class)//需要导入的类 public @interface EnableHeaderConfig{ } //加上自定义注解 @EnableHeaderConfig @SpringBootApplication public class SpringbootWebConfigApplication{ }@SpirngBootApplication注解标识在SpringBoot工程的引导类上,是SpringBoot中最重要的注解,由三部分组成
- @SpringBootConfiguration:该注解与@Configuration注解作用相同,声明当前类是一个配置类
- @ComponentScan:组件扫描,默认扫描当前引导类所在包以及子包
- @EnableAutoConfiguration:SpringBoot实现自动化配置的核心注解
@Conditional条件装配注解
- 作用:按照一定条件判断,在满足条件之后才会注册对应的bean对象到Spring的IOC容器中
- 位置:方法、类
- @Conditional本事是一个父注解,派生处大量的子注解:
- @ConditionalOnClass:判断环境中是否有对应字节码文件,才注册bean到IOC容器中
- @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器中
- @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册到IOC容器中
十二、Maven高级
(一)分模块设计与开发
将项目按照功能拆分成若干子模块,方便项目的管理维护、拓展,也方便模块之间的相互调用,资源共享
步骤:
创建maven模块 tilas-pojo,存放实体类
创建maven模块 tilas-utils,存放相关工具类
导入对应依赖至新建模块中,刷新依赖
再在原来程序的xml文件中把新建的模块导入依赖,刷新依赖
<!--tlias-pojo包--> <dependency> <groupId>com.itheima</groupId> <artifactId>tlias-pojo</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
注意:分模块开发需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕再拆分
(二)继承与聚合
继承与聚合
- 作用
- 聚合用于快速构建项目
- 继承用于简化依赖配置、统一管理依赖
- 相同点
- 聚合与继承的pom.xml文件打包方式均为pom,可以将两种关系制作到同一个pom文件中
- 聚合与继承均属于设计型模块,并无实际的模块内容
- 不同点
- 聚合是在聚合工程中配置关系,聚合可以感知到参与聚合的模块有哪些
- 继承时在子模块中配置关系,父模块无法感知哪些子模块继承自己
继承
1.继承关系
概念:继承描述的是两个工程之间的关系,与java中的继承类似,子工程可以继承父工程中的配置信息(依赖),常见于关系的继承,把子工程公共的依赖配置在父工程下
实现:
<parent> ...... <!--父工程的坐标--> </parent>创建maven模块tlias-parent,设置打包方式pom(默认jar),同时让其继承spring-boot-starter-parent工程(与java相同单继承机制,此处是因为tlias-web-management模块继承了spring-boot-starter-parent)
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.5</version> </parent> <groupId>com.itheima</groupId> <artifactId>tlias-parent</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <!--修改打包方式,用packing-->在子工程的pom.xml文件中配置继承关系
<parent> <groupId>com.itheima</groupId> <artifactId>tlias-parent</artifactId> <version>1.0-SNAPSHOT</version> <relativePath>../tlias-parent/pom.xml</relativePath><!-- ../表示退一级目录 --> </parent>在父工程中配置子工程共有依赖
注意事项:
relativePath指定父工程的pom文件的相对位置(如果不指定,将从本地仓库/远程仓库查找该工程)
如果父子工程都配置了同一个依赖的不同版本,以子工程的为准
结构说明,实例案例是拆分项目,实际开发可能是第二种
补充说明:
- jar:普通的模块打包,springboot项目基本都是jar包(内嵌tomcat运行)
- war:普通web程序打包,需要部署在外部的tomcat服务器中运行
- pom:父工程或聚合工程,该模块不写代码,仅进行依赖管理
2.版本锁定
前提条件:不是所有模块有相同的依赖,部分依赖相同时,通过版本锁定来控制其版本号相同(注意:管理并不是导入,管理之后还需要在子工程中导入相关依赖,只是不需要添加版本号,需要单独列出)
在maven中,可以在父工程的pom文件中通过来统一管理版本依赖
此时,子工程引入依赖时,无需指定版本号,父工程统一管理。变更依赖版本只需要在父工程中统一修改
父工程:
<dependencyManagement> <dependencies> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> </dependencies> </dependencyManagement>子工程:
<dependencies> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency> </dependencies>自定义属性/引用属性
<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!--自定义属性--> <lombok.version>1.18.24</lombok.version> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <!--${}替代--> </dependency> </dependencies>聚合
打包:maven生命周期中的packge
(若本地仓库没有相应依赖模块,需要在maven生命周期中选择install安装到本地仓库)
传统打包方式:打包某个工程时,需要先安装其所依赖的工程以及其父工程,再对项目进行打包,非常繁琐
- 聚合:将多个模块组织成一个整体,同时进行项目的构建
- 聚合工程:一个不具有业务功能的“空”工程(有且仅有一个pom文件),可以使用父工程
- 作用:快速构建项目,无需手动构建
maven中可以通过设置当前聚合工程所包含的子模块名称
聚合工程中所包含的模块,在构建时,会自动根据模块间的依赖关系设置构建顺序,与modules中的顺序无关
<!--聚合--> <modules> <module>../tlias-pojo</module> ../表示退一层级(这里表示parent的xml,需要退一层找parent同级的模块) <module>../tlias-utils</module> <module>../tlias-web-management</module> </modules>(三)私服
介绍:私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用于代理位于外部的中央仓库,用于解决团队内部的资源共享和资源同步问题
依赖的查找顺序:本地仓库 -》私服 -》中央仓库
一般开发中私服不需要我们自己搭建
<groupId>com.itheima</groupId> <artifactId>tlias-parent</artifactId> <version>1.0-SNAPSHOT</version> //这里就是项目的版本 <packaging>pom</packaging>私服配置说明
访问私服:http://192.168.150.101:8081
访问密码:admin/admin
使用私服,需要在maven的settings.xml配置文件中,做如下配置:
1.需要在 servers 标签中,配置访问私服的个人凭证(访问的用户名和密码)
<server> <id>maven-releases</id> <username>admin</username> <password>admin</password> </server> <server> <id>maven-snapshots</id> <username>admin</username> <password>admin</password> </server>2.在 mirrors 中只配置我们自己私服的连接地址(如果之前配置过阿里云,需要直接替换掉)
<mirror> <id>maven-public</id> <mirrorOf>*</mirrorOf> <url>http://192.168.150.101:8081/repository/maven-public/</url> </mirror>3.需要在 profiles 中,增加如下配置,来指定snapshot快照版本的依赖,依然允许使用
<profile> <id>allow-snapshots</id> <activation> <activeByDefault>true</activeByDefault> </activation> <repositories> <repository> <id>maven-public</id> <url>http://192.168.150.101:8081/repository/maven-public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </profile>4.如果需要上传自己的项目到私服上,需要在项目的pom.xml文件中,增加如下配置,来配置项目发布的地址(也就是私服的地址)
<distributionManagement> <!-- release版本的发布地址 --> <repository> <id>maven-releases</id> <url>http://192.168.150.101:8081/repository/maven-releases/</url> </repository> <!-- snapshot版本的发布地址 --> <snapshotRepository> <id>maven-snapshots</id> <url>http://192.168.150.101:8081/repository/maven-snapshots/</url> </snapshotRepository> </distributionManagement>发布项目,直接运行 deploy 生命周期即可 (发布时,建议跳过单元测试)
启动本地私服
解压: apache-maven-nexus.zip
进入目录: apache-maven-nexus\nexus-3.39.0-01\bin
启动服务:双击 start.bat
访问服务:localhost:8081
私服配置说明:将上述配置私服信息的 192.168.150.101 改为 localhost
说真的,这两年看着身边一个个搞Java、C++、前端、数据、架构的开始卷大模型,挺唏嘘的。大家最开始都是写接口、搞Spring Boot、连数据库、配Redis,稳稳当当过日子。
结果GPT、DeepSeek火了之后,整条线上的人都开始有点慌了,大家都在想:“我是不是要学大模型,不然这饭碗还能保多久?”
先给出最直接的答案:一定要把现有的技术和大模型结合起来,而不是抛弃你们现有技术!掌握AI能力的Java工程师比纯Java岗要吃香的多。
即使现在裁员、降薪、团队解散的比比皆是……但后续的趋势一定是AI应用落地!大模型方向才是实现职业升级、提升薪资待遇的绝佳机遇!
如何学习AGI大模型?
作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
2025最新版CSDN大礼包:《AGI大模型学习资源包》免费分享**
一、2025最新大模型学习路线
一个明确的学习路线可以帮助新人了解从哪里开始,按照什么顺序学习,以及需要掌握哪些知识点。大模型领域涉及的知识点非常广泛,没有明确的学习路线可能会导致新人感到迷茫,不知道应该专注于哪些内容。
我们把学习路线分成L1到L4四个阶段,一步步带你从入门到进阶,从理论到实战。
L1级别:AI大模型时代的华丽登场
L1阶段:我们会去了解大模型的基础知识,以及大模型在各个行业的应用和分析;学习理解大模型的核心原理,关键技术,以及大模型应用场景;通过理论原理结合多个项目实战,从提示工程基础到提示工程进阶,掌握Prompt提示工程。
L2级别:AI大模型RAG应用开发工程
L2阶段是我们的AI大模型RAG应用开发工程,我们会去学习RAG检索增强生成:包括Naive RAG、Advanced-RAG以及RAG性能评估,还有GraphRAG在内的多个RAG热门项目的分析。
L3级别:大模型Agent应用架构进阶实践
L3阶段:大模型Agent应用架构进阶实现,我们会去学习LangChain、 LIamaIndex框架,也会学习到AutoGPT、 MetaGPT等多Agent系统,打造我们自己的Agent智能体;同时还可以学习到包括Coze、Dify在内的可视化工具的使用。
L4级别:大模型微调与私有化部署
L4阶段:大模型的微调和私有化部署,我们会更加深入的探讨Transformer架构,学习大模型的微调技术,利用DeepSpeed、Lamam Factory等工具快速进行模型微调;并通过Ollama、vLLM等推理部署框架,实现模型的快速部署。
整个大模型学习路线L1主要是对大模型的理论基础、生态以及提示词他的一个学习掌握;而L3 L4更多的是通过项目实战来掌握大模型的应用开发,针对以上大模型的学习路线我们也整理了对应的学习视频教程,和配套的学习资料。
二、大模型经典PDF书籍
书籍和学习文档资料是学习大模型过程中必不可少的,我们精选了一系列深入探讨大模型技术的书籍和学习文档,它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础。(书籍含电子版PDF)
三、大模型视频教程
对于很多自学或者没有基础的同学来说,书籍这些纯文字类的学习教材会觉得比较晦涩难以理解,因此,我们提供了丰富的大模型视频教程,以动态、形象的方式展示技术概念,帮助你更快、更轻松地掌握核心知识。
四、大模型项目实战
学以致用,当你的理论知识积累到一定程度,就需要通过项目实战,在实际操作中检验和巩固你所学到的知识,同时为你找工作和职业发展打下坚实的基础。
五、大模型面试题
面试不仅是技术的较量,更需要充分的准备。
在你已经掌握了大模型技术之后,就需要开始准备面试,我们将提供精心整理的大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
2025最新版CSDN大礼包:《AGI大模型学习资源包》免费分享