明天的太阳

导航

Java动态代理、AOP和装饰器模式

面向切面编程AOP-Aspect Oriented Programing,主要用于处理核心业务逻辑外的一些东西,
比如日志和缓存。这个“切面”可以理解为在代码的某个地方切一刀,在其中加一些东西。

装饰器

以日志为例,如果没有使用AOP,那么可以使用装饰来实现类似的代码。
我们使用装饰器模式来实现一下在执行一个方法前后打印代码的程序来演示。

// 提供一个接口
interface CatService {
    public String eat();
}

public class CatServiceImpl implements CatService {
    // 提供一个实现
    @Override
    public String eat() {
        System.out.println("Eating");
        return "Eating";
    }
}

// 以Decorator为类名实现一个CatService
public class LogDecorator implements CatService {
    private final CatService catService;

    // 这里提供一个基\父类,赋值给其子类,即多态
    public LogDecorator(CatService service) {
        this.catService = service;
    }

    @Override
    public String eat() {
        System.out.println("before eating");
        String eat = catService.eat();
        System.out.println("after eating");
        return eat;
    }
}

// 在main方法中执行
public class Main {

    static CatService service = new CatServiceImpl();
    static CatServiceImpl serviceImpl = new CatServiceImpl();
    static CatService catService = new LogDecorator(service);

    public static void main(String[] args) {
        // decorator实现
        String eat = catService.eat();
    }
}

JDK Proxy实现AOP

public class LogProxy implements InvocationHandler {
    // 同样是基类
    private CatService catService;

    public LogProxy(CatService catService) {
        this.catService = catService;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy before");
        Object o = method.invoke(catService, args);
        System.out.println("proxy after");
        return o;
    }
}

使用Proxy.newProxyInstance创建基于接口的动态代理实例,方法有三个参数:

  1. 类加载器 (ClassLoader): 用于加载生成的代理类,我们这里要生成CatServiceImpl代理类。
  2. 接口数组 (Class<?>[]): 代理实例所实现的接口的数组, 注意只能接收接口interface,不能接受类class。
  3. InvocationHandler (InvocationHandler): 当调用代理方法时,这里是上面实现了InvocationHandlerLogProxy类,其中实现了invoke方法。
static CatService service = new CatServiceImpl();
static CatService catService = new LogDecorator(service);

public static void main(String[] args) {
    jdkAOP();
}

public static void jdkAOP() {
    Object o = Proxy.newProxyInstance(service.getClass().getClassLoader(),
            new Class[]{CatService.class},
            new LogProxy(service));
    CatService catServiceProxy = (CatService) o;
    String eat = catServiceProxy.eat();
    System.out.println("eat:" + eat);
}

JDK动态代理只适⽤于接⼝,但是不需要依赖任何第三⽅库

Cglib实现AOP日志

cglib是一个用来生成字节码的库,不过已经好多年没更新了,文档中说在新的JDK中可能不能工作了,尤其是JDK 17以后。

public class LoginInterceptor implements MethodInterceptor {
    CatServiceImpl catService;

    public LoginInterceptor(CatServiceImpl catService) {
        this.catService = catService;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib before");
        Object o1 = methodProxy.invoke(catService, objects);
        System.out.println("cglib after");
        return o1;
    }
}

public static void main(String[] args) {
    jdkAOP();
}
public static void cglibAOP() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(CatServiceImpl.class);
    enhancer.setCallback(new LoginInterceptor(serviceImpl)) ;

    CatServiceImpl en = (CatServiceImpl) enhancer.create();
    String eat = en.eat();
    System.out.println("eat:" + eat);
}

不能增强final类/final/private⽅法

ByteBuddy 实现AOP日志

Cglib提到可以迁移到ByteBuddy,那就试一试吧。

先引入

<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.14.11</version>
</dependency>
public class CatServiceAspect {
    public static CatService apply() {
        CatService catService = null;
        try {
            catService = new ByteBuddy()
                    .subclass(CatServiceImpl.class)                               // 继承CatServiceImpl类
                    .method(ElementMatchers.named("eat"))                         // ElementMatchers.any()可以匹配所有方法
                    .intercept(MethodDelegation.to(CatServiceInterceptor.class))  // 拦截eat()方法并调用CatServiceInterceptor的eat()方法
                    .make()
                    .load(CatServiceAspect.class.getClassLoader())
                    .getLoaded()
                    .getDeclaredConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        return catService;
    }
}

创建一个拦截器,处理eat方法。

public class CatServiceInterceptor  {
    // 注意这里是static方法,否则会提示None of [] allows for delegation。没有方法能被代理
    // ByteBuddy的SuperCall注解表示zuper是实际的可执行的CatServiceImpl中的eat方法
    public static String eat(@SuperCall Callable<?> zuper) throws Exception {
        System.out.println("before bytebuddy eating");
        String call = (String) zuper.call();
        System.out.println("after bytebuddy eating");
        return call;
    }
}

在main方法中调用

public class Main {

    static CatService service = new CatServiceImpl();
    static CatServiceImpl serviceImpl = new CatServiceImpl();
    static CatService catService = new LogDecorator(service);

    public static void main(String[] args) {
        logBytebuddy();
    }

    public static void logBytebuddy() {
        CatService apply = CatServiceAspect.apply();// 应用面向切面编程的配置
        String eat = apply.eat();
        System.out.println(eat);
    }
}

不能增强final类/final/private⽅法

如果引入maven依赖的时候提示the trustAnchors parameter must be non-empty可能是jdk中的cacerts过期了,不在可信任的根证书颁发机构(CA)中了。可以尝试用新一点的JDK。

posted on 2024-01-16 22:30  东方来客  阅读(62)  评论(0编辑  收藏  举报