2026/3/29 0:25:28
网站建设
项目流程
做网站按钮,wordpress后台是英文,协会网站建设及维护,12306网站 花了多少钱建设项目工程
一、环境搭建 准备数据库表#xff08;dept、emp#xff09; 创建springboot工程#xff0c;引入对应的起步依赖#xff08;web、mybatis、mysql驱动、lombok#xff09; 配置文件application.properties中引入maybatis的配置信息#xff0c;准备对应的实体类…项目工程一、环境搭建准备数据库表dept、emp创建springboot工程引入对应的起步依赖web、mybatis、mysql驱动、lombok配置文件application.properties中引入maybatis的配置信息准备对应的实体类准备对应的Mapper、Service接口、实现类、Controller基础结构application.properties文件配置spring.application.nameSpring1 #驱动类名称 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.urljdbc:mysql://localhost:3306/mybatis #连接数据库的用户名 spring.datasource.usernameroot #连接数据库的密码 spring.datasource.password123456 #配置mybatis日志指定输出到控制台 mybatis.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl #开启mybatis驼峰命名自动映射开关(例a_column ---aColumn) mybatis.configuration.map-underscore-to-camel-casetrue el-casetrue导入结构controller实体类mapper接口pojo实体类service接口配置xml映射文件编写SQL语句在resources文件夹下新建和Mapper接口名一样在resources下创建包时不能用 “.” 来分层只能用 “/”但是显现出的效果相同约束直接从官方文档拷贝在Mybatis中文网进入“入门”找到配置SQL语句的XML文件?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespaceMapper接口的全类名 /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查询数据 ListDept 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 { /* * 查询全部部门数据*/ ListDept list(); } //实现类Service public class DeptServiceImpl implements DeptService { Autowired private DeptMapper deptMapper;//调用Dao层从数据库获取数据 Override public ListDept list() { return deptMapper.list(); } }在Dao层实现从数据库获取数据用注解方式进行调用/** * 部门管理 */ Mapper public interface DeptMapper { /* * 查询全部部门方法 * */ Select(select * from dept) ListDept 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);PSpostman测试时记得把请求方式换成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);五、员工管理基本数据封装对象PageBeanpackage 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; } }补充RequestParamPathVariable注解的区别RequestParam从前端获取参数,其中的 defaultValue 可设置默认值PathVariable通过请求路径指定参数其中 defaultValue() 设置默认值请求路径/depts/{id}DateTimeFormat其中的pattern可以设置日期的格式如DateTimeFormat (pattern yyyy-MM-dd)RequestBodyRequsetParam 可以接受post请求但是请求的contentType 为 form-data格式而RequestBody接受的请求方式application/json;charsetUTF-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(pagenull)page 1; // if(pageSizenull)pageSize10; 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 ListEmp empSelect(Integer page, Integer pageSize); Select(select count(*) from emp) public Long empTotal(); }2、PageHelper插件配置pom.xml文件!--PageHelper分页插件-- dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.6/version /dependencyEmpMapper/** * 员工信息查询 * return */ Select(select * from emp) public ListEmp list();EmpServicelmpOverride public PageBean empSelect(Integer page, Integer pageSize) { //1.设置分页参数 PageHelper.startPage(page,pageSize); //2.执行查询 ListEmp empList empMapper.list(); PageEmp p (PageEmp) 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.执行查询 ListEmp empList empMapper.list(name,gender,begin ,end); PageEmp p (PageEmp) empList;//强制转换成Page对象 //3.封装pagebean对象 PageBean bean new PageBean(p.getTotal(),p.getResult()); return bean; } }dao层改造list方法mapperpublic ListEmp list(String name, Short gender, LocalDate begin, LocalDate end);EmpMapper映射文件?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.itheima.mapper.EmpMapper select idlist resultTypecom.itheima.pojo.Emp select * from emp where if testname!null name like concat(%,#{name},%) /if if testgender!null and gender #{gender} /if if testbegin!null and end!null and entrydate between #{begin} and #{end} /if /where order by update_time desc /select /mapper二批量删除员工数据contrallerDeleteMapping(/emps/{ids}) public Result Delete(PathVariable ArrayListInteger 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); }daomappervoid Delete(ArrayList ids);xml映射文件!-- 批量删除操作-- delete idDelete delete from emp where id in foreach collectionids itemid separator, open( close) #{id} /foreach /delete三新增员工数据controllerPostMapping(/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); }daoInsert(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中controllerpackage 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第三方服务思路SDKSoftware Development Kit软件开发工具包包括辅助软件开发的依赖jar包、代码示例等Bucket存储空间是用户用于存储对象Object就是文件的容器所有的对象都必须隶属于某个存储空间。开通OSS服务之后就可以进入到阿里云对象存储的控制台点击左侧的 “Bucket列表”创建一个BucketAccessKey:AccessKey IDAccessKey Secret入门程序引入依赖项dependency groupIdcom.aliyun.oss/groupId artifactIdaliyun-sdk-oss/artifactId version3.17.4/version /dependency dependency groupIdjavax.xml.bind/groupId artifactIdjaxb-api/artifactId version2.3.1/version /dependency dependency groupIdjavax.activation/groupId artifactIdactivation/artifactId version1.1.1/version /dependency !-- no more than 2.3.3-- dependency groupIdorg.glassfish.jaxb/groupId artifactIdjaxb-runtime/artifactId version2.3.3/version /dependency上传文件流以下代码用于将文件流上传到目标存储空间examplebucket中exampledir目录下的exampleobject.txt文件。1.修改Endpoint2.填写bucketName3.指定保存至阿里云时的名称 objectName4.指定将要上传的文件的本地路径 filePath5.配置id和secretimport 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博客集成UploadController1.接收上传的图片2.存储图片OSS3.返回访问图片的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的路径返回 } }上传图片接口开发controllerpackage 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.回显controllerGetMapping(/{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); }daoSelect(select * from emp where id #{id}) Emp Search(Integer id);2.修改数据controllerPutMapping 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); }daomappervoid update(Emp emp);映射文件update idupdate update emp set if testusername!null and username! username #{username}, /if if testpassword ! null and password ! password #{password}, /if if testname ! null and name ! name #{name}, /if if testgender ! null gender #{gender}, /if if testimage ! null and image ! image #{image}, /if if testjob ! null job #{job}, /if if testentrydate ! null entrydate #{entrydate}, /if if testdeptId ! null dept_id #{deptId}, /if if testupdateTime ! 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 bucket122333Value 注解用于外部配置的属性注入具体用法如下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.propertiesserver.port 8080 server.address 127.0.0.1application.yml / yaml冒号后要加上空格每一级用两个空格缩进server: port: 8080 address: 127.0.0.1xmlserver port8080/port address127.0.0.1/address /serveryml基本语法大小写分明数值前必须有空格作为分隔符使用缩进表示层级关系缩进时不允许使用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.nameSpring1 #驱动类名称 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.urljdbc:mysql://localhost:3306/mybatis #连接数据库的用户名 spring.datasource.usernameroot #连接数据库的密码 spring.datasource.password123456 #配置mybatis日志指定输出到控制台 mybatis.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl #开启mybatis驼峰命名自动映射开关(例a_column ---aColumn) mybatis.configuration.map-underscore-to-camel-casetrue #配置单个文件最大上传大小 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 groupIdorg.springframework.boot/groupId artifactIdspring-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.客户端会话跟踪技术Cookie2.服务端会话跟踪技术Session3.令牌技术1Cookie1.CookieHTTP请求头包含存储先前通过与所述服务器发送的HTTP cookies Set-Cookie 头2.Set-Cookie所述Set-Cookie HTTP响应头被用于从服务器向代理发送 cookie存储在浏览器本地请求之后返回cookie值下次访问时从浏览器本地发送cookie2Sessions存储在服务器端请求后从服务端查询是否存在相应值但是服务器集群无法使用3JWT令牌主流全称JSON Web Token定义了一种简洁的、自包含的格式用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在这些信息是可靠的组成第一部分Header头记录令牌类型、签名算法等。例如{“alg”:“HS256” , “type”:“JWT”}第二部分Payload有效载荷携带一些自定义信息、默认信息等。例如{“id”:“1” , “username”:“Tom”}第三部分Signature签名防止Token被纂改、确保安全性。将header、payload并加入指定密钥通过指定签名算法计算而来使用场景登录验证登录成功后生成令牌后续每个请求都要携带JWT令牌系统在每次请求处理之前先校验令牌通过后再处理配置xml文件!--JWT令牌-- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency在测试类中添加测试方法生成令牌要是报错ClassNotFoundException,添加依赖jaxb-api!--jaxb-api-- dependency groupIdjavax.xml.bind/groupId artifactIdjaxb-api/artifactId version2.3.1/version /dependency生成令牌Test //生成JWT令牌 public void testGenJwt(){ MapString, Object claims new HashMap(); claims.put(id,1); claims.put(name,tom); String jwt Jwts.builder() .signWith(SignatureAlgorithm.HS256, itheima)//用于设置数字签名的算法例如HS256256位密钥 .setClaims(claims)//自定义内容载荷 .setExpiration(new Date(System.currentTimeMillis() 3600 * 1000))//设置有效期为1小时 .compact();//获取成字符串 System.out.println(jwt); }解析令牌Test public void testParseJWT(){ MapString, 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(MapString, 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){ //登录成功生成令牌下发令牌 MapString,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(用户名或密码错误); } } }四统一拦截1Filter过滤器概念是javaweb三大组件Servlet、Filter、Listener之一过滤器可以把对资源的请求拦截下来基本操作定义Filter定义一个类实现Filter接口PS导入的Filter是javax.servlet.Filter配置Filter在Filter类上加上WebFilter注解配置拦截资源的路径urlPatterns启动类加上ServletComponentScan开启Servlet组件支持定义和配置Filterpackage 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 groupIdcom.alibaba/groupId artifactIdfastjson/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); } }2Interceptor拦截器基本操作定义拦截器定义一个类实现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:debugTransactional//事务注解 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); } }DeptLogMapperpackage 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-finallyTransactional(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 groupIdorg.springframework.boot/groupId artifactIdspring-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( )注解加在切面类上来控制顺序目标方法前的通知数字小的先执行目标方法后的通知数字大的先执行四切入点表达式常见形式1execution(…)根据方法的签名来匹配格式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下的两个方法ListDept list(); 和 void delete(Integer id);Pointcut(execution(* com.itheima.service.DeptService.list()) || execution(* com.itheima.service.DeptService.delete())) private void pointcut(){ }2annotation(…)根据注释匹配用于匹配标识有特定注解的方法Before(annotation(com.itheima.anno.Log)) public void before(){ }步骤自定义注解LogRetention(RetentionPolicy.RUNTIME) Target(ElementType.METHOD)//作用于方法上 public interface MyLog { }在对应方法上加上自定义注解起到标识作用Log Override public ListDept list() { ListDept 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表示存在于运行阶段通常使用的自定义注解使用的都是RUNTIMEDocumented说明该注解将被包含在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反射机制的反射类ClassMethodFieldConstructor常用方法getClassgetinterfacesgetMethodsgetDeclaredFieldgetDeclaredConstructorgetAnnotarionClass.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修饰的字段其他classmethod同理 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();//ArgsArguments的缩写方法参数 log.info(目标方法运行时传入的参数: {}, Arrays.toString(args)); //4. 放行 目标方法执行 . Object result joinPoint.proceed(); //5. 获取 目标方法运行的返回值 . log.info(目标方法运行的返回值: {},result); log.info(MyAspect8 around after ...); return result; }六AOP记录操作日志将案例中增删改相关接口的操作日志记录到数据库中包括操作人操作时间执行方法的全类名执行方法名方法运行时参数返回值方法执行时长具体步骤创建自定义注解类 LogTarget(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中提供了propertiesyamlyml三种配置文件格式当三个文件都配置了同一个属性时优先级propertiesymlyamlspringboot除了支持配置文件属性配置还支持Java系统属性和命令行参数的方式进行属性配置java系统属性-Dserver.port 9000命令行参数优先级最高--server.port 10010二bean的管理主动获取bean对象根据name获取bean对象Object getBean(String name)根据类型获取beanT T getBean(ClassT requiredType)根据name获取bean带类型转换T TgetBean(String name, ClassT 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组件扫描默认扫描当前引导类所在包以及子包EnableAutoConfigurationSpringBoot实现自动化配置的核心注解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 groupIdcom.itheima/groupId artifactIdtlias-pojo/artifactId version1.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-parentparent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.5/version /parent groupIdcom.itheima/groupId artifactIdtlias-parent/artifactId version1.0-SNAPSHOT/version packagingpom/packaging !--修改打包方式用packing--在子工程的pom.xml文件中配置继承关系parent groupIdcom.itheima/groupId artifactIdtlias-parent/artifactId version1.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 groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency /dependencies /dependencyManagement子工程dependencies dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId /dependency /dependencies自定义属性/引用属性properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding !--自定义属性-- lombok.version1.18.24/lombok.version /properties dependencies dependency groupIdorg.projectlombok/groupId artifactIdlombok/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三私服介绍私服是一种特殊的远程仓库它是架设在局域网内的仓库服务用于代理位于外部的中央仓库用于解决团队内部的资源共享和资源同步问题依赖的查找顺序本地仓库 -》私服 -》中央仓库一般开发中私服不需要我们自己搭建groupIdcom.itheima/groupId artifactIdtlias-parent/artifactId version1.0-SNAPSHOT/version //这里就是项目的版本 packagingpom/packaging私服配置说明访问私服http://192.168.150.101:8081访问密码admin/admin使用私服需要在maven的settings.xml配置文件中做如下配置1.需要在 servers 标签中配置访问私服的个人凭证(访问的用户名和密码)server idmaven-releases/id usernameadmin/username passwordadmin/password /server server idmaven-snapshots/id usernameadmin/username passwordadmin/password /server2.在 mirrors 中只配置我们自己私服的连接地址(如果之前配置过阿里云需要直接替换掉)mirror idmaven-public/id mirrorOf*/mirrorOf urlhttp://192.168.150.101:8081/repository/maven-public//url /mirror3.需要在 profiles 中增加如下配置来指定snapshot快照版本的依赖依然允许使用profile idallow-snapshots/id activation activeByDefaulttrue/activeByDefault /activation repositories repository idmaven-public/id urlhttp://192.168.150.101:8081/repository/maven-public//url releases enabledtrue/enabled /releases snapshots enabledtrue/enabled /snapshots /repository /repositories /profile4.如果需要上传自己的项目到私服上需要在项目的pom.xml文件中增加如下配置来配置项目发布的地址(也就是私服的地址)distributionManagement !-- release版本的发布地址 -- repository idmaven-releases/id urlhttp://192.168.150.101:8081/repository/maven-releases//url /repository !-- snapshot版本的发布地址 -- snapshotRepository idmaven-snapshots/id urlhttp://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大模型学习资源包》免费分享