实现简单springAOP

实现简单springAOP

项目地址

在IOC的基础上进行扩展

原理

  • AOP面向切面编程,是OOP面向对象编程的一种补充

  • AOP通过继承、封装、多态等等概念构建一个对象的层级结构。构建的是一个纵向的关系,面对横向的问题,实现起来比较复杂,比如日志文件的输出。使用面向对象的思想,每个类都需要添加日志打印的相关代码,但是使用AOP就可以很简单解决这个问题

  • AOP影响了很多类的公共行为(如日志打印)可以封装为一个模块,定义为一个切面,切面中包含切入点、通知、连接点等概念

  • 切入点:需要做切面处理的位置,可以通过@PointCut中的execution指定某个包、某个类或者某个方法。同时也可以自定义注解标注

  • 通知:包括5种:分别是前置、后置、返回、异常和环绕通知,分别定义增强代码执行的时机

  • 连接点:是可以用来作为切入点的位置,是程序执行的某个位置,可以为程序执行前,也可以是执行后或者抛出异常等一些时间点

  • AOP的作用是降低耦合度,提高重用性和开发效率

官方框架使用

  • 需要开启aop注解
<aop:aspectj-autoproxy/>
  • 强制使用cglib
<aop:aspectj-autoproxy proxy-target-class="true"/>
  • 使用jdk代理必须要实现同一个接口,代理出来的类和接口的实现类是兄弟关系,必须使用接口接收
@Autowired
private OrderService orderService;
  • cglib代理的思想是生成一个子类,使用当前的类就能接受(未实现接口)
@Autowired
private OrderService orderService;

实现

思路:在依赖注入DI之前将容器中的对象替换为代理增强之后的对象

切面类

package com.hodor.aop;

import org.springframework.annotation.Around;
import org.springframework.annotation.Aspect;
import org.springframework.aop.ProceedingJoinPoint;

/**
 * @author :hodor007
 * @date :Created in 2021/2/16
 * @description :切面类
 * @version: 1.0
 */
@Aspect
public class LogAop {
    @Around(execution = "com.hodor.service.impl.OrderServiceImpl.addOrder")
    public Object around(ProceedingJoinPoint joinPoint) {
        Object result = null;
        //调用目标对象的目标方法
        try {
            System.out.println("==>前置通知......");
            result = joinPoint.proceed();
            System.out.println("==>返回通知......");
        } catch (Throwable throwable) {
            System.out.println("==>异常通知......" + throwable.getMessage());
        } finally {
            System.out.println("==>后置通知......");
        }
        return result;
    }
}

自定义注解

  • @Around注解,简化版,没有模仿execution切入表达式
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)    //作用在类上
@Documented  //会被javadoc处理
public @interface Around {
    String execution() default "";
}
  • @Aspect注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)    //作用在类上
@Documented  //会被javadoc处理
public @interface Aspect {

}

反射注解

  • 反射@Aspect和@Around注解,获取增强的目标方法com.hodor.service.impl.OrderServiceImpl.addOrder,@Autowired注入的对象是被代理后的对象

  • 使用线程安全的Set存储AOP切面类(src/main/java/org/springframework/container/ClassPathXmlApplicationContext.java)

//存放AOP切面类的集合
private Set<Class<?>> aopClassSet = new CopyOnWriteArraySet<>();
  • 获取类中所有的方法,有注解@Around的方法(只实现了@Around注解)

自定义ProceedingJoinPoint

用于执行目标类的,目标方法(如注入的orderService的addOrder方法)

public class ProceedingJoinPoint {
    //目标方法
    private Method method;

    //目标对象
    private Object target;

    //目标方法参数
    private Object[] args;

    public ProceedingJoinPoint(Method method, Object target, Object[] args) {
        this.method = method;
        this.target = target;
        this.args = args;
    }

    public Object proceed() {
        try {
            //调用目标方法
            return method.invoke(target, args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } finally {

        }
        return null;
    }
    //省略getter和setter......
}

自定义JdkProxy动态代理类

用于执行AOP切面类中的增强方法,同时增强类中的增强方法通过参数ProceedingJoinPoint调用了目标类的方法,实现了对目标类方法的增强(通过这个方式给目标类包裹上了增强的代码)

/**
 * @author :hodor007
 * @date :Created in 2021/2/16
 * @description :JdkProxy动态代理
 * @version: 1.0
 */
public class JdkProxy<T> {
    /**
     * 目标类class
     */
    private Class<?> targetClass;

    /**
     * aop切面类class
     */
    private Class<?> aopClass;

    /**
     * 目标对象
     */
    private Object targetObject;

    /**
     * 要被代理的方法的名字
     */
    String methodName;

    /**
     * AOP切面类的方法(带@Around注解的)
     */
    Method aopMethod;

    public JdkProxy(Class<?> targetClass, Class<?> aopClass, Object targetObject, String methodName, Method aopMethod) {
        this.targetClass = targetClass;
        this.aopClass = aopClass;
        this.targetObject = targetObject;
        this.methodName = methodName;
        this.aopMethod = aopMethod;
    }

    /**
     * 核心的动态代理类
     * 如果是目标的方法就封装jointpoint作为参数,反射aop切面类的class调用aop切面类中的增强方法
     * 同时传入的jointPoint参数的process方法反射了目标类的class,执行了目标类的目标方法(套娃),在这个动作的前后可以执行增强(查看LogAop切面类)
     * 获取jdk代理对象
     * @return
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(targetClass.getClassLoader(), targetClass.getInterfaces(), new InvocationHandler() {
            //
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //调用目标对象的目标方法
                if(methodName.equals(method.getName())) {
                    ProceedingJoinPoint joinPoint = new ProceedingJoinPoint(method, targetObject, args);
                    return aopMethod.invoke(aopClass, joinPoint);
                } else {
                    return method.invoke(targetObject, args);
                }
            }
        });
    }
    //省略getter和setter......
}

将容器中的对象替换为代理对象

这么做导致IOC中后一步@Autowired依赖注入失败,代理的对象没法依赖注入

java.lang.IllegalArgumentException: Can not set com.hodor.dao.OrderDao field com.hodor.service.impl.OrderServiceImpl.orderDao to com.sun.proxy.$Proxy9

原因是经过了动态代理,目标对象中没有名为***Dao的属性了,没法注入

Snipaste_2021-02-17_10-29-29

调换AOP和DI的顺序,直接从容器中取出service执行是有切面增强的(如下图),但是由于先执行依赖注入,所以OrderController中的属性OrderService还是未代理的,调用OrderController中的方法还是没有增强的

image-20210217163015769

解决方案:依旧是先AOP增强再执行依赖注入,只对动态代理的类提前执行依赖注入(将依赖注入的代码抽取出来作为单独的方法,同一个类中有多个AOP增强的方法,只将这个类提前注入一次)

image-20210217171953747

在AOP之后的依赖注入DI步骤中要跳过需要动态代理的类(已经提前注入过了)

/**
 * 5. 实现依赖注入
 */
private void doDi() {
    Set<Class<?>> classes = iocContainer.keySet();
    if(classes != null) {
        //通过class遍历所有的对象
        for (Class<?> aClass : classes) {
            if(!proxyClassSet.contains(aClass)) {
                doDiByClass(aClass);
            }
        }
    }
}

执行结果

image-20210217172043297

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值