美文网首页
一步步演进AOP的原理

一步步演进AOP的原理

作者: 乱七八糟谈技术 | 来源:发表于2020-06-15 21:13 被阅读0次

    之前介绍过一篇关于AOP的文章,主要是介绍其概念、基本用法以及常见的应用场景,但感觉没有讲解透彻,这篇文章将使用一个常见的例子一步一步的演进为什么会需要用到AOP以及AOP的实现原理。涉及到的技术包括如下:

    代理模式

    动态代理

    AOP

    Annotation的使用

    实际场景

    在微服务调用过程中,经常会出现异常,或者是服务本身有问题,或者是网络瞬间抖动导致调用失败,当遇到这种情况的时候肯定需要重试机制,微服务RPC调用框架都已经实现了这个功能,比如SpringCloud中的Feign只需要通过简单的配置就能实现重现机制,如下:

    service:
    ribbon:
    MaxAutoRetries: 1
    MaxAutoRetriesNextServer: 2
    ConnectTimeout: 5000
    ReadTimeout: 2000OkToRetryOnAllOperations: true

    这篇文章不是介绍如何实现重试机制,只是把它当作一个具体场景,来一步步演进来实现这个功能,最后达到理解AOP技术。

    最直接方式

    拿到这个需求,最简单粗暴的方法就很简单,在调用另一个服务的地方加上重试,重试指定次数仍然失败则抛出异常。这个代码就比较简单,其中outerService方法是模拟外部服务的异常,如下:

    上面的代码很简单,但如果项目中有很多的微服务,每个调用其他服务的代码都这么写,功能虽然没啥问题,但可读性会很差。因此,自然会想到如何改进这段代码,了解过代理模式的同学应该会想到使用代理来实现。

    应用代理模式

    首先回忆一下设计模式中的代理模式,代理模式的定义如下:

    代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。客户通过中介买房,都是完成买房这个任务,在编程中可以理解为买房是一个接口,中介和客户都需要实现这个接口,中介在买房的任务中需要做很多其他的事情,比如约卖家,准备合同等,当需要客户参与时,就需要喊上客户来完成,在编程中就理解为在中介买房的过程中,需要调用客户的买房接口。类图如下:

    回到我们的场景中,我们可以实现一个代理类,这个代理类来实现重试机制,当在真正调用服务的地方,再调用具体的服务来实现,是不是完全满足代理模式?重构代码如下:

    public interface HelloService {
        void sayHello();
    }

    @Slf4j
    @Service("HelloService")
    public class HelloServiceImpl implements HelloService {
        private static final int MAX_TRIES_COUNT = 5;

        private void outerService(){
            System.out.println("call outer service.....");
            throw new RuntimeException("Out service throw exception");
        }
        @Override
        public void sayHello() {
            outerService();
        }
    }

    @Service("HelloServiceProxy")
    public class HelloServiceProxyImpl implements HelloService {
        private static final int MAX_TRIES_COUNT = 5;
        private HelloService helloService;

        public HelloServiceProxyImpl(HelloService helloService){
            helloService = helloService;
        }
        @Override
        public void sayHello() {
            int count = 0;
            while (count < MAX_TRIES_COUNT) {
                try {
                    helloService.sayHello();
                    break;
                } catch (Exception e) {
                    count++;
                    if(count >= MAX_TRIES_COUNT) {
                        throw new RuntimeException(e);
                    }
                }
            }

        }
    }

    使用代理模式来实现这个功能,代码可读性和可维护性就大大提高,不需要在每个服务调用的地方都增加重试逻辑,交给代理来来完成即可。但是,如果项目中有很多的服务,需要为每个服务都实现一个代理类来实现原有接口,这个开发工作量也是相当大的而且很冗余,因此熟悉动态代理的同学应该想到可以使用JAVA的动态代理功能来完成,使用反射技术来生成动态代理类。

    基于接口的动态代理

    Java中动态代理的实现,关键就是这两个东西:Proxy、InvocationHandler。InvocationHandler接口Invoke参数如下:

     /**
         *
         * @param proxy, 代理类实例
         * @param method,调用的方法
         * @param args,调用方法参数
         * @return 方法的返回值*/
    public Object invoke(Object proxy, Method method, Object[] args); 

    另外一个创建动态代理类的方法如下:

    Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

    因此,使用基于接口的动态代理类也可以很容易实现此功能,代码如下:

    public class HelloServiceImpl implements HelloService {
        private void outerService(){
            System.out.println("call outer service.....");
            throw new RuntimeException("Out service throw exception");
        }
        @Override
        public void sayHello() {
            outerService();
        }
    }

    public class ServiceDynamicProxy implements InvocationHandler {
        private static final int MAX_TRIES_COUNT = 5;
        private final Object service;

        public ServiceDynamicProxy(Object service) {
            this.service = service;
        }


        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            int count = 0;
            while (count < MAX_TRIES_COUNT) {
                try {
                    return method.invoke(service, args);
                } catch (Exception e) {
                    count++;
                    if (count >= MAX_TRIES_COUNT) {
                        throw new RuntimeException(e);
                    }
                }
            }
            return null;
        }

        /**
         * 获取动态代理
         *
         * @param realService 代理对象     */
        public static Object getProxy(Object realService) {
            //我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
            InvocationHandler handler = new ServiceDynamicProxy(realService);
            return Proxy.newProxyInstance(handler.getClass().getClassLoader(),
                    realService.getClass().getInterfaces(), handler);
        }

    }

    @Test
    void testDynamicProxy(){
        HelloService helloService = new HelloServiceImpl();
        HelloService proxyService = (HelloService) 
        ServiceDynamicProxy.getProxy(helloService);
        proxyService.sayHello();
    }

    上面实现对于面向接口的动态代理是没问题,但是如果代理类没有接口,这种方式不能成功

    CGLIB方式生成动态子类

    使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。使用CGLIB主要依赖MethodInterceptor和Enhancer两个类,

    Object intercept(Object service, Method method, Object[] args, MethodProxy methodProxy)

    使用CGLIB方式实现此功能代码如下:

    public class HelloServiceImpl {
        private void outerService(){
            System.out.println("call outer service.....");
            throw new RuntimeException("Out service throw exception");
        }
        //@Override
        public void sayHello() {
            outerService();
        }
    }
    public class ServiceCglibProxy implements MethodInterceptor {
        private static final int MAX_TRIES_COUNT = 5;

        @Override
        public Object intercept(Object service, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            int count = 0;
            while (count < MAX_TRIES_COUNT) {
                try {
                    return methodProxy.invokeSuper(service, args);
                } catch (Exception e) {
                    count++;
                    if (count >= MAX_TRIES_COUNT) {
                        throw new RuntimeException(e);
                    }
                }
            }
            return null;
        }
        public Object getProxy(Class clazz) {
            Enhancer enhancer = new Enhancer();
            //目标对象类
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);
            //通过字节码技术创建目标对象类的子类实例作为代理
            return enhancer.create();
        }
    }
    @Test
    void testCglibProxy(){
        HelloServiceImpl helloServiceImpl = (HelloServiceImpl) new 
       ServiceCglibProxy().getProxy(HelloServiceImpl.class);
        helloServiceImpl.sayHello();

    }

    因此,通过上面两种代理方式,就成功的使用代理模式来完成这个功能的重构,经过重构后,不需要再每个调用的地方都写上一段重复的代码,也不需要为每个服务类创建一个代理类,代码精简了不少。其实这就是AOP的原理,AOP就是基于上面两种代理模式来实现。

    终极方案AOP

    上面两种代理模式其实就是AOP的实现原理,前一种兄弟模式,spring会使用JDK的java.lang.reflect.Proxy类,它允许Spring动态生成一个新类来实现必要的接口,织入通知,并且把对这些接口的任何调用都转发到目标类。后一种父子模式,spring使用CGLIB库生成目标类的一个子类,在创建这个子类的时候,spring织入通知,并且把对这个子类的调用委托到目标类。明白了AOP实现原理,使用AOP就比较简单,定义切入点和Aspect.代码如下:

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Retries {
        Class<? extends Throwable> value() default RuntimeException.class;

        int maxAttempts() default 5;

    }

    @Slf4j
    @Service("HelloService")
    public class HelloServiceImpl implements HelloService{
        private void outerService(){
            System.out.println("call outer service.....");
            throw new RuntimeException("Out service throw exception");
        }
        @Override
        @Retries(maxAttempts = 5, value = RuntimeException.class)
        public void sayHello() {
            outerService();
        }
    }

    @Aspect
    @Component
    public class RetriesAspect {
        @Pointcut("execution(public * com.example.demo.service.impl.*.say*(..)) &&" +
                "@annotation(com.example.demo.Retries)")
        public void tryPointcut() {
        }

        private Method getCurrentMethod(ProceedingJoinPoint point) {
            try {
                Signature sig = point.getSignature();
                MethodSignature methodSignature = (MethodSignature) sig;
                Object target = point.getTarget();
                return target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }

        @Around("tryPointcut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            Method method = getCurrentMethod(point);
            Retries retries = method.getAnnotation(Retries.class);
            int maxAttempts = retries.maxAttempts();
            if (maxAttempts <= 1) {
                return point.proceed();
            }
            int count = 0;
            final Class<? extends Throwable> exceptionClass = retries.value();
            while (count < maxAttempts) {
                try {
                    return point.proceed();
                } catch (Throwable e) {
                    count++;
                    if (count >= maxAttempts /*||                        !e.getClass().isAssignableFrom(exceptionClass)*/) {
                        throw new Throwable(e);
                    }
                }
            }
            return null;
        }
    }
    @SpringBootTest
    class DemoApplicationTests {
        @Autowired
        @Qualifier(value = "HelloService")
        private HelloService helloService;

       @Test
       void testAop(){
           helloService.sayHello();
       }
    }

    写在最后

    通过简单方式,代理方式,动态代理方式,CGlib代理方式到最后的AOP实现方式,能看到整个技术的演变过程,其实也是代码不断重构的过程。AOP的实现原理也是基于动态代理和CGlib两种方式来实现的,把这两种技术理解清楚了,AOP就比较容易理解。

    相关文章

      网友评论

          本文标题:一步步演进AOP的原理

          本文链接:https://www.haomeiwen.com/subject/uetkxktx.html