关于SpringBoot | 字数总计: 4.3k | 阅读时长: 21分钟 | 阅读量:
本文是学习Springboot时的一些笔记。
面向注解开发!!!
零、pom.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.7.5</version > <relativePath /> </parent > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.2.2</version > </dependency > <dependency > <groupId > com.mysql</groupId > <artifactId > mysql-connector-j</artifactId > <scope > runtime</scope > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > com.github.pagehelper</groupId > <artifactId > pagehelper-spring-boot-starter</artifactId > <version > 1.4.2</version > </dependency > <dependency > <groupId > com.aliyun.oss</groupId > <artifactId > aliyun-sdk-oss</artifactId > <version > 3.15.1</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 > <dependency > <groupId > org.glassfish.jaxb</groupId > <artifactId > jaxb-runtime</artifactId > <version > 2.3.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > </dependency > <dependency > <groupId > io.jsonwebtoken</groupId > <artifactId > jjwt</artifactId > <version > 0.9.1</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.76</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-aop</artifactId > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <configuration > <excludes > <exclude > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </exclude > </excludes > </configuration > </plugin > </plugins > </build > </project >
一、注解 1.1 Controller层 1 2 3 4 @RestController @Slf4j @RequiredArgsConstructor @RequestMapping("/depts")
1.2 GET、POST、DELETE、PUT、PATCCH等 1 2 3 4 5 @GetMapping("/depts") @PostMapping @DeleteMapping("/{deptId}") @PutMapping @PatchMapping
1.3 Server层 1 2 @Service @RequiredArgsConstructor
1.4 Mapper层
1.5 pojo实现类 1 2 3 @Data @NoArgsConstructor @AllArgsConstructor
二、application.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server: port: 8090 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springboot username: root password: root servlet: multipart: max-file-size: 1MB max-request-size: 10MB mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true aliyun: oss: endpoint: https://oss-cn-chengdu.aliyuncs.com accessKeyId: accessKeySecret: bucketName: hrui-zym logging: level: org.springframework.jdbc.support.JdbcTransactionManager: debug
三、统一返回实现类 在 pojo 包下实现 Result.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.herui.springbootcase.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class Result { private Boolean status; private String desc; private Object data; public static Result success () { return new Result (true , "successful" , null ); } public static Result success (String msg) { return new Result (true , msg, null ); } public static Result success (String msg, Object data) { return new Result (true , msg, data); } public static Result success (Object data) { return new Result (true , "successful" , data); } public static Result error (String msg) { return new Result (false , msg, null ); } }
四、跨域请求 方法1:过滤器实现跨域 创建 config 包,在此包下创建 CorsConfig.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.herui.springbootcase.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;import org.springframework.web.filter.CorsFilter;@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter () { CorsConfiguration corsConfiguration = new CorsConfiguration (); corsConfiguration.addAllowedOrigin("http://localhost:9528/" ); corsConfiguration.addAllowedHeader("*" ); corsConfiguration.addAllowedMethod("*" ); corsConfiguration.setAllowCredentials(true ); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource (); source.registerCorsConfiguration("/**" , corsConfiguration); return new CorsFilter (source); } }
方法2:局部跨域 Controller层在需要跨域的类或者方法上加上@CrossOrigin 该注解即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @CrossOrigin(origins = {"/*"},maxAge = 3600) @RestController @Slf4j @RequiredArgsConstructor @RequestMapping("/depts") public class DeptController { private final DeptService deptService; @GetMapping public Result getDeptList () { log.info("查询全部的部门数据" ); List<Dept> deptList = deptService.getDeptList(); return Result.success(deptList); } }
五、登录拦截 只有携带token的用户才能访问资源
方法1:JWT令牌+过滤器 注解:@WebFilter(urlPatterns = {“/*”})
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package com.herui.springbootcase.filter;import com.alibaba.fastjson.JSONObject;import com.herui.springbootcase.pojo.Result;import com.herui.springbootcase.utils.JwtUtils;import jakarta.servlet.*;import jakarta.servlet.annotation.WebFilter;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import lombok.extern.slf4j.Slf4j;import org.springframework.util.StringUtils;import java.io.IOException;@Slf4j @WebFilter(urlPatterns = {"/*"}) public class AuthFilter implements Filter { @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; String url = request.getRequestURL().toString(); log.info("请求的URL:{}" , url); if (url.contains("login" )){ filterChain.doFilter(servletRequest,servletResponse); }else { String token = request.getHeader("token" ); if ( !StringUtils.hasLength(token)){ log.info("请求头的token为空,用户未登录" ); Result error = Result.error("NOT_LOGIN" ); String notLogin = JSONObject.toJSONString(error); servletResponse.getWriter().write(notLogin); } else { try { JwtUtils.parseJWT(token); log.info("令牌合法" ); filterChain.doFilter(servletRequest,servletResponse); } catch (Exception e) { e.printStackTrace(); log.info("解析令牌失败,返回未登录错误信息!" ); Result error = Result.error("NOT_LOGIN" ); String notLogin = JSONObject.toJSONString(error); servletResponse.getWriter().write(notLogin); } } } } }
JWT令牌 的生成与解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package com.herui.springbootcase.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 final String signKey = "cmVnZ2llX3Rha2VvdXRfdG9rZW5faGVydWlfand0X3NlY3JldGtleQ==" ; private static final Long expire = 30000L ; private static SecretKey generateKeyByDecoders () { return Keys.hmacShaKeyFor(Decoders.BASE64.decode(signKey)); } public static String generateJwt (Map<String, Object> claims) { return Jwts.builder() .addClaims(claims) .signWith(generateKeyByDecoders()) .setExpiration(new Date (System.currentTimeMillis() + expire)) .compact(); } public static Claims parseJWT (String jwt) { return Jwts.parserBuilder() .setSigningKey(generateKeyByDecoders()) .build() .parseClaimsJws(jwt) .getBody(); } }
在Application.java 启动类中添加@ServletComponentScan
1 2 3 4 5 6 7 8 9 @ServletComponentScan @SpringBootApplication public class SpringbootCaseApplication { public static void main (String[] args) { SpringApplication.run(SpringbootCaseApplication.class, args); } }
方法2: JWT令牌+Interceptor(拦截器) 编写拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package com.herui.springbootcase.interceptor;import com.alibaba.fastjson.JSONObject;import com.herui.springbootcase.pojo.Result;import com.herui.springbootcase.utils.JwtUtils;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;@Component @Slf4j public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle......" ); String url = request.getRequestURL().toString(); log.info("请求的URL:{}" , url); String token = request.getHeader("token" ); if ( !StringUtils.hasLength(token)){ log.info("请求头的token为空,用户未登录" ); Result error = Result.error("NOT_LOGIN" ); String notLogin = JSONObject.toJSONString(error); response.getWriter().write(notLogin); return false ; } else { try { JwtUtils.parseJWT(token); log.info("令牌合法" ); return true ; } catch (Exception e) { e.printStackTrace(); log.info("解析令牌失败,返回未登录错误信息!" ); Result error = Result.error("NOT_LOGIN" ); String notLogin = JSONObject.toJSONString(error); response.getWriter().write(notLogin); return false ; } } } @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......" );; } }
注册拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.herui.springbootcase.config;import com.herui.springbootcase.interceptor.AuthInterceptor;import lombok.RequiredArgsConstructor;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { private final AuthInterceptor authInterceptor; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(authInterceptor).addPathPatterns("/**" ).excludePathPatterns("/login" ); } }
在Application.java 启动类中添加@ServletComponentScan
1 2 3 4 5 6 7 8 9 @ServletComponentScan @SpringBootApplication public class SpringbootCaseApplication { public static void main (String[] args) { SpringApplication.run(SpringbootCaseApplication.class, args); } }
六、全局异常处理类 注解:@RestControllerAdvice
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.herui.springbootcase.exception;import com.herui.springbootcase.pojo.Result;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public Result ex (Exception exception) { exception.printStackTrace(); return Result.error("对不起,操作失败,请联系管理员!" ); } }
七、事务回滚 注解:@Transactional(rollbackFor = Exception.class)
业务要求:删除部门的时候,把部门下的员工也一起删除。
实现方法:使用逻辑外键
异常情况:删除了部门,但是此时出现异常,导致该部门下的员工没有删除。出现数据不一致的情况。
在service实现类中,业务代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Override @Transactional(rollbackFor = Exception.class) public void deleteDeptById (Integer deptId) { Log log = new Log (); log.setCreateTime(LocalDateTime.now()); try { deptMapper.deleteDeptById(deptId); empMapper.deleteEmpByDeptId(deptId); } catch (Exception e) { e.printStackTrace(); log.setDesc("执行解散ID为: " +deptId+" 的部门操作!失败!" ); throw e; } finally { if (log.getDesc().length() == 0 ){ log.setDesc("执行解散ID为: " +deptId+" 的部门操作!成功!" ); } logService.insertLog(log); } }
!!!注意: 如果异常被try{}catch{}了,事务就不回滚了,如果想让事务回滚必须再往外抛try{}catch{throw Exception}。因为一旦你try{}catch{}了。系统会认为你已经手动处理了异常,就不会进行回滚操作。
添加新的事务
1 2 3 4 5 6 7 8 @Override @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public void insertLog (Log log) { logMapper.insertLog(log); }
八、阿里云OSS文件上传 在阿里云OSS对象存储服务中创建对应的bucket。并且引入相关依赖。
属性类: AliOSSProperties.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.herui.springbootcase.utils;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;@Component @Data @ConfigurationProperties(prefix = "aliyun.oss") public class AliOSSProperties { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; }
文件上传实现类:AliOSSUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package com.herui.springbootcase.utils;import com.aliyun.oss.OSS;import com.aliyun.oss.OSSClientBuilder;import lombok.Data;import lombok.RequiredArgsConstructor;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;import org.springframework.web.multipart.MultipartFile;import java.io.*;import java.util.UUID;@Data @Component @RequiredArgsConstructor public class AliOSSUtils { private final AliOSSProperties aliOSSProperties; public String upload (MultipartFile file) throws IOException { String endpoint = aliOSSProperties.getEndpoint(); String accessKeyId = aliOSSProperties.getAccessKeyId(); String bucketName = aliOSSProperties.getBucketName(); String accessKeySecret = aliOSSProperties.getAccessKeySecret(); InputStream inputStream = file.getInputStream(); String originalFilename = file.getOriginalFilename(); String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("." )); OSS ossClient = new OSSClientBuilder ().build(endpoint, accessKeyId, accessKeySecret); ossClient.putObject(bucketName, fileName, inputStream); String url = endpoint.split("//" )[0 ] + "//" + bucketName + "." + endpoint.split("//" )[1 ] + "/" + fileName; ossClient.shutdown(); return url; } }
使用类:UploadController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.herui.springbootcase.controller;import com.herui.springbootcase.pojo.Result;import com.herui.springbootcase.utils.AliOSSUtils;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import java.io.IOException;@RestController @Slf4j @RequiredArgsConstructor public class UploadController { private final AliOSSUtils aliOSSUtils; @PostMapping("/upload") public Result uploadFile (MultipartFile image) throws IOException { log.info("文件上传, 文件名: {}" , image.getOriginalFilename()); String url = aliOSSUtils.upload(image); log.info("文件上传成功,访问路径: {}" , url); return Result.success("file upload successfully" , url); } }
九、分页查询 业务要求:根据(姓名、性别、入职时间)(以上三个可以没有)查询员工信息,并分页展示
pojo 实体类: PageBean.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.herui.springbootcase.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.util.List;@Data @NoArgsConstructor @AllArgsConstructor public class PageBean { private Long total; private List<Emp> rows; }
Controller:
1 2 3 4 5 6 7 8 9 10 @GetMapping public Result getEmpListByPage (String name, Short gender, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end, @RequestParam(defaultValue = "1") Integer pageNumber, @RequestParam(defaultValue = "5") Integer pageSize) { log.info("分页查询,参数: {}, {}, {}, {}, {}, {}" ,name, gender, begin, end, pageNumber, pageSize); PageBean empListByPage = empService.getEmpListByPage(name, gender, begin, end, pageNumber, pageSize); return Result.success("select successfully!" , empListByPage); }
service:
1 2 3 4 5 6 7 @Override public PageBean getEmpListByPage (String name, Short gender, LocalDate begin, LocalDate end, Integer pageNumber, Integer pageSize) { PageHelper.startPage(pageNumber, pageSize); List<Emp> empList = empMapper.getEmpList(name, gender, begin, end); Page<Emp> page = (Page<Emp>) empList; return new PageBean (page.getTotal(), page.getResult()); }
Mapper
1 2 3 4 5 6 7 8 9 10 11 12 @Select("select count(*) from sb_emp;") public Integer getCount () ;public List<Emp> getEmpList (String name, Short gender, LocalDate begin, LocalDate end) ;
mapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <select id ="getEmpList" resultType ="com.herui.springbootcase.pojo.Emp" > select * from sb_emp <where > <if test ="name != null and name != ''" > emp_name like concat('%', #{name}, '%') </if > <if test ="gender != null and gender != ''" > and emp_gender = #{gender} </if > <if test ="begin != null and end != null" > and emp_entryDate between #{begin} and #{end} </if > </where > order by update_time desc </select >
十、AOP(面向切面编程)
AOP的作用:在程序运行期间在不修改源代码的基础上对已有方法进行增强(无侵入性: 解耦)
注解:
@Compoent :注册为spring组件
@Aspect :注册切面类
@Order(number) : 【可选】执行顺序;对于@Before,数字越小越先执行;对于@After,数字越小越后执行
导入依赖:
pom.xml
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-aop</artifactId > </dependency >
Spring中AOP的通知类型:
@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行【重点掌握 】
@Before:前置通知,此注解标注的通知方法在目标方法前被执行
@After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
@AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
@AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行
切入点表达式
1、execution 1 execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
示例:
1 2 3 4 5 @Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))") execution(* com.itheima.service.impl.DeptServiceImpl.delete(*)) execution(* com..*.*(..))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Slf4j @Component @Aspect public class MyAspect7 { @Pointcut("@annotation(com.itheima.anno.MyLog)") private void pt () {} @Before("pt()") public void before (JoinPoint joinPoint) { log.info(joinPoint.getSignature().getName() + " MyAspect7 -> before ..." ); } @Before("pt()") public void after (JoinPoint joinPoint) { log.info(joinPoint.getSignature().getName() + " MyAspect7 -> after ..." ); } @Around("pt()") public Object around (ProceedingJoinPoint pjp) throws Throwable { String name = pjp.getTarget().getClass().getName(); log.info("目标类名:{}" ,name); String methodName = pjp.getSignature().getName(); log.info("目标方法名:{}" ,methodName); Object[] args = pjp.getArgs(); log.info("目标方法参数:{}" , Arrays.toString(args)); Object returnValue = pjp.proceed(); return returnValue; } }
2、annotation 自定义注解 :MyLog
1 2 3 4 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyLog {}
在业务类要做为连接点的方法上添加自定义注解
业务类 :DeptServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Service public class DeptServiceImpl implements DeptService { @Autowired private DeptMapper deptMapper; @Override @MyLog public List<Dept> list () { List<Dept> deptList = deptMapper.list(); return deptList; } }
切面类 :LogAspect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Component @Aspect public class MyAspect6 { @Before("@annotation(com.itheima.anno.MyLog)") public void before () { log.info("MyAspect6 -> before ..." ); } @After("@annotation(com.itheima.anno.MyLog)") public void after () { log.info("MyAspect6 -> after ..." ); } }