Spring_AOP(spect-Oriented Programming, 面向切面编程)

一、理解AOP

AOP,即面向切面编程,在不改变原有业务逻辑方法前提下,进行增强(如AOP系统日志);能够解耦,有利于维护代码。

二、AOP原理

动态代理设计模式,在bean“初始化后”(AbstractAutoProxyCreator -> postProcessAfterInitialization())进行操作,对目标对象进行增强处理;默认使用JDK动态代理,若没有出现接口 或者 开启动态代理时设置属性,则转而使用CGLIB代理。

三、JDK动态代理

配置类、接口和实现接口类

@ComponentScan(basePackages = "com.igeek.ch01.jdk")
public class MyConfig {
}


public interface ICount {

    public int add(int a , int b);

    public int div(int a , int b);

}


/**
 * TODO
 *
 * @author ding
 * @since 2024/5/22
 *
 * 需求:计算器实现 + 日志实现
 *
 * 代理设计模式:对原有的方法,无侵入性的进行增强
 * 1.JDK动态代理  涉及接口
 * 2.CGLIB代理   无需接口
 */
@Component
public class CountImpl implements ICount{
    @Override
    public int add(int a, int b) {
        int c = a+b;
        System.out.println("a+b = "+c);
        return c ;
    }

    @Override
    public int div(int a, int b) {
        int c = a/b;
        System.out.println("a/b = "+c);
        return c ;
    }
}

获取代理对象

1.直接获取代理对象

public class CountProxyImpl {

    //目标对象
    private ICount target;

    public CountProxyImpl(ICount target){
        this.target = target;
    }

    //获取代理对象的方法
    public ICount getProxy(){
        //代理对象
        //第一个参数:目标对象的类加载器   第二个参数:目标对象所有实现的接口列表   第三个参数:执行目标方法的处理器
        ICount proxy = (ICount)Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * @param proxy  代理对象,一般不使用
                     * @param method 执行目标方法对象
                     * @param args   执行目标方法中的参数列表
                     * return 目标方法的执行结果返回,若目标方法没有返回值则返回null
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //获取执行目标方法的名称
                        String methodName = method.getName();
                        System.out.println("日志追踪 The method "+methodName+" begin with "+ Arrays.toString(args));
                        //执行目标方法
                        Object result = method.invoke(target, args);
                        System.out.println("日志追踪 The method "+methodName+" end with "+result);
                        return result;
                    }
                }
        );
        return proxy;
    }
}

2.在bean“初始化后”获取代理对象,实现增强对象

@Component
public class MyAware implements BeanPostProcessor {

    /**
     * 初始化后
     * @param target   实例bean
     * @param beanName 唯一标识
     * @return 经过处理后的实例bean
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object target, String beanName) throws BeansException {
        //只针对计算器ICount进行方法业务逻辑的增强
        if(target instanceof ICount){
            //代理对象
            ICount proxy = (ICount)Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //前置通知
                            System.out.println("MyAware 日志追踪 The method "+method.getName()+" begin with "+ Arrays.toString(args));

                            //执行目标方法
                            Object result = null;
                            try {
                                result = method.invoke(target, args);
                                //返回通知
                                System.out.println("MyAware 日志追踪 The method "+method.getName()+" end with "+ result);
                            }catch (Exception e){
                                //异常通知
                                System.out.println("MyAware 日志追踪 The method "+method.getName()+" 发生异常,"+ e.getMessage());
                                e.printStackTrace();
                            }
                            //后置通知
                            System.out.println("MyAware 日志追踪 The method "+method.getName()+" end with ");
                            return result;
                        }
                    }
            );
            //返回代理对象
            return proxy;
        }
        //返回目标对象
        return target;
    }
}

 MainTest

public class MainTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);

        /*//目标对象
        CountImpl target = ac.getBean(CountImpl.class);
        //com.igeek.ch01.jdk.CountImpl
        System.out.println("target = "+target.getClass().getName());
        //代理对象
        ICount proxy = new CountProxyImpl(target).getProxy();
        //com.sun.proxy.$Proxy7
        System.out.println("proxy = "+proxy.getClass().getName());*/

        //Spring IOC 后
        ICount proxy = ac.getBean(ICount.class);
        //com.sun.proxy.$Proxy8
        System.out.println("proxy = "+proxy.getClass().getName());

        proxy.add(10 , 20);
        proxy.div(50 , 20);
    }

}

四、AOP 注解字 + 配置类 版本

1.修改 pom.xml 文件
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.13</version>
</dependency>

<!-- 引入aspectj依赖 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
2.新建Spring的核心配置类

Maven项目要在Spring IOC容器中启用AsppectJ注解支持;若为SpringBoot项目,则在启动类上添加

//开启AspectJ功能 proxyTargetClass = true
//1.@EnableAspectJAutoProxy  默认使用的是JDK动态代理,必须要有实现接口
//2.若出现以下情况则将会使用CGLIB动态代理
//2.1 若未出现实现接口,则直接使用CGLIB动态代理
//2.2 若设置proxyTargetClass = true,则直接使用CGLIB动态代理
//当前类是一个配置类
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.igeek.ch02")
public class MyConfig {
}


//接口类
public interface ICount {

    public int add(int a , int b);

    public int sub(int a , int b);

    public int mul(int a , int b);

    public int div(int a , int b);

}

//实现类

@Component
public class CountImpl /*implements ICount*/ {
    //@Override
    public int add(int a, int b) {
        int c = a+b;
        System.out.println("a+b = "+c);
        return c ;
    }

    //@Override
    public int sub(int a, int b) {
        int c = a-b;
        System.out.println("a-b = "+c);
        return c ;
    }

    //@Override
    public int mul(int a, int b) {
        int c = a*b;
        System.out.println("a*b = "+c);
        return c ;
    }

    //@Override
    public int div(int a, int b) {
        int c = a/b;
        System.out.println("a/b = "+c);
        return c ;
    }
}
3.@Aspect

在AspectJ注解中,切面只是一个带有@Aspect注解的Java类。通知是标注有某种注解的简单的Java方法。

4.通知

AsectJ支持五种类型的通知注解

4.1 前置通知

@Before:前置通知,在方法执行之前执行

@Component
@Aspect
@Order(1)  //可以存在多个切面类,通过@Order来设置切面类的先后执行顺序,数字越小越先执行
public class LogAspect {

    /**
     * 前置通知 @Before("execution(切入点)")
     *
     * 切点表达式
     * execution(访问权限  返回值  包名.类名.方法名(形参列表)) 表达式
     * 例如:execution(public int com.igeek.ch02.log.ICount.add(int , int))
     * 简化:execution(* ICount.*(..))
     * 第一个*:代表任意访问权限和任意返回值类型
     * 第二个*:代表任意方法
     * 第三个..:代表任意的形参列表
     *
     * 连接点  JoinPoint
     * 通过连接点获取目标方法及其参数列表
     */
    @Before("execution(* ICount.*(..))")
    public void beforeAdvice(JoinPoint joinPoint){
        //获取目标方法名
        String name = joinPoint.getSignature().getName();
        //获取目标方法形参列表
        Object[] args = joinPoint.getArgs();
        System.out.println("LogAspect 日志追踪 The method "+name+" begin with "+ Arrays.toString(args));
    }

}
4.2 后置通知

@After:后置通知,在方法之后执行

@Component
@Aspect
public class LogAspect {

    @After("execution(* ICount.*(..))")
    public void afterAdvice(JoinPoint joinPoint){
        System.out.println("LogAspect 日志追踪 The method "+joinPoint.getSignature().getName()+" end");
    }

}
4.3 返回通知

@AfterReturning:返回通知,在方法返回结果之后执行

@Component
@Aspect
public class LogAspect {

    /**
     * 返回通知 @AfterReturning
     * 1.value属性:即pointcut属性,声明切点表达式
     * 2.returning属性:记录方法执行的结果,若目标方法没有返回值则为null
     *
     * @param joinPoint  连接点
     * @param result  形参名称,必须与注解中的returning的值一致
     */
    @AfterReturning(value = "execution(* ICount.*(..))" , returning = "result")
    public void returnAdvice(JoinPoint joinPoint , Object result){
        System.out.println("LogAspect 日志追踪 The method "+joinPoint.getSignature().getName()+" end with "+result);
    }

}
4.4 异常通知

@AfterThrowing:异常通知,在方法抛出异常之后

@Component
@Aspect
public class LogAspect {

    /**
     * 异常通知 @AfterThrowing
     * 1.value属性:即pointcut属性,声明切点表达式
     * 2.throwing属性:接收方法运行时抛出的异常信息
     *
     * @param joinPoint 连接点
     * @param ex 形参名称,必须与注解中的throwing的值一致
     */
    //@AfterThrowing(pointcut = "execution(* ICount.*(..))" , throwing = "ex")
    @AfterThrowing(pointcut = "p()" , throwing = "ex")
    public void throwAdvice(JoinPoint joinPoint , Throwable ex){
        System.out.println("LogAspect 日志追踪 The method "+joinPoint.getSignature().getName()+" 发生异常 "+ex.getMessage());
    }

}

public class MainTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);

        //ICount count = ac.getBean(ICount.class);
        CountImpl count = ac.getBean(CountImpl.class);
        //com.sun.proxy.$Proxy16   JDK动态代理
        //com.igeek.ch02.log.CountImpl$$EnhancerBySpringCGLIB$$3f6dec88  CGLIB动态代理
        System.out.println(count.getClass().getName());


        int divResult = count.div(10, 0);
        System.out.println("divResult = "+divResult);
    }

}
4.5 环绕通知

环绕通知@Around是所有通知类型中功能最为强大的(前面四种通知都能够实现),能够全面地控制连接点,甚至可以控制是否执行连接点。

注:环绕通知的方法需要返回目标方法执行之后的结果,即调用ProceedingJoinPoint的proceed()方法来调用原始方法的返回值,否则会出现空指正异常。

@Component
@Aspect
public class LogAspect {

    //环绕通知
    @Around(value = "execution(* ICount.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint pjp){
        String name = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        System.out.println("@Around 日志追踪 The method "+name+" begin with "+ Arrays.toString(args));
        Object result = null;
        try {
            //执行目标方法,若目标方法没有返回值则返回null
            result = pjp.proceed(args);
            System.out.println("@Around 日志追踪 The method "+name+" end with "+result);
        } catch (Throwable e) {
            System.out.println("@Around 日志追踪 The method "+name+" 发生异常 "+e.getMessage());
            throw new RuntimeException(e);
        }
        System.out.println("@Around 日志追踪 The method "+name+" end");
        return result;
    }
}

五、切点表达式

1.execution(切入点)

execution(访问权限 返回值 包名.类名.方法名(形参列表))。

例如:execution(public int com.igeek.ch02.log.ICount.add(int , int))

或者可以写 重入切点表达式

    /**
     * 重入切点表达式
     * 注解@Pointcut("execution(* ICount.*(..))")
     * 第一个*:代表任意访问权限和任意返回值类型;第二个*:代表任意方法;第三个..:代表任意的形参列表
     *
     * 公共的切入点如何使用?  @Before("p()")
     * 1.本类         方法名()
     * 2.同包下不同类   类名.方法名()
     * 3.不同包下的类   包名.类名.方法名()
     *
     * 声明重入切点表达式有没有要求?
     * 1.访问权限修饰符,代表了当前切入点的可见性
     * 2.不能有返回值void,不可以写形参列表,不可以有方法体
     */
    //@Pointcut("execution(* ICount.*(..))")
    @Pointcut("execution(* CountImpl.*(..))")
    public void p(){}

则其他通知切入点直接使用方法名称即可,如 @Before("p()")

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值