AOP和yaml的使用

一、AOP

AOP:Aspect Oriented Programming,面向切面编程,其作用是对方法进行增强

AOP相关概念

  • 通知是什么:要增强的逻辑

  • 目标是什么:被增强的对象

  • 代理是什么:增强后的对象

  • 连接点是什么:目标对象的方法

  • 切点是什么:被增强的方法

  • 切面是什么:通知和切点组成切面

1. AOP解决方法耗时问题

  1. 引入切点表达式依赖
	 <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>
  1. 编写切面类:写切面、写通知、调目标、定切点
@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() 根据方法注解匹配

  • 使用注解来完成切入点表达式的编写的好处

    1. 通过业务代码就能确定切入点
    2. 简化切入点表达式的编写
  • 根据指定注解匹配要增强的方法

    • 自定义注解
    /**
     * @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 支持两种代理方式

  1. jdk 动态代理: 仅支持接口方式的代理

  2. cglib 代理:支持接口方式的代理,以及子类方式的代理

使用哪一种?

  • springboot 默认配置 spring.aop.proxy-target-class=true

    • 此时无论目标是否实现接口,都是采用【cglib 技术】,生成的都是子类代理
  • 如果设置了 spring.aop.proxy-target-class=false,那么又分两种情况

    • 如果【目标】实现了接口,Spring 会【jdk 动态代理技术】生成代理

    • 如果【目标】没有实现接口,Spring 会采用【cglib 技术】生成代理

二、 yaml配置

SpringBoot 除了支持 properties 的配置文件格式以外,还支持 yaml 格式的配置文件,文件名不变仍然固定为 application,后缀变成 yamlyml

  • 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
  • 语法格式

    1. 大小写敏感
    2. 冒号之后如果有值,那么冒号与值之间必须加空格
    3. 以空格的缩进表示层次关系,左对齐的数据属于同一层级的
    4. 不能以tab的缩进表示层次关系
    5. 注释与properties一样,使用#
    6. 一些特殊字符 &, * 等有特殊含义,要用单引号或单引号引起来
    7. 可以使用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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.han、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值