一篇文章了解如何实现方法增强(AOP方案)

序言

开发过程中,可能会遇到这种情况,比如你想对某个已经实现好的方法进行扩展或者增强,因为这个方法无法达到你的预期,但是这个方法的实现你又需要用到,你要别人去改代码来达到你自己的需求,这个可能不太现实。这时候你就需要有几种方案去实现方法的扩展或增强。下面就通过一个例子了解如何实现方法增强

栗子:

自定义接口,

package com.hust.zhang.example;

public interface SuperMarket {

    int SUCCESS = 200;
    int ERROR = 100;

    /**
     * 采购商品
     *
     * @return 返回状态码
     */
    int purchasing();

    /**
     * 售卖商品
     *
     * @param id 商品Id
     * @return 返回状态码
     */
    int selling(Long id);
}

接口实现类,

public class SuperMarketImpl implements SuperMarket {
    /**
     * 商品成本列表
     */
    private static final HashMap costMap = new HashMap() {
        {
            put(1L, 100);
            put(2L, 200);
            put(3L, 300);
        }
    };

    @Override
    public int purchasing() {
        System.out.println(this.getClass().getSimpleName() + " commodity ");
        return SUCCESS;
    }

    @Override
    public int selling(Long id) {
        int price = costMap.get(id);
        if (Objects.isNull(price)) {
            System.out.println("the commodity id: " + id + " is not exist");
            return ERROR;
        }
        System.out.println(this.getClass().getSimpleName() + " commodity id: " + id + " price: " + price);
        return SUCCESS;
    }

}

代理(Proxy)

代理模式(Proxy Pattern)是通过代理对象访问目标对象,这样可以在目标对象基础上增强额外的功能,如添加权限,访问控制和审计等功能。

代理分为静态代理和动态代理,静态代理会生成代理类,而动态代理不同于静态代理,它不会生成过多的代理类对象,而是通过Java反射机制生成。

静态代理(Static Proxy)

静态代理对象,

public class MarketAgent implements SuperMarket {

    /**
     * 被代理对象
     */
    private SuperMarket target;

    /**
     * 代理对象 有参构造函数
     * @param target
     */
    public MarketAgent(SuperMarket target) {
        this.target = target;
    }

    @Override
    public int purchasing() {
        target.purchasing();
        return SUCCESS;
    }

    @Override
    public int selling(Long id) {
        target.selling(id);
        return SUCCESS;
    }
}

静态代理测试类及结果如下,可以看到静态代理每次使用都需要写代理类,被代理类实例化后通过代理类的构造方法传入,再通过代理类进行调用代理方法。

一篇文章了解如何实现方法增强(AOP方案)_第1张图片

动态代理(Dynamic Proxy)

JDK(Java Development Kit)动态代理

JDK动态代理通过Proxy类的newProxyInstance方法创建代理实例。

package com.hust.zhang.example;

import java.lang.reflect.Proxy;
import java.util.Objects;

public class JdkDynamicProxy {
    private static final String PURCHASE = "purchasing";
    private static final String SELL = "selling";

    public static void main(String[] args) {
        SuperMarket market = new SuperMarketImpl();

        SuperMarket proxyInstance = (SuperMarket) Proxy.newProxyInstance(market.getClass().getClassLoader(), market.getClass().getInterfaces(), ((object, method, param) -> {
            int result = 0;
            if (Objects.equals(method.getName(), PURCHASE)) {
                System.out.println("代理商完成采购");
                result = (int) method.invoke(market, param);
            } else if (Objects.equals(method.getName(), SELL)) {
                System.out.println("代理商完成售卖");
                result = (int) method.invoke(market, param);
            }
            return result;
        }));

        proxyInstance.selling(1L);
    }
}

上面的方式属于JDK动态代理,主要流程如下:

  1. 创建代理对象,通过反射获取相同的接口,校验权限。
  2. 重新生成一个类,实现被代理类所有接口。
  3. 动态生成Java代码,添加进新的业务逻辑。
  4. 编译新生成Java代码,重新加载到JVM中。

CGLIB(Code Generation Library)动态代理

Cglib动态代理使用Enhancer类对方法进行增强。Enhancer类是一个增强类,它会生成动态子类以启动方法拦截。通过Enhancer.create()方法创建实例。

public class CglibDynamicProxy {
    private static final String PURCHASE = "purchasing";
    private static final String SELL = "selling";

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SuperMarketImpl.class);

        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
            int result = 0;
            if (Objects.equals(method.getName(), PURCHASE)) {
                System.out.println("代理商完成采购");
                result = (int) methodProxy.invokeSuper(o, objects);
            } else if (Objects.equals(method.getName(), SELL)) {
                System.out.println("代理商完成售卖");
                result = (int) methodProxy.invokeSuper(o, objects);
            }
            return result;
        });

        SuperMarket market = (SuperMarket) enhancer.create();
        market.selling(1L);
    }
}

注意:

  1. 上面的代码里因为SuperMarketImpl类被增强,所以类名加上了EnhancerByCGLIB等信息。
  2. 如果一个类没有实现接口,那么它就不能使用JDK动态代理,但是可以使用CGLIB动态代理。

静态织入(Static Weaving)

本工程使用了Springboot框架,加上@Service注解注入组件,使用自定义注解对方法进行环绕增强,详细可参看AOP原理。

@Service
public class SuperMarketImpl implements SuperMarket {
    /**
     * 商品成本列表
     */
    private static final HashMap costMap = new HashMap() {
        {
            put(1L, 100);
            put(2L, 200);
            put(3L, 300);
        }
    };

    @Override
    public int purchasing() {
        System.out.println(this.getClass().getSimpleName() + " commodity ");
        return SUCCESS;
    }

    @Override
    @EnhanceMethod(field = "test static weaving")
    public int selling(Long id) {
        int price = costMap.get(id);
        if (Objects.isNull(price)) {
            System.out.println("the commodity id: " + id + " is not exist");
            return ERROR;
        }
        System.out.println(this.getClass().getSimpleName() + " commodity id: " + id + " price: " + price);
        return SUCCESS;
    }

}

这里对selling动作进行增强,使用无侵入的方式,新建一个注解EnhanceMethod,其中元注解解释如下:

@Target:描述注解的使用范围(即:被修饰的注解可以用在什么地方)
@Retention:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时)
@Documented:描述在使用javadoc工具为类生成帮助文档时是否要保留其注解信息。
@Inherited:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface EnhanceMethod {
    //注解传入的属性
    String field() default "Static Weaving";
}

增加切面类,AOP提供5种类型,这里使用环绕增强,对该方法增强只多打印两行日志(参数从切入点获取):

  • 前置通知/增强(Before Advise)
  • 后置通知(After Returning Advise)
  • 环绕通知(Interception Around Advise)
  • 异常抛出通知(Throws Advise)
  • 引介通知(Introduction Advise) 
@Component
@Aspect
public class EnhanceMethodAspect {

    private static final String ANNOTATION_PATH = "@annotation(com.hust.zhang.example.EnhanceMethod)";
    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut(ANNOTATION_PATH)
    public void testPointcut() {

    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    @Around(ANNOTATION_PATH)
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        //通过反射拿到目标方法的类名、方法名、参数、方法
        String clazz = pjp.getTarget().getClass().getSimpleName();
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();

        logger.info("Invoke {}.{} and parameter is {}", clazz, methodName, args);

        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        EnhanceMethod myAnnotation = method.getAnnotation(EnhanceMethod.class);

        logger.info("my definition annotation is {}", myAnnotation);

        return pjp.proceed();
    }
}

 启动应用,调用对外提供的接口,可以看到在方法上进行了增强,只是通过多增加了一个注解。

一篇文章了解如何实现方法增强(AOP方案)_第2张图片

总结

本文介绍了对方法增强的几种方式,静态代理、动态代理(JDK和CGLIB)以及静态织入,最后一种方案是通过AOP来实现的,是一种无侵入式的方案,通常对方法加缓存或加日志都采用这种方式。不过具体开发中开发者如何选型可以根据实际情况来决定。

你可能感兴趣的:(笔记,java)