OSGI中通过 动态代理+自定义注解 实现方法增强(模拟AOP功能)

发表于 2020-07-07  25 次阅读


需求背景

接到新的需求,需要记录对南向设备的所有的操作日志。

大家都知道,在spring中通过AOP很容易就可以实现这样的日志记录功能;但是公司使用的是OSGI框架,没有提供类似spring AOP的架构去实现这样的方法增强的需求。

通过分析AOP原理,通过动态代理+自定义注解的方式实现了该功能。

难点分析

  1. 如果标记出需要增强的方法(需要记录日志的方法);
  2. 如何实现方法增强,并且不存在重复代码;
  3. 如果使得从OSGI容器中获取的服务实现类是代理类,而不是原始的实现类;

难点一:通过自定义注解标记需要增强的方法,同时可以自定义参数;

新增自定义的注解 @Log

package 设计模式.动态代理;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
    String operation() default "insert";
}

在测试代码上增加注解,标记该方法需要增强

package 设计模式.动态代理;

public class TestImpl implements ITest {

    @Log(operation = "测试代码")
    @Override
    public String test() {
        return "hello world!";
    }
}

难点二:通过动态代理的方式可以轻松实现;

这里选用JDK内置的动态代理实现,因为一般我们写OSGI的服务都是基于接口的;

定义接口ITest

package 设计模式.动态代理;

public interface ITest {
    String test();
}

编写代理实现类 LogProxy

package 设计模式.动态代理;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class LogProxy implements InvocationHandler {
    private Object o;

    private LogProxy(Object o) {
        this.o = o;
    }

    // 构建代理类
    public static <T> T createProxy(T obj) {
        return ( T ) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                new LogProxy(obj));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //从实际的类对象中查找到所调用的具体方法,用来检测实际类的方法上的注解
        //否则需要把注解加载接口的方法上
        Method[] methods = o.getClass().getMethods();
        List<Class<?>> types = new ArrayList<Class<?>>();
        Method mm = null;
        //通过反射查找方法
        if (args == null || args.length == 0) {
            mm = o.getClass().getMethod(method.getName(), null);
        } else {
            for (Object oo : args) {
                types.add(oo.getClass());
            }
            mm = o.getClass().getMethod(method.getName(), ( Class<?>[] ) types.toArray());
        }
        System.out.println("代理启动");
        //检查注解类型
        Log anno = mm.getAnnotation(Log.class);
        if (Objects.nonNull(anno)) {
            // 写方法增强代码
            System.out.println("探测到了LogTag注解,并且发现注解的level值为:" + anno.operation());
        }

        //调用具体的方法,执行业务逻辑
        Object result = method.invoke(o, args);
        System.out.println("代理结束");
        return result;
    }
}

编写测试类,测试 动态代理+自定义注解 实现效果

package 设计模式.动态代理;

public class testMain {
    public static void main(String[] args) {
        ITest proxy = LogProxy.createProxy(new TestImpl());
        proxy.test();
    }
}

难点三:要使得从OSGI容器中获取的服务实现类是代理类,那发布服务的实现类时就需要发布代理类

本例通过使用maven插件实现了注解注入和发布OSGI服务,如果你没有使用插件,需要手动在蓝图中书写对应代码。

编写工具类,发布代理类到OSGI容器中

package org.dylan.demo.api.utils;

import org.dylan.demo.api.proxy.LogProxy;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;

import java.util.Dictionary;
import java.util.Hashtable;

public class ProxyRegister {

    public static <T> void registerProxyService(T service) {
        BundleContext bundleContext = FrameworkUtil.getBundle(ProxyRegister.class).getBundleContext();
        Dictionary<String, Object> properties = new Hashtable<>(1);
        properties.put("type", "proxy");

        // 默认注册 被代理类实现的第一个接口
        bundleContext.registerService(service.getClass().getInterfaces()[0].getName(),
                LogProxy.createProxy(service), properties);

    }
}

通过代码,使用统一的provider去发布代理类

package org.dylan.demo.impl.init;

import org.dylan.demo.api.utils.ProxyRegister;
import org.dylan.demo.impl.TestImpl;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class DemoProvider {

    @Inject
    public DemoProvider() {
    }

    @PostConstruct
    public void init() {
        ProxyRegister.registerProxyService(new TestImpl());
    }

}

引入OSGI服务时,通过过滤器获取代理实现类

package org.dylan.demo.rest;

import org.apache.aries.blueprint.annotation.service.Reference;
import org.dylan.demo.api.ITest;

import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class RestProvider {
    public static ITest test;

    @Inject
    public RestProvider(
            @Reference(filter = "(type=proxy)") ITest test) {
        RestProvider.test = test;
    }
}

待解决问题:如何实现自动扫描自定义注解并且自动注册发布代理类到OSGI容器。

public static <T> void AutoRegisterProxyService(String packageName) {
        BundleContext bundleContext = FrameworkUtil.getBundle(ProxyRegister.class).getBundleContext();
        try {
            // 通过工具类,获取指定目录下的class集合
            Set<Class> classSet = ClassScanUtil.getClass4Annotation(packageName);
            // 遍历 获取带自定义注解的方法
            classSet.forEach(c -> {
                Method[] methods = c.getMethods();

                for (Method method : methods) {
                    if (Objects.nonNull(method.getAnnotation(Log.class))) {
                        bundleContext.registerService(c.getInterfaces()[0], LogProxy.createProxy(// TODO 难点:如何获取到被代理类的实例));
                    }
                }
            });
        } catch (IOException | ClassNotFoundException e) {
            e.getStackTrace();
        }
    }

本站文章基于国际协议BY-NA-SA 4.0协议共享;
如未特殊说明,本站文章皆为原创文章,请规范转载。

0

从业时长3年半的佛系码农,并不会唱跳、rap和篮球