基于动态代理实现的事件驱动式编程框架

最近在重构公司代码,考虑使用事件式驱动编程将增值代码逻辑都提取出来,但是看了一些其他人写的代码,总感觉不符合我要求。正好最近在研究spring和mybatis源码,于是参考mybatis源码自己实现一套吧。

目标

既然是有实现目标的,那先从制定期望实现的效果开始吧

(1)既然是事件机制,那总有个事件触发器吧。触发器就应该简单明了,一个接口就行了,方法参数可以随便定

public interface OrderFinishTrigger {
    void process(String orderId, String empId);
}

(2)既然有触发器,那肯定有监听器,监听器便是业务代码实现的地方。1.通过implements触发器接口来和触发器进行绑定,这样简单明了,2.一个触发器当然可以有多个监听器,3.因为是基于spring的,且监听器是实现业务用的,所以正常的依赖注入功能应该还是得够支持的。

//订单完成时给服务人员发工资
@EventListener
public class TestOrderFinishDealGiveMoneyListener implements OrderFinishTrigger {
    @Override
    public void process(String orderId, String empId) {
        System.out.println("订单完成发工钱啦:orderId:"+orderId+";empId:"+empId);
    }
}
//订单完成时,发送短信
@EventListener
public class TestOrderFinishDealSendSMSListener implements OrderFinishTrigger {
    @Autowired()
    private TestService testService;

    @Override
    public void process(String orderId, String empId) {
        System.out.println("订单完成发短信啦"+orderId+";empId:"+empId);
        testService.showMe(orderId);
    }
}
//其他的普通业务类
@Service
public class TestService {
    public void showMe(String param){
        System.out.println("chuancan:"+param);
    }
}

(3)既然是事件机制,那总有个地方可以主动触发事件吧,就取名为事件提供者吧。为了简单起见,事件提供者只需要是一个接口,类似于mybatis的mapper一样,只需要指定触发器类和触发器方法即可。因此,事件提供者便需要采用动态代理来实现

@EventProvider
public interface TestEventProvider {
    //调用这个方法可以触发订单完成事件
    @EventTrigger(triggerClass = OrderFinishTrigger.class,method = "process")
    void orderFinished(String orderId, String empId);
}

先把结果放出来吧

测试类

只需要简单调用TestEventProvider接口的方法,就可以触发事件

@RunWith(SpringRunner.class)
@SpringBootTest
public class EventApplicationTests {
    @Autowired
    TestEventProvider testEventProvider;

    @Test
    public void eventTest() {
        /*
        balabala各种业务代码
        */
        //触发一下订单完成事件
        testEventProvider.orderFinished("aaaa","bbbb");
    }

}

执行结果:

开始实现吧

几个主要注解

(1)EventProvider,只是用来标明是事件提供者,所以不需要添加其他东西

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE})
public @interface EventProvider {
}

(2)EventTrigger的话则需要指明触发器类和方法

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.METHOD})
public @interface EventTrigger {
    Class<?> triggerClass();

    String method();
}

(3)监听器则只需要指明监听方式即可

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE})
public @interface EventListener {
    ListenerTriggeriOpportunityEnum type() default ListenerTriggeriOpportunityEnum.SYNCHRONIZE; // 默认同步触发
}

枚举

public enum ListenerTriggeriOpportunityEnum {
    SYNCHRONIZE, //同步
    ASYNCHRONOUS; //异步
}

事件类的扫描和注入spring

因为我们加的这几个注解都没有声明@Component,所以spring自然不会为我们初始化这些类。因此事件提供者和监听者的扫描以及注入spring的工作就要我们亲自动手了。

首先定义一个扫描工具类,继承自ClassPathScanningCandidateComponentProvider,这个类是spring内部扫描用的工具类,如果不了解的话自行百度。

public class EventBeanScanner extends ClassPathScanningCandidateComponentProvider {

    public EventBeanScanner(){
        super(false);
        addIncludeFilter(new AnnotationTypeFilter(EventProvider.class));
        addIncludeFilter(new AnnotationTypeFilter(EventListener.class));
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return true;//返回true则listener和provider都能扫描进来了
    }
}

接下来就得考虑EventProvider作为一个接口,应该注入什么对象到spring容器中呢。答案很明确了,动态代理呗。不清楚动态代理的小伙伴先去了解下jdk的动态代理是什么鬼吧,这里就不展开讲了 https://blog.csdn.net/bestkilly/article/details/82141802

贴上代理类代码。其中manager在下文讲,是本项目的关键类,在代理类中通过使用manager的execute方法执行所有监听器。invoke方法是真正执行的方法,需要排除Object中定义的方法。

public class EventProviderProxy<T> implements InvocationHandler {
    private EventInitializationManager providerMethod;

    public EventProviderProxy(EventInitializationManager manager) {
        providerMethod = manager;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())){
                return method.invoke(this,args);
            }else if (isDefaultMethod(method)){
                return invokeDefaultMethod(proxy,method,args);
            }
        }catch (Throwable throwable){
            throwable.printStackTrace();
        }
        EventTrigger eventTrigger = method.getAnnotation(EventTrigger.class);
        if (eventTrigger == null)
            return null;
        //使用代理方法执行
        providerMethod.execute(eventTrigger.triggerClass(),method,args);
        return null;
    }


    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
            throws Throwable {
        final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                .getDeclaredConstructor(Class.class, int.class);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }
        final Class<?> declaringClass = method.getDeclaringClass();
        return constructor
                .newInstance(declaringClass,
                        MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
                .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }


    private boolean isDefaultMethod(Method method) {
        return (method.getModifiers()
                & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
                && method.getDeclaringClass().isInterface();
    }
}

接下来是代理工厂,用来创建返回代理对象

public class EventProxyFactory<T> {

    private final Class<T> providerInterface;

    public EventProxyFactory(Class<T> providerInterface) {
        this.providerInterface = providerInterface;
    }

    //获得动态代理对象
    @SuppressWarnings("unchecked")
    public T newInstance(EventInitializationManager manager){
        return (T) Proxy.newProxyInstance(providerInterface.getClassLoader(),new Class[]{providerInterface},new EventProviderProxy<T>(manager));
    }

    public Class<T> getProviderInterface() {
        return providerInterface;
    }
}

关键是在什么时候进行扫描和注入呢?看过ioc源码的小伙伴应该知道,BeanFactoryPostProcessor会在单例bean初始化前调用,在这个时候我们可以直接注入bean或者bean的定义信息到spring容器中。

下面是EventInitializationManager的代码,ApplicationListener<ContextRefreshedEvent>的作用过会儿再讲,现在先讲BeanFactoryPostProcessor和ResourceLoaderAware

ResourceLoaderAware就不详细说了,只是用来拿到ResourceLoader协助扫描

BeanFactoryPostProcessor需要实现postProcessBeanFactory(),在这里可以拿到beanFactory,利用这个就可以往spring容器中塞入bean或者bean的定义信息。通过循环需要扫描的包packages(这个属性是什么时候赋值的过会儿讲),扫描出所有的EventListener和EventProvider,其中EventProvider直接注入对象,而EventListener注入bean的定义信息(因为可能需要注入业务代码,因此初始化工作交给spring来完成)

/**
 * @author yhxst
 * @date 2019-02-22
 * 事件的管理类,职责为在spring初始化单实例bean之前
 * 主要职责为
 * 1.把provider动态代理对象和listener对象扫描出来并注册到spring容器中
 * 2.在容器初始化完成后,构建listener,trigger的映射关系
 * 3.提供触发事件的统一方法execute()
 */
public class EventInitializationManager implements ApplicationListener<ContextRefreshedEvent>, BeanFactoryPostProcessor, ResourceLoaderAware {
    private ApplicationContext applicationContext;

    //保存格式为:trigger接口类-->{trigger触发执行的method-->[listener对象]}
    private Map<Class<?>, Map<Method,Set<Object>>> PROVIDER_LISTENER_SYNC_MAP = new HashMap<>(); //同步执行队列
    private Map<Class<?>, Map<Method,Set<Object>>> PROVIDER_LISTENER_ASYNC_MAP = new HashMap<>(); //异步执行队列

    private ResourceLoader resourceLoader;

    // @EventProvider接口类所在的包
    private String[] packages = {};

    //保存所有事件提供者类
    private List<Class<?>> eventProviders = new ArrayList<>();
    //保存所有监听者类
    private List<Class<?>> eventListeners = new ArrayList<>();

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Set<BeanDefinition> beans = scanInterfaceDef();
        DefaultListableBeanFactory bf = (DefaultListableBeanFactory)beanFactory;
        registerProxyBean(beans, bf);
    }

    /**
     * 将扫描到的provider和listener注册到spring容器中
     * @param beans
     * @param beanFactory
     */
    private void registerProxyBean(Set<BeanDefinition> beans, DefaultListableBeanFactory beanFactory){
        for (BeanDefinition bd : beans){
            Class<?> clazz;
            try {
                clazz = Class.forName(bd.getBeanClassName());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                continue;
            }

            if (clazz.isAnnotationPresent(EventProvider.class)){
                eventProviders.add(clazz);

                String beanName = getBeanName(clazz);
                //直接实例化并注册上provider的FactoryBean
                EventProviderFactoryBean obj = new EventProviderFactoryBean<>(clazz,this);
                beanFactory.registerSingleton(beanName,obj );
            }else if (clazz.isAnnotationPresent(EventListener.class)){
                eventListeners.add(clazz);
                //将listener的信息注册到spring容器中,初始化交给spring来完成
                BeanDefinition listenerBeanefinition = new RootBeanDefinition();
                listenerBeanefinition.setBeanClassName(clazz.getName());

                beanFactory.registerBeanDefinition(getBeanName(clazz),listenerBeanefinition);
            }
            //有可能是被@Component修饰的其他接口或类
        }
    }

    private String getBeanName(Class<?> clazz) {
        String name = clazz.getSimpleName();
        //按首字母小写的形式生成bean名
        name = name.substring(0,1).toLowerCase() + name.substring(1);
        return name;
    }

    /**
     * 扫描各个包下的provider和listener
     * @return
     */
    private Set<BeanDefinition> scanInterfaceDef() {
        ClassPathScanningCandidateComponentProvider componentProvider = new EventBeanScanner();
        componentProvider.setResourceLoader(resourceLoader);

        Set<BeanDefinition> beans = new LinkedHashSet<>();
        for (String pkg:packages){
            beans.addAll(componentProvider.findCandidateComponents(pkg));
        }
        return beans;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    /**
     * 监听容器初始化完毕时间,开始映射监听
     * @param event
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        this.applicationContext = event.getApplicationContext();

        System.out.println("读取到所有的事件提供者::"+ eventProviders);
        if (eventProviders == null || eventProviders.isEmpty())
            return;

        for (Class<?> providerClz : eventProviders){
            initializationProvider(providerClz);
        }
    }

    private void initializationProvider(Class<?> providerClass) {
        Method[] methods = providerClass.getMethods();
        for (Method method:methods){
            if (!method.isAnnotationPresent(EventTrigger.class))
                continue;
            List<Object> asynList = new ArrayList<>();
            List<Object> synList = new ArrayList<>();

            //获得对应的触发器类
            EventTrigger eventTrigger = method.getAnnotation(EventTrigger.class);
            Class<?> triggerClass = eventTrigger.triggerClass();
            String methodName = eventTrigger.method();
            //通过反射获取监听器的方法
            Method triggerMethod;
            try {
                triggerMethod = triggerClass.getMethod(methodName,method.getParameterTypes());
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("没有找到对应的方法,trigger:["+triggerClass+"],method:["+methodName+"],param:["+method.getParameterTypes()+"]");
            }
            System.out.println(eventListeners);
            for (Class<?> listenerClz : eventListeners){
                Object listener = applicationContext.getBean(listenerClz);
                Class<?> listenerClass = listener.getClass();

                if (!isImplOf(listenerClass,triggerClass)){
                    continue;
                }
                //根据监听器注解配置的执行方法,放入相应同步异步的map
                EventListener eventListener = listenerClass.getAnnotation(EventListener.class);
                if (ListenerTriggeriOpportunityEnum.ASYNCHRONOUS.equals(eventListener.type())){
                    asynList.add(listener);
                }else {
                    synList.add(listener);
                }
            }
            addListenerToMap(triggerClass,triggerMethod,asynList,synList);
        }
    }

    /**
     * 将所有的映射关系都交给Manager管理
     * @param trigger
     * @param asynList
     * @param synList
     */
    private void addListenerToMap(Class<?> trigger, Method triggerMethod, List<Object> asynList, List<Object> synList) {
        Map<Method,Set<Object>> asynListenerMap = PROVIDER_LISTENER_ASYNC_MAP.get(trigger);
        Map<Method,Set<Object>> synListenerMap = PROVIDER_LISTENER_SYNC_MAP.get(trigger);
        if (asynListenerMap == null){
            asynListenerMap = new HashMap<>();
            PROVIDER_LISTENER_ASYNC_MAP.put(trigger,asynListenerMap);
        }
        if (synListenerMap == null){
            synListenerMap = new HashMap<>();
            PROVIDER_LISTENER_SYNC_MAP.put(trigger,synListenerMap);
        }

        Set<Object> asynListeners = asynListenerMap.get(triggerMethod);
        Set<Object> synListeners = synListenerMap.get(triggerMethod);
        if (asynListeners == null){
            asynListeners = new HashSet<>();
            asynListenerMap.put(triggerMethod,asynListeners);
        }
        if (synListeners == null){
            synListeners = new HashSet<>();
            synListenerMap.put(triggerMethod,synListeners);
        }

        asynListeners.addAll(asynList);
        synListeners.addAll(synList);
    }

    /**
     * provider 统一触发的方法
     * @param triggerClass
     * @param param
     */
    public void execute(Class<?> triggerClass,Method providerTriggerMethod, Object... param) throws NoSuchMethodException {
//  注意,异步执行目前是直接new Thread方式的,请用线程池或其他异步通知方式实现后再使用异步触发
        String methodName = providerTriggerMethod.getAnnotation(EventTrigger.class).method();
        Method method = triggerClass.getMethod(methodName, providerTriggerMethod.getParameterTypes());
        Map<Method,Set<Object>> asynListeners = PROVIDER_LISTENER_ASYNC_MAP.get(triggerClass);
        if (asynListeners != null && !asynListeners.isEmpty()){
            Set<Object> asynListenerList = asynListeners.get(method);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    runListener(asynListenerList,method,param);
                }
            }).start();
        }

        Map<Method,Set<Object>> syncListeners = PROVIDER_LISTENER_SYNC_MAP.get(triggerClass);
        if (syncListeners != null && !syncListeners.isEmpty()){
            Set<Object> syncListenerList = syncListeners.get(method);
            runListener(syncListenerList,method,param);
        }
    }

    private void runListener(Set<Object> listeners,Method method,Object... param){
        if (listeners == null)
            return;
        for (Object listener : listeners){
            try {
                method.invoke(listener,param);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    private boolean isImplOf(Class<?> listenerClass, Class<?> triggerClass) {
        Class<?>[] classes = listenerClass.getInterfaces();
        for (Class<?> c:classes){
            if (c.equals(triggerClass))
                return true;
        }
        return false;
    }

    /**
     * 创建Manager对象时赋值packages用
     * @param packages
     */
    public void setPackages(String[] packages) {
        this.packages = packages;
    }
}

EventProviderFactoryBean

public class EventProviderFactoryBean<T> implements FactoryBean<T> {
    private Class<T> clz;

    private EventInitializationManager manager;

    public EventProviderFactoryBean(Class<T> clz, EventInitializationManager manager) {
        this.clz = clz;
        this.manager = manager;
    }

    @Override
    public T getObject() throws Exception {
        //使用代理工厂获取对象
        return (T) new EventProxyFactory<T>(clz).newInstance(manager);
    }

    @Override
    public Class<?> getObjectType() {
        return clz;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

触发器和订阅者的绑定

到这里,事件类的扫描和注入工作已经完成了,接下来只需要把触发器和订阅者的映射关系绑定上就ok了。

储存的数据结构

 //保存格式为:trigger接口类-->{trigger触发执行的method-->[listener对象]}
    private Map<Class<?>, Map<Method,Set<Object>>> PROVIDER_LISTENER_SYNC_MAP = new HashMap<>(); //同步执行队列
    private Map<Class<?>, Map<Method,Set<Object>>> PROVIDER_LISTENER_ASYNC_MAP = new HashMap<>(); //异步执行队列

触发事件的时候进行调用

根据方法名和参数获得相应的Method,从上面的map拿到需要执行的Listener,循环执行就可以了,目前异步方法简单的使用new Thread()来实现,如果要在项目里用的话,请务必使用线程池或者其他异步通知方法改写后再用

 public void execute(Class<?> triggerClass,Method providerTriggerMethod, Object... param) throws NoSuchMethodException {
        String methodName = providerTriggerMethod.getAnnotation(EventTrigger.class).method();
        Method method = triggerClass.getMethod(methodName, providerTriggerMethod.getParameterTypes());
        Map<Method,Set<Object>> asynListeners = PROVIDER_LISTENER_ASYNC_MAP.get(triggerClass);
        if (asynListeners != null && !asynListeners.isEmpty()){
            Set<Object> asynListenerList = asynListeners.get(method);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    runListener(asynListenerList,method,param);
                }
            }).start();
        }

        Map<Method,Set<Object>> syncListeners = PROVIDER_LISTENER_SYNC_MAP.get(triggerClass);
        if (syncListeners != null && !syncListeners.isEmpty()){
            Set<Object> syncListenerList = syncListeners.get(method);
            runListener(syncListenerList,method,param);
        }
    }

    private void runListener(Set<Object> listeners,Method method,Object... param){
        if (listeners == null)
            return;
        for (Object listener : listeners){
            try {
                method.invoke(listener,param);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

接下来就是触发器和监听器的绑定了,因为需要拿到监听器的示例对象,因此可以考虑在容器初始化完成时来进行绑定。实现ApplicationListener<ContextRefreshedEvent>接口便会在容器初始化后调用相应的方法。具体代码在上方的manager中,毕竟数据都有,实现起来也比较简单,因此也就不详细讲了。

配置扫描包和注入EventInitializationManager

到这里为止,基本骨架都已经搭好了。现在只剩下两个问题,EventInitializationManager应该如何注入,如何配置需要扫描的路径。

不说废话,直接上注解。

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE})
@Import({EventCannerBeanDefinitionRegister.class})
public @interface EventScan {
    String[] value();
}

@Import就不在这解释了,我只要在项目中添加上这个注解,便可以把EventCannerBeanDefinitionRegister类注册到spring容器中,而这个也相当于一个开关,是否开启该事件框架。相应的,需要扫描的包也可以在注解上声明

@SpringBootApplication
@EventScan({"com.st.event.test"})
public class EventApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(EventApplication.class, args);
        TestEventProvider bean = context.getBean(TestEventProvider.class);
        System.out.println(bean);
    }

}

接下来开始贴EventCannerBeanDefinitionRegister代码。通过实现ImportBeanDefinitionRegistrar接口,便拥有了往容器中加入bean的能力。EventCannerBeanDefinitionRegister无非也就干了一件事,把EventInitializationManager定义信息(包括packages属性)放入spring中。

public class EventCannerBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EventScan.class.getName(), false);
        if(annotationAttributes != null) {
            BeanDefinition beanDefinition = getBeanDefinition((String[])annotationAttributes.get("value"));
            registry.registerBeanDefinition("com.st.event.eventInitializationManage", beanDefinition);
        }
    }

    private BeanDefinition getBeanDefinition(String[] values) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(EventInitializationManager.class);
        beanDefinition.setSource(null);
        beanDefinition.getPropertyValues().add("packages",values);
        beanDefinition.setRole(2);
        return beanDefinition;
    }
}

总结

到这里,这个事件框架的实现也完成了,这里是项目源码 https://github.com/yhxst/event 如果看了文章还不是很明白的同学可以去下载下来试试看。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值