项目工程
一、环境搭建
-
准备数据库表(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 = 100MB
2.阿里云存储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的配置参照该文章:
集成
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.com
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
#配置单个文件最大上传大小
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最新版优快云大礼包:《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)

三、大模型视频教程
对于很多自学或者没有基础的同学来说,书籍这些纯文字类的学习教材会觉得比较晦涩难以理解,因此,我们提供了丰富的大模型视频教程,以动态、形象的方式展示技术概念,帮助你更快、更轻松地掌握核心知识。

四、大模型项目实战
学以致用 ,当你的理论知识积累到一定程度,就需要通过项目实战,在实际操作中检验和巩固你所学到的知识,同时为你找工作和职业发展打下坚实的基础。

五、大模型面试题
面试不仅是技术的较量,更需要充分的准备。
在你已经掌握了大模型技术之后,就需要开始准备面试,我们将提供精心整理的大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

997

被折叠的 条评论
为什么被折叠?



