1. SpringBoot中AOP切面编程
回顾Spring框架中的AOP切面编程
引言
springboot是对原有项目中spring框架和springmvc的进一步封装,因此在springboot中同样支持spring框架中AOP切面编程,不过在springboot中为了快速开发仅仅提供了注解方式的切面编程.
SpringBoot中AOP切面编程
使用
用到的包结构
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
service接口
public interface UserService {
void save(String name);
void delete(Integer id);
void update(String name);
String find(String name);
}
service接口实现类
@Service
public class UserServiceImpl implements UserService{
@Override
public void save(String name) {
System.out.println("处理save核心业务逻辑,调用DAO~~");
}
@Override
public void delete(Integer id) {
System.out.println("处理delete核心业务逻辑,调用DAO~~");
}
@Override
public void update(String name) {
System.out.println("处理update核心业务逻辑,调用DAO~~");
}
@Override
public String find(String name) {
System.out.println("处理find核心业务逻辑,调用DAO~~");
return name;
}
}
自定义切面配置类
在一个类上加上@Configuration 表示这是一个配置类,相当于Spring中的spring.xml,并在该配置类上加入@Aspect 表示这是一个切面配置类,专门用来配置切面的。创建一个config包专门放配置相关的类,在config包中创建一个自定义切面配置类
/**
* 自定义切面配置类
*/
@Configuration // 代表当前这个类是一个spring的配置类
@Aspect // 代表这个类是一个切面配置类
public class MyAspectConfig {
// 切面Aspect = Advice 附加操作 + Pointcut 切入点
// 切入点表达式:
// 1. execution 方法级别切入点表达式 运行效率低
// 2. within:类级别切入点表达式 控制越粗,效率越高
// 3. 基于注解的切入点表达式 @annotation(com.baizhi.annotations.xxx)
//@Around("execution(* com.baizhi.service.*.*(..))") // 环绕通知
//@Around("within(com.baizhi.service.*)")
@Around("@annotation(com.baizhi.annotations.MyAdvice)")
// 返回值作用: 用来将业务方法的返回结果返回给调用者
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("========进入环绕的前置操作==========");
System.out.println("当前执行类: " + proceedingJoinPoint.getTarget());
System.out.println("方法名: " + proceedingJoinPoint.getSignature().getName());
// 放行目标方法执行
Object proceed = proceedingJoinPoint.proceed(); // 继续处理 业务逻辑方法执行
System.out.println("========进入环绕的后置操作==========");
return proceed;
}
@Before("execution(* com.baizhi.service.*.*(..))") // 前置切面
// @Before()代表这是一个核心业务逻辑执行之前的附加操作 value:用来书写切入点表达式 配置附加操作在哪里生效
// 方法名随便
public void before(JoinPoint joinPoint){
System.out.println("========前置附加操作==========");
System.out.println("当前执行目标类: " + joinPoint.getTarget());
System.out.println("当前执行目标类中方法: " + joinPoint.getSignature().getName());
}
@After("execution(* com.baizhi.service.*.*(..))") // 后置切面
public void After(JoinPoint joinPoint){
System.out.println("========后置附加操作==========");
System.out.println("当前执行目标类: " + joinPoint.getTarget());
System.out.println("当前执行目标类中方法: " + joinPoint.getSignature().getName());
}
}
测试
@Test
public void testFind(){
String name = userService.find("xiaochen");
System.out.println(name);
}
注:在测试某种切面时已将其他切面注释掉
测试前置切面
测试后置切面
测试环绕通知
这里想要补充一下关于切入点表达式的第三种,使用注解形式的切入点表达式@annotation,这种表达式非常灵活,也是开发中经常使用的。
首先我们建一个专门放注解的包annotation,然后在其中创建一个自定义注解
// 指定运行时生效
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 指定作用范围为方法
public @interface MyAdvice {
}
之后我们在环绕通知的@Around的value属性指明切入点表达式使用这个注解,之后如果在哪个方法上加上了自定义注解@MyAdvice,则该方法就会配置该通知
在service接口实现类的方法上加上这个注解表明配置了环绕通知
测试一下
总结
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
相关注解
# 切面注解
- @Aspect 用来类上,代表这个类是一个切面
- @Before 用在方法上代表这个方法是一个前置通知方法
- @After 用在方法上代表这个方法是一个后置通知方法 @Around 用在方法上代表这个方法是一个环绕的方法
- @Around 用在方法上代表这个方法是一个环绕的方法
前置切面
@Aspect
@Configuration
public class MyAspect {
@Before("execution(* com.baizhi.service.*.*(..))")
public void before(JoinPoint joinPoint){
System.out.println("前置通知");
joinPoint.getTarget();//目标对象
joinPoint.getSignature();//方法签名
joinPoint.getArgs();//方法参数
}
}
后置切面
@Aspect
@Configuration
public class MyAspect {
@After("execution(* com.baizhi.service.*.*(..))")
public void before(JoinPoint joinPoint){
System.out.println("后置通知");
joinPoint.getTarget();//目标对象
joinPoint.getSignature();//方法签名
joinPoint.getArgs();//方法参数
}
}
注意: 前置通知和后置通知都没有返回值,方法参数都为joinpoint
环绕切面
@Aspect
@Configuration
public class MyAspect {
@Around("execution(* com.baizhi.service.*.*(..))")
public Object before(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("进入环绕通知");
proceedingJoinPoint.getTarget();//目标对象
proceedingJoinPoint.getSignature();//方法签名
proceedingJoinPoint.getArgs();//方法参数
Object proceed = proceedingJoinPoint.proceed();//放行执行目标方法
System.out.println("目标方法执行之后回到环绕通知");
return proceed;//返回目标方法返回值
}
}
注意: 环绕通知存在返回值,参数为ProceedingJoinPoint,如果执行放行,不会执行目标方法,一旦放行必须将目标方法的返回值返回,否则调用者无法接受返回数据
2. SpringBoot中文件上传
文件上传
用户访问当前系统,将自己本地计算机中文件通过浏览器上传到当前系统所在的服务器过程中称之为文件的上传
文件上传: 用户将自己计算机中文件 上传到 项目所在服务器过程、文件服务器、OSS 称之为文件上传
引入依赖
默认SpringBoot是不能解析jsp的,所以引入依赖
<!--解析jsp的依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
还需要设置springboot可以访问jsp
用到的包结构
配置文件
application.yml
server:
port: 8989
servlet:
context-path: /springboot_day5
jsp:
init-parameters:
development: true # 打开jsp开发模式
spring:
mvc:
view:
prefix: / # 配置视图前缀
suffix: .jsp # 配置视图后缀
servlet:
multipart: # 修改文件上传的大小限制
max-request-size: 10MB # 用来控制文件上传大小的限制
max-file-size: 1MB # 用来指定服务器端最大文件大小
profiles:
active: local # 激活本地配置生效
# 调整日志
logging:
level:
root: info
com.baizhi: debug # 开启com.baizhi包中所有debug日志
本地环境配置文件application-local.yml
# 指定文件上传位置
file:
upload:
dir: C:\Users\86152\Desktop\imgs # 指定本次测试上传目录,日后如果在生产环境下,换成生产情况下绝对路径就可以了
生产环境配置文件application-prod.yml
# 指定文件上传位置
file:
upload:
dir: /home/upload # 指定本次测试上传目录,日后如果在生产环境下,换成生产情况下绝对路径就可以了
开发upload.jsp页面
1. 表单提交方式必须是post,因为get是通过地址栏传递,地址栏是有长度限制的
2. 表单的enctype属性必须为multipart/form-data,既能对文本做编码,又能对二进制做编码
3. 后台接受变量名字要与文件选择name属性一致
<%@ page pageEncoding="UTF-8" contentType="text/html; UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>springboot系列课程</title>
</head>
<body>
<h1>测试文件上传</h1>
<form action="${pageContext.request.contextPath}/file/uploadByJarDeploy" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" name="上传文件">
</form>
</body>
</html>
开发controller
用两种文件上传的方式,推荐使用绝对路径的那种方式,而不推荐使用由相对路径获取绝对路径的方式,因为项目上线会打包成jar,这样的话由相对路径获取绝对路径是没有权限的
@Controller
@RequestMapping("file")
public class FileController {
private static final Logger log = LoggerFactory.getLogger(FileController.class);
@Value("${file.upload.dir}")
private String realPath;
/**
* 第二种文件上传
* 注意:这种方式适用于任何一种部署方式 推荐使用这种方式
* @param file
* @return
* @throws IOException
*/
@RequestMapping("uploadByJarDeploy")
public String uploadByJarDeploy(MultipartFile file) throws IOException {
// 文件名
String originalFilename = file.getOriginalFilename();
log.debug("文件名: {}", file.getOriginalFilename());
log.debug("文件大小: {}", file.getSize()); // 单位为字节
log.debug("文件类型: {}", file.getContentType());
// 改名
String ext = originalFilename.substring(originalFilename.lastIndexOf('.'));
String newFileName = new SimpleDateFormat("yyyyMMddHHmmssSS").format(new Date()) + ext;
// 上传文件到哪
file.transferTo(new File("C:\\Users\\86152\\Desktop\\imgs", newFileName));
return "redirect:/upload.jsp";
}
/**
* 用来测试文件上传 - 这种方式不能用于jar包部署
* 注意:这种方式存在局限性,不推荐再使用这种方式进行文件上传了
* @return
*/
@RequestMapping("upload")
// MultipartFile变量名字必须和表单中file的name一致才能完成赋值
// 定义:接收文件对象MultipartFile名字要与from中Input type="file"标签name属性名一致
public String upload(MultipartFile file, HttpServletRequest request) throws IOException {
// 文件名
String originalFilename = file.getOriginalFilename();
log.debug("文件名: {}", file.getOriginalFilename());
log.debug("文件大小: {}", file.getSize()); // 单位为字节
log.debug("文件类型: {}", file.getContentType());
// 1. 根据相对 上传 "upload" 获取绝对路径(真实路径) /user/桌面.... 服务器: /home/springboot_day5...
String realPath = request.getSession().getServletContext().getRealPath("/upload");
log.debug("获取绝对路径: {}", realPath);
// 2. 上传文件 参数1: 将文件写入到哪个目录
// 修改文件名
// 获取后缀
String ext = originalFilename.substring(originalFilename.lastIndexOf('.'));
// 利用时间戳生成新文件名
String newFileName = new SimpleDateFormat("yyyyMMddHHmmssSS").format(new Date()) + ext;
// 路径 文件名
file.transferTo(new File("realPath", newFileName));
return "redirect:/upload.jsp";
}
}