Java AOP与装饰器模式

AOP与装饰器模式简介

Aspect-Oriented Programming(AOP)面向切面编程,相对而言是Object-Oriented Programming(OOP)面向对象编程。假如现在有一个特殊的需求,我们需要在每个方法进入或退出时都打印日志。我们不想在所有的方法上都傻傻地写上日志,想着这种相同的逻辑是否能用一个段代码全部实现呢?这里我们就可以使用AOP了,我们关注的是进入方法和退出方法的那一个瞬间(那一个切面)。类似地,这种需要统一处理的场景或是有着很多相同逻辑的地方我们都可以使用AOP来解决,比如日志、缓存、鉴权。如果使用OOP解决上述问题,我们则会使用装饰器模式。

装饰器模式

装饰器模式(Decorator pattern)很简单,本质上就是动态地为⼀个对象增加功能,但是不改变其结构(用人话解释就是加上个“包装”),这里就不细说了,我们实现一个带日志和缓存的装饰器模式来简单说明。一般来说装饰器模式都是和接口来混用的。每包一层装饰器就实现一个额外的功能。带日志和缓存的装饰器模式有如下的逻辑:DataService是一个接口;DataServiceImpl实现了DataService接口(是一个基本实现);LogDecorator实现了DataService,内置成员变量DataServiceImpl,增加日志逻辑,在合适的地方调用DataServiceImpl的基本实现方法;CacheDecorator同理。

定义服务接口

public interface DataService {
    String getData();
}

服务的基础实现

public class DataServiceImpl implements DataService {
    @Override
    public String getData() {
        return UUID.randomUUID().toString();
    }
}

加上日志装饰器

public class LogDecorator implements DataService {
    private final DataServiceImpl dataService;

    public LogDecorator(DataServiceImpl dataService) {
        this.dataService = dataService;
    }

    @Override
    public String getData() {
        System.out.println("method invoke");
        final String data = dataService.getData();
        System.out.println("method finish");
        return data;
    }
}

加上缓存装饰器

public class CacheDecorator implements DataService {
    private final LogDecorator logDecorator;
    private final Map map;

    public CacheDecorator(LogDecorator logDecorator) {
        this.logDecorator = logDecorator;
        this.map = new HashMap<>();
    }

    @Override
    public String getData() {
        String value = map.get("getData");
        if (value == null) {
            value = logDecorator.getData();
            map.put("getData", value);
        }
        return value;
    }
}

装饰器有个很大的问题,对于每一个方法,我们都要手动写一遍日志,实在是太麻烦了,而且很啰嗦。

AOP的两种实现:动态代理

什么是动态代理呢?动态指的是运行时动态生成字节码,代理指的是拦截掉原先的方法,然后根据当前情况完成相应的功能。总的来说,动态代理就是动态生成字节码完成一些功能扩展。一般地,动态代理可以分为JDK动态代理和动态字节码增强。

JDK动态代理

使用Proxy.newProxyInstance进行JDK动态代理,注意第二的参数需要参数一个 interface 的 Class 数组。第三个参数需要实现InvocationHandler这个接口,处理代理实例上的方法调用并返回结果。

Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
public class Main {
    public static void main(String[] args) {
        final DataService service = new DataServiceImpl();

        DataService service1 = (DataService) Proxy.newProxyInstance(service.getClass().getClassLoader(),
                new Class[]{DataService.class},
                new LogProxy(service));

        System.out.println(service1.getData());
    }
}
public class LogProxy implements InvocationHandler {
    private final DataService delegate;

    public LogProxy(DataService delegate) {
        this.delegate = delegate;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("method invoke");
        final Object value = method.invoke(delegate);
        System.out.println("method finish");
        return value;
    }
}

JDK动态代理的优点是比较方便,不需要依赖任何第三方库。缺点也比较明显,功能比较受限,只适用于接口。

动态字节码增强

如果要代理的是个类不是接口怎么办?我们可以使用CGLIB或ByteBuddy字节码⽣成。他们的本质都是在内存中生成一个动态的增强的子类。他们非常的强大,但是缺点是需要引⽤额外的第三⽅类库,由于是通过继承方式增强的,所以他们不能增强final类/final/private⽅法。关于ByteBuddy的使用,我们在Java 注解这一篇有提到。这里我们使用CGLIB来做例子。

public class Main {
    public static void main(String[] args) {
        final DataServiceImpl service = new DataServiceImpl();

        final Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(DataServiceImpl.class);
        enhancer.setCallback(new LogInterceptor(service));

        final DataServiceImpl service1 = (DataServiceImpl) enhancer.create();
        System.out.println(service1.getData());
    }
}

这一部分逻辑和上边JDK动态代理中的LogProxy还是很像的。

public class LogInterceptor implements MethodInterceptor {
    private final DataServiceImpl delegate;

    public LogInterceptor(DataServiceImpl delegate) {
        this.delegate = delegate;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("method invoke");
        final Object value = method.invoke(delegate);
        System.out.println("method finish");
        return value;
    }
}

你可能感兴趣的:(Java AOP与装饰器模式)