2021-12-28 Spring day4(动态代理,Aop五种通知)

文章目录

  • 一、Aop
  • 二、动态代理举例
  • 三、Aop五种通知
    • 1. 添加依赖3个
    • 2. 定义切点
    • 3. 新建切面
    • 4. 推荐使用@Pointcut注解定义切点

一、Aop

Aspect Oriented Programming ,面向切面编程,这是对面向对象思想的一种补充。

面向切面编程,就是在程序运行时,不改变程序源码的情况下,动态的增强方法的功能,常见的使用场景非常多:

  1. 日志
  2. 事务 --公共代码抽取出来
  3. 数据库操作

以上操作有很多模板化的代码

AOP常见概念
切点:要添加代码的地方,称作切点
通知(增强):通知就是向切点动态添加的代码
切面:切点+通知
连接点:切点的定义

AOP实现
Aop实际上是基于Java的动态代理来实现的
Java中的动态代理有两种实现方式:cglib,jdk

二、动态代理举例

  1. 定义一个计算器接口
package org.kk.aop;

/**
 * @program: ioc01
 * @description:实现动态代理,定义一个计算器接口
 * @author: zjx
 * @create: 2021-12-27 22:38
 **/
public interface MyCalculator {
    int add(int a,int b);

}

  1. 实现接口
    MyCalculatorImpl是接口的实现类
package org.kk.aop;

/**
 * @program: ioc01
 * @description: 计算器接口的一个实现
 * @author: zjx
 * @create: 2021-12-27 22:44
 **/
public class MyCalculatorImpl implements MyCalculator{


    @Override
    public int add(int a, int b) {
        return a+b;
    }
}

  1. 实现代理类 利用Proxy类
    上面的实体经过代理类的加工,返回了一个新实体(添加了方法)
package org.kk.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @program: ioc01
 * @description: 代理类里面增强功能
 * @author: zjx
 * @create: 2021-12-27 22:45
 **/
public class CalculatorProxy {
    public static Object getInstance(MyCalculatorImpl myCalculator)
    {
        //第一个参数:classLoader,第二个参数实现的接口,第三个参数为new的InvocationHandler
        return Proxy.newProxyInstance(CalculatorProxy.class.getClassLoader(), myCalculator.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            /**
            * @Description:
            * @Param: [proxy:代理对象, method:代理方法(add), args 方法的参数]
            * @return: java.lang.Object 方法的返回值
            * @Author: zjx
            * @Date: 2021/12/27
            */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("方法执行前");
                Object invoke = method.invoke(myCalculator, args);
                System.out.println("方法执行后");
                return invoke;
                //也可以return 99,那么调用add后的结果也就是99了
            }
        });

    }
}

  1. 调用,获得一个代理类加工后的对象
public class Main {
    @Test
    public void test1()
    {
        MyCalculatorImpl myCalculator=new MyCalculatorImpl();
        MyCalculator calculator = ((MyCalculator) CalculatorProxy.getInstance(myCalculator));
        int add=calculator.add(3,4);
        System.out.println("add="+add);
    }

}

调用结果:

方法执行前
方法执行后
add=7
  1. 总结
    Aop的原理与上面的动态代理一样,都是不需要改变原来的代码(Impl),添加新的方法(通过代理类)

三、Aop五种通知

Aop的通知类型有五种:

在以上代码中,在method.invoke之前执行——前置通知,在method.invoke之后执行——后置通知,异常通知,返回通知,环绕通知

1. 添加依赖3个

    <dependencies>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.19.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.7</version>
        </dependency>


    </dependencies>

2. 定义切点

两种方式:使用注解(不推荐)、使用规则

使用注解

  1. 自定义Action注解
package org.kk.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
}

  1. Impl中对方法添加自定义注解
package org.kk.aop;

import org.springframework.stereotype.Component;

/**
 * @program: ioc01
 * @description: 计算器接口的一个实现
 * @author: zjx
 * @create: 2021-12-27 22:44
 **/

@Component
public class MyCalculatorImpl implements MyCalculator{
    @Override
    @Action  //对需要增强的方法添加这个自定义注解 侵入式--要改源码
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public void min(int a, int b) {
        System.out.println(a+"-"+b+"="+(a-b));
    }

    //然后想要给这两个方法增加一个日志 在不改变源码的前提下
}

  1. 新建类,用于定义通知

3. 新建切面

@Component
@Aspect

其中的方法用于通知:前置通知 @Before("@annotation(org.javaboy.app.Action)" ) 注解的包路径

package org.kk.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @program: ioc01
 * @description:定义增强和通知
 * @author: zjx
 * @create: 2021-12-28 13:46
 **/
@Component
@Aspect //表示这是一个切面 五种通知
public class LogAspect {
    /**
    * @Description: 前置通知,在add逻辑开始之前执行
    * @Param: [joinPoint]
    * @return: void
    * @Author: zjx
    * @Date: 2021/12/28
    */
    @Before("@annotation(Action)")//有Action注解的方法 执行之前会有前置通知
    public void before(JoinPoint joinPoint)
    {
        String name=joinPoint.getSignature().getName();
        System.out.println(name+"方法开始执行了");

    }

}

配置类添加注解,需要开启自动代理

package org.kk.aop;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

/**
 * @program: ioc01
 * @description:
 * @author: zjx
 * @create: 2021-12-28 13:56
 **/
@Configuration
@ComponentScan
//还要开启自动代理
@EnableAspectJAutoProxy
public class JavaConfig {

}

测试


    @Test
    public void test2()
    {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
        MyCalculator bean = context.getBean(MyCalculator.class);
        int res = bean.add(3, 4);
        System.out.println(res);

    }

结果

add方法开始执行了
7

四种通知方法

package org.kk.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @program: ioc01
 * @description:定义增强和通知
 * @author: zjx
 * @create: 2021-12-28 13:46
 **/
@Component
@Aspect //表示这是一个切面 五种通知
public class LogAspect {
    /**
    * @Description: 前置通知,在add逻辑开始之前执行
    * @Param: [joinPoint]
    * @return: void
    * @Author: zjx
    * @Date: 2021/12/28
    */
    @Before("@annotation(Action)")//有Action注解的方法 执行之前会有前置通知
    public void before(JoinPoint joinPoint)
    {
        String name=joinPoint.getSignature().getName();
        System.out.println(name+"方法开始执行了");

    }

    //后置通知
    @After("@annotation(Action)")
    public void after(JoinPoint joinPoint)
    {
        String name=joinPoint.getSignature().getName();
        System.out.println(name+"方法执行结束了");
    }

    //返回通知
    /**
    * @Description: 返回通知,可以在该方法中获取目标方法的返回值,如果目标方法的返回值是void,则收到null
    * @Param: [joinPoint, r 返回的参数名称,和这里的方法的参数名一一对应]
    * @return: void
    * @Author: zjx
    * @Date: 2021/12/28
    */
    @AfterReturning(value = "@annotation(Action))",returning = "r")
    public void returning(JoinPoint joinPoint,Integer r)//类型不匹配方法返回类型,是不会返回结果的;可以用Object
    {
        String name=joinPoint.getSignature().getName();
        System.out.println(name+"返回通知:"+r);//r是方法执行的结果
    }

    /**
    * @Description: 异常通知,当目标方法抛出异常时,该方法会被触发
    * @Param: [joinPoint, e 异常参数,和方法的参数名一一对应,注意异常的类型]
    * @return: void
    * @Author: zjx
    * @Date: 2021/12/28
    */
    @AfterThrowing(value = "@annotation(Action)",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Exception e)
    {
        String name=joinPoint.getSignature().getName();
        System.out.println(name+"方法异常通知:"+e.getMessage());

    }
    /**
    * @Description: 环绕通知,是上面四种方法集大成者,环绕通知的核心类似于在反射中执行方法
    * @Param: [pjp]
    * @return: java.lang.Object
    * @Author: zjx
    * @Date: 2021/12/28
    */
    @Around("@annotation(Action)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //这个有点类似于method.invoke方法,我们可以在这个方法前后分别添加日志,相当于前置和后置通知
        Object proceed=pjp.proceed(new Object[]{5,5});
        
        return proceed;

    }

}

    //优化1 通过方法来定义切点  下面的注解都改 @Before("pointcut()")
    @Pointcut("@annotation(Action)")
    public void pointcut()
    {

    }

非侵入式定义切点:

4. 推荐使用@Pointcut注解定义切点

    //非侵入式定义切点
    @Pointcut("execution(* org.kk.aop.service.*.*(..))") //任意类 任意方法 参数类型、个数任意
    public void pointcut()
    {

    }

package org.kk.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @program: ioc01
 * @description:定义增强和通知--定义的切面
 * @author: zjx
 * @create: 2021-12-28 13:46
 **/
@Component
@Aspect //表示这是一个切面 五种通知
public class LogAspect {
//    //优化1 通过方法来定义切点  下面的注解都改 @Before("pointcut()")
//    @Pointcut("@annotation(Action)")
//    public void pointcut()
//    {
//
//    }
    //非侵入式定义切点
    @Pointcut("execution(* org.kk.aop.service.*.*(..))") //任意类 任意方法 参数类型、个数任意
    public void pointcut()
    {

    }




    /**
    * @Description: 前置通知,在add逻辑开始之前执行
    * @Param: [joinPoint]
    * @return: void
    * @Author: zjx
    * @Date: 2021/12/28
    */
    @Before("pointcut()")//有Action注解的方法 执行之前会有前置通知
    public void before(JoinPoint joinPoint)
    {
        String name=joinPoint.getSignature().getName();
        System.out.println(name+"方法开始执行了");

    }

    //后置通知
    @After("pointcut()")
    public void after(JoinPoint joinPoint)
    {
        String name=joinPoint.getSignature().getName();
        System.out.println(name+"方法执行结束了");
    }

    //返回通知
    /**
    * @Description: 返回通知,可以在该方法中获取目标方法的返回值,如果目标方法的返回值是void,则收到null
    * @Param: [joinPoint, r 返回的参数名称,和这里的方法的参数名一一对应]
    * @return: void
    * @Author: zjx
    * @Date: 2021/12/28
    */
    @AfterReturning(value = "pointcut())",returning = "r")
    public void returning(JoinPoint joinPoint,Integer r)//类型不匹配方法返回类型,是不会返回结果的;可以用Object
    {
        String name=joinPoint.getSignature().getName();
        System.out.println(name+"返回通知:"+r);//r是方法执行的结果
    }

    /**
    * @Description: 异常通知,当目标方法抛出异常时,该方法会被触发
    * @Param: [joinPoint, e 异常参数,和方法的参数名一一对应,注意异常的类型]
    * @return: void
    * @Author: zjx
    * @Date: 2021/12/28
    */
    @AfterThrowing(value = "pointcut()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Exception e)
    {
        String name=joinPoint.getSignature().getName();
        System.out.println(name+"方法异常通知:"+e.getMessage());

    }
    /**
    * @Description: 环绕通知,是上面四种方法集大成者,环绕通知的核心类似于在反射中执行方法
    * @Param: [pjp]
    * @return: java.lang.Object
    * @Author: zjx
    * @Date: 2021/12/28
    */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //这个有点类似于method.invoke方法,我们可以在这个方法前后分别添加日志,相当于前置和后置通知
        Object proceed=pjp.proceed(new Object[]{5,5});  //return了10

        return proceed;

    }







}


xml配置Aop

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.kk.aop.LogAspect" name="logAspect"/>
    <bean class="org.kk.aop.service.MyCalculatorImpl" name="myCalculator"/>
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* org.kk.aop.service.*.*(..))"/>
        <aop:aspect ref="logAspect">
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:before method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

你可能感兴趣的:(JAVA,spring,java,后端)