根据动态代理手写一个AOP

Posted Dream_it_possible!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了根据动态代理手写一个AOP相关的知识,希望对你有一定的参考价值。

目录

功能列表

一、定义切面接口

二、定义ProxyFactory 

JDKProxyFactory

CglibProxyFactory

三、ServiceLoader兼顾可插拔性

四、默认使用Cglib创建代理对象

五、代理核心实现 

六、ProxyUtil 

七、自定义切面类


        SpringAop是面向切面编程的实现手段,也是动态代理的表现,我们可以借助SpringAOP在应用里来实现一个切面,可管理应用的日志、安全和事务等功能,只需要用@Aspect 注解标记一个类为切面,用@Pointcut注解定义一组service为一个切点, 然后定义方法的通知类型即可完成一个Aop的定义。

        抛开SpringAOP, 使用JDK 动态代理和Cglib 动态代理如何实现对象的动态代理,对外只需要提供目标对象和切面类,实现对目标对象的环绕通知和异常处理。

功能列表

1. 统一切面的通知模式,模式包含前置通知、后置通知和异常处理,SpringAop环绕通知也就是前置通知和后置通知的结合。

2. 能定制切面,可以对切面进行二次开发,如一组功能类似的类使用同一个切面。

3. 默认采用Cglib 生成代理对象,因为JDK动态代理需要目标对象实现额外的接口。

4. 只要提供目标对象和自定义的切面类即可生成代理对象,实现的目标如下:

FixPhone fixPhone = ProxyUtil.Proxy(new FixPhone(), MyAspect.class);

一、定义切面接口

        切面接口只需要包含3个方法before、after和throwException, 主要包含3个参数分别是目标类对象的实例、目标方法对象和目标方法的参数。

package com.bing.sh.aop.aspect;

import java.lang.reflect.Method;

/**
 * @Desc:
 * @Author: bingbing
 * @Date: 2022/4/18 0018 23:12
 */
public interface Aspect 


    /**
     * 目标方法执行前
     * @param target
     * @param method
     * @param args
     * @return
     */
    boolean before(Object target, Method method, Object[] args);


    /**
     * 目标方法执行后
     * @param target
     * @param method
     * @param args
     * @param returnValue
     * @return
     */
    boolean after(Object target, Method method, Object[] args, Object returnValue);


    /**
     * 目标方法调用异常
     * @param target
     * @param method
     * @param args
     * @param throwable
     * @return
     */
    boolean throwException(Object target, Method method, Object[] args, Throwable throwable);


        下面使用工厂模式去生产2种代理实现,分别是JDKProxyFactory和CglibProxyFactory。

二、定义ProxyFactory 

        定义抽象类ProxyFactory, 由JDKProxyFactory和CglibProxyFactory实现。

public abstract class ProxyFactory implements Serializable 

    private static final long serialVersionUID = 122319479213L;




    /**
     * 执行代理
     */
    public abstract <T> T proxy(T target, Aspect aspect);

JDKProxyFactory

        我们可以直接使用java.lang.reflect包下的Proxy对象调用newProxyInstance创建代理对象,参数Class<?>[] interfaces为目标对象实现的接口列表,InvocationHandler 接口需要我们自己去实现。

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) 
        Objects.requireNonNull(h);

        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();

        /*
         * Look up or generate the designated proxy class and its constructor.
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

        return newProxyInstance(caller, cons, h);
    
package com.bing.sh.aop.proxy;

import com.bing.sh.aop.aspect.Aspect;
import com.bing.sh.aop.interceptor.JDKInterceptor;

import java.lang.reflect.Proxy;

/**
 * @Desc:
 * @Author: bingbing
 * @Date: 2022/4/18 0018 23:14
 */
public class JDKProxyFactory extends ProxyFactory 


    @Override
    public <T> T proxy(T target, Aspect aspect) 
        JDKInterceptor interceptor = new JDKInterceptor(target, aspect);
        return  (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), interceptor);
        
    

CglibProxyFactory

        我们在使用Cglilb生成代理对象时,需要添加cglib的dependency,借助Enhancer创建代理对象 。

  <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
</dependency>
package com.bing.sh.aop.proxy;


import com.bing.sh.aop.aspect.Aspect;
import com.bing.sh.aop.interceptor.CglibInterceptor;
import net.sf.cglib.proxy.Enhancer;

/**
 * @Desc:
 * @Author: bingbing
 * @Date: 2022/4/18 0018 23:14
 */
public class CglibProxyFactory extends ProxyFactory 


    @Override
    public <T> T proxy(T target, Aspect aspect) 
        final Enhancer enhancer = new Enhancer();
        // super class 为目标对象的class。
        enhancer.setSuperclass(target.getClass());
        //设置callback, 就是MethodInterceptor的接口的实现类
        enhancer.setCallback(new CglibInterceptor(target, aspect));
        return (T) enhancer.create();
    

三、ServiceLoader兼顾可插拔性

        为了提高应用的可插拔性,JDK提供了一个SPI给我们开发者,我们可以使用JDK里的ServerLoader类去加载META-INF/services目录下文件名定义的类实现。

        这也是JDK对开发者提供的一种SPI机制,当我们对一个接口或抽象类有多个实现时,我们在选择实现类的时候,只需要从多个实现中选择一个即可,如果没有该配置,我们也可以使用一个默认的实现,配置的方式全限定名的方式,如下:

         一般实现只配置一个就行,比如Spring框架里的spring-web模块,实现了servlet提供的初始化接口javax.servlet.ServletContainerInitializer,实现类为:org.springframework.web.SpringServletContainerInitializer。

        我在项目里的com.bing.sh.aop.proxy.ProxyFactory 抽象类有2个实现,分别是CglibProxyFactory和JDKProxyFactory, 那么我们可以直接将CglibProxyFactory和JDKProxyFactory的全限定名配置到该文件里。

        ServiceLoader类提供了一个静态方法load 去加载META-INF/services目录下的clazz和对应的实现:

Params:
service – The interface or abstract class representing the service
loader – The class loader to be used to load provider-configuration files and provider classes, or null if the system class loader (or, failing that, the bootstrap class loader) is to be used
Type parameters:
<S> – the class of the service type
Returns:
A new service loader
Throws:
ServiceConfigurationError – if the service type is not accessible to the caller or the caller is in an explicit module and its module descriptor does not declare that it uses service
API Note:
If the class path of the class loader includes remote network URLs then those URLs may be dereferenced in the process of searching for provider-configuration files.
This activity is normal, although it may cause puzzling entries to be created in web-server logs. If a web server is not configured correctly, however, then this activity may cause the provider-loading algorithm to fail spuriously.
A web server should return an HTTP 404 (Not Found) response when a requested resource does not exist. Sometimes, however, web servers are erroneously configured to return an HTTP 200 (OK) response along with a helpful html error page in such cases. This will cause a ServiceConfigurationError to be thrown when this class attempts to parse the HTML page as a provider-configuration file. The best solution to this problem is to fix the misconfigured web server to return the correct response code (HTTP 404) along with the HTML error page.
@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
  return new ServiceLoader<>(Reflection.getCallerClass(), service, loader);

        我们只需要传入class,使用Iterator去遍历就能拿到service所有的实现实例。

package com.bing.sh.utils;

import com.bing.sh.aop.proxy.ProxyFactory;

import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.function.Supplier;

/**
 * @Desc:
 * @Author: bingbing
 * @Date: 2022/4/19 0019 14:26
 */
public class ServiceLoaderUtil 


    public static <T> T loadService(Class<T> clazz, Supplier<? extends T> supplier) 
        final Iterator<T> iterator = load(clazz).iterator();
        while (iterator.hasNext()) 
            try 
                return iterator.next();
             catch (ServiceConfigurationError e) 
                // ignore
            
        
        // 如果没有那么默认注入一个
        return supplier.get();
    

    public static <T> ServiceLoader<T> load(Class<T> clazz) 
        return load(clazz, null);
    


    public static <T> ServiceLoader<T> load(Class<T> clazz, ClassLoader classLoader) 
        return ServiceLoader.load(clazz, ObjectUtil.defaultIfNull(classLoader, ClassLoaderUtil::getClassLoader));
    

四、默认使用Cglib创建代理对象

        Cglib动态代理不需要目标对象实现接口即可创建代理对象,相比于JDK动态代理稍微更简洁些。

        如果没有配置services,那么使用CglibProxcFactory:

    private static ProxyFactory createFactory() 
        return ServiceLoaderUtil.loadService(ProxyFactory.class, () -> 
            try 
                return ClassUtil.loadClass(CglibProxyFactory.class);
             catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) 
                e.printStackTrace();
            
            return null;
        );
    

五、代理核心实现 

        我们只需要在Interceptor类里实现代理方法的befor、after、throwException的调用即可。

package com.bing.sh.aop.interceptor;

import com.bing.sh.aop.aspect.Aspect;
import com.bing.sh.utils.ClassUtil;
import com.bing.sh.utils.ReflectUtils;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Desc:  代理的对象不需要实现额外的接口即可生成一个代理对象,也是默认的代理方式
 * @Author: bingbing
 * @Date: 2022/4/18 0018 23:32
 */
public class CglibInterceptor implements MethodInterceptor, Serializable 
    private static final long serialVersionUID = 1L;

    private final Object target;

    private final Aspect aspect;

    public CglibInterceptor(Object target, Aspect aspect) 
        this.target = target;
        this.aspect = aspect;
    

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable 
        final Object target = this.target;
        Object result = null;

        if (aspect.before(target, method, args)) 
            try 
                ReflectUtils.setAccessible(method);
                result = method.invoke(ClassUtil.isStatic(method) ? null : target, args);
             catch (InvocationTargetException e) 
                // 捕获业务代码导致的异常
                if (aspect.throwException(target, method, args, e.getTargetException())) 
                    throw e;
                
            
        

        if (aspect.after(target, method, args, result)) 
            return result;
        

        return null;
    

六、ProxyUtil 

        使用Class<? extends Aspect>表示参数的上限类型为Aspect, 可传入Aspect接口的子类。

package com.bing.sh.aop.proxy;

import com.bing.sh.aop.aspect.Aspect;
import com.bing.sh.aop.interceptor.JDKInterceptor;

import java.lang.reflect.Proxy;

/**
 * @Desc:
 * @Author: bingbing
 * @Date: 2022/4/19 0019 19:48
 */
public class ProxyUtil 

    public static <T> T Proxy(T target, Class<? extends Aspect> aspect) 
        return ProxyFactory.createProxy(target, aspect);
    

    public static <T> T newProxyInstance(ClassLoader classLoader, JDKInterceptor interceptor, Class<?>[] interfaces) 
        return (T) Proxy.newProxyInstance(classLoader, interfaces, interceptor);
    

七、自定义切面类

        实现Aspect接口,所有方法均返回true。

package com.bing.sh.aop.aspect;

import java.lang.reflect.Method;

/**
 * @Desc: 继承此类并重新该方法就能创建某个类的代理对象
 * @Author: bingbing
 * @Date: 2022/4/19 0019 10:59
 */
public class SimpleAspect implements Aspect 
    @Override
    public boolean before(Object target, Method method, Object[] args) 
        return true;
    

    @Override
    public boolean after(Object target, Method method, Object[] args, Object returnValue) 
        return true;
    

    @Override
    public boolean throwException(Object target, Method method, Object[] args, Throwable throwable) 
        return true;
    

        我们只需要继承SimpleAspect类就能创建一个切面类:

public   class MyAspect extends SimpleAspect 


        @Override
        public boolean before(Object target, Method method, Object[] args) 
            System.out.println("先开机...");
            return true;
        

        @Override
        public boolean after(Object target, Method method, Object[] args, Object returnValue) 
            System.out.println("挂掉...");
            return true;
        
    

 测试:

package com.bing.sh.aop;

import com.bing.sh.aop.aspect.SimpleAspect;
import com.bing.sh.aop.proxy.ProxyFactory;
import com.bing.sh.aop.proxy.ProxyUtil;
import org.junit.Before;
import org.junit.Test;

import java.lang.reflect.Method;

/**
 * @Desc:
 * @Author: bingbing
 * @Date: 2022/4/19 0019 9:41
 */
public class AopTests 

    // 如果是内部类那么必须为静态内部类//java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
    static class FixPhone 

        public void call() 
            System.out.println("fixphone打电话...");
        

        public void sendMessage() 
            System.out.println("发送短信...");
        
    


    static class MobilePhone implements Phone 

        public MobilePhone() 
            //java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
        

        @Override
        public void call() 
            System.out.println("mobile打电话...");
        

        @Override
        public void sendMessage() 
            System.out.println("mobile发送短信...");
        
    


    interface Phone 
        void call();

        void sendMessage();
    

   public class MyAspect extends SimpleAspect 


        @Override
        public boolean before(Object target, Method method, Object[] args) 
            System.out.println("先开机...");
            return true;
        

        @Override
        public boolean after(Object target, Method method, Object[] args, Object returnValue) 
            System.out.println("挂掉...");
            return true;
        
    


    public class AnimalAspect extends SimpleAspect 


        @Override
        public boolean before(Object target, Method method, Object[] args) 
            System.out.println("先起床...");
            return true;
        

        @Override
        public boolean after(Object target, Method method, Object[] args, Object returnValue) 
            System.out.println("后睡觉...");
            return true;
        
    


    @Test
    public void testAopByAutoCgLib() 
        FixPhone fixPhone = ProxyUtil.Proxy(new FixPhone(), MyAspect.class);
        fixPhone.call();

        MobilePhone mobilePhone = ProxyUtil.Proxy(new MobilePhone(), MyAspect.class);
        mobilePhone.call();

        Dog dog = ProxyUtil.Proxy(new Dog(), AnimalAspect.class);
        dog.eat();
    

Hutool参考文档

以上是关于根据动态代理手写一个AOP的主要内容,如果未能解决你的问题,请参考以下文章

了解spring的AOP实现的必要基础

Spring容器AOP的理解

Spring aop 基于JDK动态代理和CGLIB代理的原理以及为什么JDK代理需要基于接口

Spring aop 基于JDK动态代理和CGLIB代理的原理以及为什么JDK代理需要基于接口

Spring的AOP

Spring学习记录