一、AOP
AOP:Aspect Oriented Programming,面向切面编程,其作用是对方法进行增强。
AOP相关概念
-
通知是什么:要增强的逻辑
-
目标是什么:被增强的对象
-
代理是什么:增强后的对象
-
连接点是什么:目标对象的方法
-
切点是什么:被增强的方法
-
切面是什么:通知和切点组成切面
1. AOP解决方法耗时问题
- 引入切点表达式依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
- 编写切面类:写切面、写通知、调目标、定切点
@Slf4j
@Component //非MVC架构,让spring创建对象,保存到Spring容器进行管理
@Aspect //切面:表示该类是一个切面类,里面定义通知+切点,提供代理逻辑
public class Aspect1 {
/**
* 定义通知:使用@Around()注解定义的方法叫做通知方法,里面编写通知代码
* 定义切点:在@Around()注解中使用切面表达式找到要增强的方法
* *:任意一个返回值、任意方法、任意一个类型的参数
* .. :任意多个任意类型的参数
* @param pjp 正在执行的连接点对象,表示正在执行的目标对象的方法
* @return 哪里调用代理对象方法就返回到哪里
*/
@Around("execution(* com.hsl.service.impl.PersonServiceImpl.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//1.获取开始时间
long start = System.nanoTime();
//2.执行操作:调用目标方法
Object result = pjp.proceed();
//3.获取结束时间,计算耗时
long end = System.nanoTime();
log.info("共耗时:{}", end-start);
//哪里调用代理对象就返回到哪里
return result;
}
}
总结:如果进行了AOP配置,那么容器中存放的是代理对象,注入的也是代理对象
2. 切点表达式
切点表达式:用来匹配哪些目标方法需要应用通知。
execution(返回值类型 包名.类名.方法名(参数类型))
- * 可以通配任意返回值类型、包名、类名、方法名、或任意类型的一个参数
- … 可以通配任意层级的包、或任意类型、任意个数的参数
- +:专用于匹配子类类型
- 包名和类名可以省略]
- 类名写接口,可以匹配到它的实现类
//使用切点表达式匹配要增强的方法。
@Around("execution(* com.hsl.service.impl.PersonServiceImpl.*(..))")
-
&& :连接两个切入点表达式,表示两个切入点表达式同时成立的匹配
-
|| :连接两个切入点表达式,表示两个切入点表达式成立任意一个的匹配
-
! :连接单个切入点表达式,表示该切入点表达式不成立的匹配
@annotation() 根据方法注解匹配
-
使用注解来完成切入点表达式的编写的好处
- 通过业务代码就能确定切入点
- 简化切入点表达式的编写
-
根据指定注解匹配要增强的方法
- 自定义注解
/** * @annotation() 的使用方式 2: @Around("@annotation(anno)") * 第一步:自定义注解MyAnno * 第二步:在需要增强的方法上使用@MyAnno注解 * 第三步:在通知方法上使用@MyAnno代替切点表达式 */ @Slf4j @Component //让spring创建对象,保存到spring容器中进行管理 @Aspect //切面,表示该类是一个切面类,里面定义通知+切点,提供代理逻辑 public class Aspect4 { //需求:对PersonServiceImpl中的save和update方法进行增强,其他方法不增强 @Around("@annotation(anno)") //含义:在通知方法中找一个注解名叫anno的注解,根据这个注解找切点方法 public Object around(ProceedingJoinPoint pjp,MyAnno anno) throws Throwable { log.info("anno的value值 = {}", anno.value()); //1 获取开始时间 long start = System.nanoTime(); //2 执行操作:调用目标对象方法 Object result = pjp.proceed();//调用目标对象方法 //3 获取结束时间,计算耗时并打印 long end = System.nanoTime(); log.info("共耗时:{}", end-start); //哪里调用代理对象方法就返回到哪里 return result; } }
- 在要增强的类或者方法上使用注解
@Target(ElementType.METHOD) //MyAnno注解只能写在方法上 @Retention(RetentionPolicy.RUNTIME) //MyAnno在运行期可以被获取到 public @interface MyAnno { String value() default ""; }
- 在通知方法注解中使用@annotation注解指定要匹配的注解全类名
@Around(“@annotation(com.hsl.anno.MyAnno)”)
-
抽取公共切入点表达式
@Pointcut("execution(* com.hsl.service.impl.PersonServiceImpl.save(..)))")
public void pt1() {}
3. 通知类型
@Around:环绕通知,一种手动调用目标对象方法的通知方法
@Around("execution(* com.hsl.service.impl.PersonServiceImpl.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object result = null;//调用目标对象方法
try {
//1 获取开始时间 ---前增强(前置通知)
long start = System.nanoTime();
//2 执行操作:调用目标对象方法
result = pjp.proceed();
//3 获取结束时间,计算耗时并打印 --- 后增强(后置通知)
long end = System.nanoTime();
log.info("共耗时:{}", end-start);
} catch (Throwable throwable) {
throwable.printStackTrace();
//定义异常通知
} finally {
//定义最终通知
}
//哪里调用代理对象方法就返回到哪里
return result;
}
- @Before:前置通知,在目标方法执行之前执行
//前置通知:被@Before注解标注,在目标方法执行之前之前
@Before("execution(* com.hsl.service.impl.PersonServiceImpl.*(..))")
public void before(){
log.info("before...前置通知");
}
- @AfterReturning :后置通知,在目标方法执行之后且没有异常时执行
//后置通知/返回后通知:被@AfterReturning注解标注,在目标方法执行之后并且没有出现异常时才执行。
@AfterReturning("execution(* com.hsl.service.impl.PersonServiceImpl.*(..))")
public void afterReturning(){
log.info("afterReturning...后置通知/返回后通知");
}
- @AfterThrowing :异常通知,在目标方法执行之后且出现异常时执行
//异常通知:被@AfterThrowing注解标注,在目标方法执行之后并且必须出现异常时才执行。
@AfterThrowing("execution(* com.hsl.service.impl.PersonServiceImpl.*(..))")
public void afterThrowing(){
log.info("afterThrowing...异常通知");
}
- @After :最终通知,在目标方法执行之后无论是否出现异常都执行
//最终通知:被@After注解标注,在目标方法执行之后无论是否出现异常时都执行。
@After("execution(* com.hsl.service.impl.PersonServiceImpl.*(..))")
public void after(){
log.info("after...最终通知");
}
(前置通知、后置通知、异常通知、最终通知) 与 环绕通知(@Around)区别是:它们不用考虑目标方法的执行,而 @Around 需要自己调用 ProceedingJoinPoint.proceed() 来让目标方法执行。
4. 通知获取连接点
简单理解就是目标方法,在Spring 中用 JoinPoint 抽象了连接点,用它可以获得方法执行时的相关信息,如方法名、方法参数类型、方法实际参数
- 所有的通知方法上都可以添加连接点参数,但是必须是在第一个。
- 对于 @Around 通知,获取连接点信息只能使用 ProceedingJoinPoint
- 对于其他四种通知,获取连接点信息只能使用 JoinPoint,它是 ProceedingJoinPoint 的父类型
//1.获取方法签名
MethodSignature signature = (MethodSignature) pjp.getSignature();
//2.获取方法名称
Method method = signature.getMethod();
//3.获取方法的返回值类型
Class returnType = signature.getReturnType();
//4.获取参数类型
Class[] parameterTypes = signature.getParameterTypes();
//5.获取方法实际参数
Object[] args = pjp.getArgs();
5. 通知顺序
当有多个切面的切点都匹配目标时,多个通知方法都会被执行。
-
如果还有下一个通知,则调用下一个通知
-
如果没有下一个通知,则调用目标
执行顺序:
- 默认按照 bean 的名称字母排序
- 用 @Order(数字) 加在切面类上来控制顺序
- 目标前的通知方法:数字小先执行
- 目标后的通知方法:数字小后执行
6. 代理方式
Spring 支持两种代理方式
-
jdk 动态代理: 仅支持接口方式的代理
-
cglib 代理:支持接口方式的代理,以及子类方式的代理
使用哪一种?
-
springboot 默认配置 spring.aop.proxy-target-class=true
- 此时无论目标是否实现接口,都是采用【cglib 技术】,生成的都是子类代理
-
如果设置了 spring.aop.proxy-target-class=false,那么又分两种情况
-
如果【目标】实现了接口,Spring 会【jdk 动态代理技术】生成代理
-
如果【目标】没有实现接口,Spring 会采用【cglib 技术】生成代理
-
二、 yaml配置
SpringBoot 除了支持 properties 的配置文件格式以外,还支持 yaml 格式的配置文件,文件名不变仍然固定为 application,后缀变成 yaml 或 yml
- Properties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql:///db1?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
- Yaml
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///db1?useSSL=false
username: root
password: root
-
语法格式
- 大小写敏感
- 冒号之后如果有值,那么冒号与值之间必须加空格
- 以空格的缩进表示层次关系,左对齐的数据属于同一层级的
- 不能以tab的缩进表示层次关系
- 注释与properties一样,使用#
- 一些特殊字符 &, * 等有特殊含义,要用单引号或单引号引起来
- 可以使用EL表达式${}引用文件中的属性
-
1、在application.yml中定义数据
user:
username: zhangsan
# username: ${name}
age: 20
address:
- 北京
- 武汉
- 北京
- 深圳
- 广州
- 2、定义javabean,封装yaml中的属性
@Configuration//配置类
@Data
@ConfigurationProperties(prefix = "user")
public class UserConfig {
private String username;
private Integer age;
private String[] address;
}