SpringBoot整合canal实现自定义监听

实现的功能

通过canal实现自定义监听数据库变化并触发自定义请求

Canal介绍

binlog是mysql的二进制日志,对于操作数据库的语句,都以此形式保存。Canal是阿里MySQL数据库Binlog的增量订阅&消费组件 。基于数据库Binlog可以监控数据库数据的变化进而用于数据同步等业务。

Linux(Ubuntu)安装canal

这边为了节约时间直接引入别人的博客:链接: Linux(Ubuntu)安装canal.

SpringBoot整合Canal

省略SpringBoot工程创建过程…

pom文件
   <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-configuration-processorartifactId>
        dependency>
        <dependency>
            <groupId>com.alibaba.ottergroupId>
            <artifactId>canal.protocolartifactId>
            <version>1.1.5version>
        dependency>
        <dependency>
            <groupId>com.alibaba.ottergroupId>
            <artifactId>canal.clientartifactId>
            <version>1.1.5version>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

application.yml配置文件

server:
  port: 8080
canal:
  client:
    instances:
      example:
        host: 开通canal的ip地址
        port: 端口
        batchSize: 1000
        userName: 你的账号/可不填
        password: 你的密码/可不填
spring:
  main:
    allow-bean-definition-overriding: true

目录

SpringBoot整合canal实现自定义监听_第1张图片

annotaion

CanalEventListener

import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;

import java.lang.annotation.*;



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

    @AliasFor(annotation = Component.class)
    String value() default "";

}

DeleteListenPoint

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ListenPoint(eventType = CanalEntry.EventType.DELETE)
public @interface DeleteListenPoint {

    /**
     * canal destination
     * default for all
     * @return canal destination
     */
    @AliasFor(annotation = ListenPoint.class)
    String destination() default "";

    /**
     * database schema which you are concentrate on
     * default for all
     * @return canal destination
     */
    @AliasFor(annotation = ListenPoint.class)
    String[] schema() default {};

    /**
     * tables which you are concentrate on
     * default for all
     * @return canal destination
     */
    @AliasFor(annotation = ListenPoint.class)
    String[] table() default {};

}

EnableCanalClient

import com.yyq.common.canal.config.CanalClientConfiguration;
import com.yyq.common.canal.config.CanalConfig;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({CanalConfig.class, CanalClientConfiguration.class})
public @interface EnableCanalClient {
}

InsertListenPoint

import com.alibaba.otter.canal.protocol.CanalEntry;
import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;


@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ListenPoint(eventType = CanalEntry.EventType.INSERT)
public @interface InsertListenPoint {

    /**
     * canal destination
     * default for all
     * @return canal destination
     */
    @AliasFor(annotation = ListenPoint.class)
    String destination() default "";

    /**
     * database schema which you are concentrate on
     * default for all
     * @return canal destination
     */
    @AliasFor(annotation = ListenPoint.class)
    String[] schema() default {};

    /**
     * tables which you are concentrate on
     * default for all
     * @return canal destination
     */
    @AliasFor(annotation = ListenPoint.class)
    String[] table() default {};

}

UpdateListenPoint

import com.alibaba.otter.canal.protocol.CanalEntry;
import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;



@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ListenPoint(eventType = CanalEntry.EventType.UPDATE)
public @interface UpdateListenPoint {

    /**
     * canal destination
     * default for all
     * @return canal destination
     */
    @AliasFor(annotation = ListenPoint.class)
    String destination() default "";

    /**
     * database schema which you are concentrate on
     * default for all
     * @return canal destination
     */
    @AliasFor(annotation = ListenPoint.class)
    String[] schema() default {};

    /**
     * tables which you are concentrate on
     * default for all
     * @return canal destination
     */
    @AliasFor(annotation = ListenPoint.class)
    String[] table() default {};

}

ListenPoint

import com.alibaba.otter.canal.protocol.CanalEntry;

import java.lang.annotation.*;



@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ListenPoint {

    /**
     * canal destination
     * default for all
     * @return canal destination
     */
    String destination() default "";

    /**
     * database schema which you are concentrate on
     * default for all
     * @return canal destination
     */
    String[] schema() default {};

    /**
     * tables which you are concentrate on
     * default for all
     * @return canal destination
     */
    String[] table() default {};

    /**
     * canal event type
     * default for all
     * @return canal event type
     */
    CanalEntry.EventType[] eventType() default {};

}

client/exception

InsertListenPoint

public class CanalClientException extends RuntimeException {
    public CanalClientException() {
    }

    public CanalClientException(String message) {
        super(message);
    }

    public CanalClientException(String message, Throwable cause) {
        super(message, cause);
    }

    public CanalClientException(Throwable cause) {
        super(cause);
    }

    public CanalClientException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

client/transfer

AbstractBasicMessageTransponder

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.Message;
import com.yyq.common.canal.annotation.ListenPoint;
import com.yyq.common.canal.client.ListenerPoint;
import com.yyq.common.canal.client.exception.CanalClientException;
import com.yyq.common.canal.config.CanalConfig;
import com.yyq.common.canal.event.CanalEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;


public abstract class AbstractBasicMessageTransponder extends AbstractMessageTransponder {

    private final static Logger logger = LoggerFactory.getLogger(AbstractBasicMessageTransponder.class);

    public AbstractBasicMessageTransponder(CanalConnector connector, Map.Entry<String, CanalConfig.Instance> config, List<CanalEventListener> listeners, List<ListenerPoint> annoListeners) {
         super(connector, config, listeners, annoListeners);
    }

    @Override
    protected void distributeEvent(Message message) {
        List<CanalEntry.Entry> entries = message.getEntries();
        for (CanalEntry.Entry entry : entries) {
            //ignore the transaction operations
            List<CanalEntry.EntryType> ignoreEntryTypes = getIgnoreEntryTypes();
            if (ignoreEntryTypes != null
                    && ignoreEntryTypes.stream().anyMatch(t -> entry.getEntryType() == t)) {
                continue;
            }
            CanalEntry.RowChange rowChange;
            try {
                rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new CanalClientException("ERROR ## parser of event has an error , data:" + entry.toString(),
                        e);
            }
            //ignore the ddl operation
            if (rowChange.hasIsDdl() && rowChange.getIsDdl()) {
                processDdl(rowChange);
                continue;
            }
            for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
                //distribute to listener interfaces
                distributeByImpl(rowChange.getEventType(), entry);
                //distribute to annotation listener interfaces
                distributeByAnnotation(destination,
                        entry.getHeader().getSchemaName(),
                        entry.getHeader().getTableName(),
                        rowChange.getEventType(),
                        entry);
            }

        }
    }

    /**
     * process the ddl event
     * @param rowChange rowChange
     */
    protected void processDdl(CanalEntry.RowChange rowChange) {}

    /**
     * distribute to listener interfaces
     *
     * @param eventType eventType
     * @param rowData rowData
     */
    protected void distributeByImpl(CanalEntry.EventType eventType, CanalEntry.Entry rowData) {
        if (listeners != null) {
            for (CanalEventListener listener : listeners) {
                listener.onEvent(eventType, rowData);
            }
        }
    }

    /**
     * distribute to annotation listener interfaces
     *
     * @param destination destination
     * @param schemaName schema
     * @param tableName table name
     * @param eventType event type
     * @param rowData row data
     */
    protected void distributeByAnnotation(String destination,
                                        String schemaName,
                                        String tableName,
                                        CanalEntry.EventType eventType,
                                        CanalEntry.Entry rowData) {
        //invoke the listeners
        annoListeners.forEach(point -> point
                .getInvokeMap()
                .entrySet()
                .stream()
                .filter(getAnnotationFilter(destination, schemaName, tableName, eventType))
                .forEach(entry -> {
                    Method method = entry.getKey();
                    method.setAccessible(true);
//                    try {
                        Object[] args = getInvokeArgs(method, eventType, rowData);
                    try {
                        method.invoke(point.getTarget(), args);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException(e);
                    }
//                    } catch (Exception e) {
//                        logger.error("{}: Error occurred when invoke the listener's interface! class:{}, method:{}",
//                                Thread.currentThread().getName(),
//                                point.getTarget().getClass().getName(), method.getName());
//                    }
                }));
    }
    /**
     * get the filters predicate
     *
     * @param destination destination
     * @param schemaName schema
     * @param tableName table name
     * @param eventType event type
     * @return predicate
     */
    protected abstract Predicate<Map.Entry<Method, ListenPoint>> getAnnotationFilter(String destination,
                                                                                     String schemaName,
                                                                                     String tableName,
                                                                                     CanalEntry.EventType eventType);

    /**
     * get the args
     *
     * @param method method
     * @param eventType event type
     * @param rowData row data
     * @return args which will be used by invoking the annotation methods
     */
    protected abstract Object[] getInvokeArgs(Method method, CanalEntry.EventType eventType,
                                              CanalEntry.Entry rowData);

    /**
     * get the ignore eventType list
     *
     * @return eventType list
     */
    protected List<CanalEntry.EntryType> getIgnoreEntryTypes() {
        return Collections.emptyList();
    }

}

AbstractMessageTransponder

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
import com.yyq.common.canal.client.ListenerPoint;
import com.yyq.common.canal.config.CanalConfig;
import com.yyq.common.canal.event.CanalEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;


public abstract class AbstractMessageTransponder implements MessageTransponder {

    /**
     * canal connector
     */
    private final CanalConnector connector;

    /**
     * custom config
     */
    protected final CanalConfig.Instance config;

    /**
     * destination of canal server
     */
    protected final String destination;

    /**
     * listeners which are used by implementing the Interface
     */
    protected final List<CanalEventListener> listeners = new ArrayList<>();

    /**
     * listeners which are used by annotation
     */
    protected final List<ListenerPoint> annoListeners = new ArrayList<>();

    /**
     * running flag
     */
    private volatile boolean running = true;

    private static final Logger logger = LoggerFactory.getLogger(AbstractMessageTransponder.class);

    public AbstractMessageTransponder(CanalConnector connector,
                                      Map.Entry<String, CanalConfig.Instance> config,
                                      List<CanalEventListener> listeners,
                                      List<ListenerPoint> annoListeners) {
        Objects.requireNonNull(connector, "connector can not be null!");
        Objects.requireNonNull(config, "config can not be null!");
        this.connector = connector;
        this.destination = config.getKey();
        this.config = config.getValue();
        if (listeners != null)
            this.listeners.addAll(listeners);
        if (annoListeners != null)
            this.annoListeners.addAll(annoListeners);
    }

    @Override
    public void run() {
        int errorCount = config.getRetryCount();
        final long interval = config.getAcquireInterval();
        final String threadName = Thread.currentThread().getName();
        while (running && !Thread.currentThread().isInterrupted()) {
            try {
                Message message = connector.getWithoutAck(config.getBatchSize());
                long batchId = message.getId();
                int size = message.getEntries().size();
                if (logger.isDebugEnabled()) {
                    logger.debug("{}: Get message from canal server >>>>> size:{}", threadName, size);
                }
                //empty message
                if (batchId == -1 || size == 0) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("{}: Empty message... sleep for {} millis", threadName, interval);
                    }
                    Thread.sleep(interval);
                } else {
                    distributeEvent(message);
                }
                // commit ack
                connector.ack(batchId);
                if (logger.isDebugEnabled()) {
                    logger.debug("{}: Ack message. batchId:{}", threadName, batchId);
                }
            } catch (CanalClientException e) {
                errorCount--;
                logger.error(threadName + ": Error occurred!! ", e);
                try {
                    Thread.sleep(interval);
                } catch (InterruptedException e1) {
                    errorCount = 0;
                }
            } catch (InterruptedException e) {
                errorCount = 0;
                connector.rollback();
            } finally {
                if (errorCount <= 0) {
                    stop();
                    logger.info("{}: Topping the client.. ", Thread.currentThread().getName());
                }
            }
        }
        stop();
        logger.info("{}: client stopped. ", Thread.currentThread().getName());
    }

    /**
     * to distribute the message to special event and let the event listeners to handle it
     *
     * @param message canal message
     */
    protected abstract void distributeEvent(Message message);

    /**
     * stop running
     */
    void stop() {
        running = false;
    }

}

DefaultMessageTransponder

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.yyq.common.canal.annotation.ListenPoint;
import com.yyq.common.canal.client.ListenerPoint;
import com.yyq.common.canal.config.CanalConfig;
import com.yyq.common.canal.event.CanalEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;


public class DefaultMessageTransponder extends AbstractBasicMessageTransponder {

    private final static Logger logger = LoggerFactory.getLogger(DefaultMessageTransponder.class);

    public DefaultMessageTransponder(CanalConnector connector,
                          Map.Entry<String, CanalConfig.Instance> config,
                          List<CanalEventListener> listeners,
                          List<ListenerPoint> annoListeners) {
        super(connector, config, listeners, annoListeners);
    }

    /**
     * get the filters predicate
     *
     * @param destination destination
     * @param schemaName schema
     * @param tableName table name
     * @param eventType event type
     * @return predicate
     */
    @Override
    protected Predicate<Map.Entry<Method, ListenPoint>> getAnnotationFilter(String destination,
                                                                            String schemaName,
                                                                            String tableName,
                                                                            CanalEntry.EventType eventType) {
        Predicate<Map.Entry<Method, ListenPoint>> df = e -> StringUtils.isEmpty(e.getValue().destination())
                || e.getValue().destination().equals(destination);
        Predicate<Map.Entry<Method, ListenPoint>> sf = e -> e.getValue().schema().length == 0
                || Arrays.stream(e.getValue().schema()).anyMatch(s -> s.equals(schemaName));
        Predicate<Map.Entry<Method, ListenPoint>> tf = e -> e.getValue().table().length == 0
                || Arrays.stream(e.getValue().table()).anyMatch(t -> t.equals(tableName));
        Predicate<Map.Entry<Method, ListenPoint>> ef = e -> e.getValue().eventType().length == 0
                || Arrays.stream(e.getValue().eventType()).anyMatch(ev -> ev == eventType);
        return df.and(sf).and(tf).and(ef);
    }

    @Override
    protected Object[] getInvokeArgs(Method method, CanalEntry.EventType eventType, CanalEntry.Entry rowData) {
        return Arrays.stream(method.getParameterTypes())
                .map(p -> p == CanalEntry.EventType.class
                        ? eventType
                        : p == CanalEntry.Entry.class
                        ? rowData : null)
                .toArray();
    }

    @Override
    protected List<CanalEntry.EntryType> getIgnoreEntryTypes() {
        return Arrays.asList(CanalEntry.EntryType.TRANSACTIONBEGIN, CanalEntry.EntryType.TRANSACTIONEND, CanalEntry.EntryType.HEARTBEAT);
    }
}

DefaultTransponderFactory

import com.alibaba.otter.canal.client.CanalConnector;
import com.yyq.common.canal.client.ListenerPoint;
import com.yyq.common.canal.config.CanalConfig;
import com.yyq.common.canal.event.CanalEventListener;

import java.util.List;
import java.util.Map;


public class DefaultTransponderFactory implements TransponderFactory {
    @Override
    public MessageTransponder newTransponder(CanalConnector connector, Map.Entry<String, CanalConfig.Instance> config, List<CanalEventListener> listeners,
                                             List<ListenerPoint> annoListeners) {
        return new DefaultMessageTransponder(connector, config, listeners, annoListeners);
    }
}

MessageTransponder


public interface MessageTransponder extends Runnable {
}

AbstractBasicMessageTransponder

public class MessageTransponders {

    public static TransponderFactory defaultMessageTransponder() {
        return new DefaultTransponderFactory();
    }
}

client

AbstractCanalClient


import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.yyq.common.canal.client.exception.CanalClientException;
import com.yyq.common.canal.client.transfer.TransponderFactory;
import com.yyq.common.canal.config.CanalConfig;
import org.springframework.util.StringUtils;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;


public abstract class AbstractCanalClient implements CanalClient {

    /**
     * running flag
     */
    private volatile boolean running;

    /**
     * customer config
     */
    private CanalConfig canalConfig;


    /**
     * TransponderFactory
     */
    protected final TransponderFactory factory;

    AbstractCanalClient(CanalConfig canalConfig, TransponderFactory factory) {
        Objects.requireNonNull(canalConfig, "canalConfig can not be null!");
        Objects.requireNonNull(canalConfig, "transponderFactory can not be null!");
        this.canalConfig = canalConfig;
        this.factory = factory;
    }

    @Override
    public void start() {
        Map<String, CanalConfig.Instance> instanceMap = getConfig();
        for (Map.Entry<String, CanalConfig.Instance> instanceEntry : instanceMap.entrySet()) {
            process(processInstanceEntry(instanceEntry), instanceEntry);
        }

    }

    /**
     * To initialize the canal connector
     * @param connector CanalConnector
     * @param config config
     */
    protected abstract void process(CanalConnector connector, Map.Entry<String, CanalConfig.Instance> config);

    private CanalConnector processInstanceEntry(Map.Entry<String, CanalConfig.Instance> instanceEntry) {
        CanalConfig.Instance instance = instanceEntry.getValue();
        CanalConnector connector;
        if (instance.isClusterEnabled()) {
            List<SocketAddress> addresses = new ArrayList<>();
            for (String s : instance.getZookeeperAddress()) {
                String[] entry = s.split(":");
                if (entry.length != 2)
                    throw new CanalClientException("error parsing zookeeper address:" + s);
                addresses.add(new InetSocketAddress(entry[0], Integer.parseInt(entry[1])));
            }
            connector = CanalConnectors.newClusterConnector(addresses, instanceEntry.getKey(),
                    instance.getUserName(),
                    instance.getPassword());
        } else {
            connector = CanalConnectors.newSingleConnector(new InetSocketAddress(instance.getHost(), instance.getPort()),
                    instanceEntry.getKey(),
                    instance.getUserName(),
                    instance.getPassword());
        }
        connector.connect();
        if (!StringUtils.isEmpty(instance.getFilter())) {
            connector.subscribe(instance.getFilter());
        } else {
            connector.subscribe();
        }

        connector.rollback();
        return connector;
    }

    /**
     * get the config
     *
     * @return config
     */
    protected Map<String, CanalConfig.Instance> getConfig() {
        CanalConfig config = canalConfig;
        Map<String, CanalConfig.Instance> instanceMap;
        if (config != null && (instanceMap = config.getInstances()) != null && !instanceMap.isEmpty()) {
            return config.getInstances();
        } else {
            throw new CanalClientException("can not get the configuration of canal client!");
        }
    }

    @Override
    public void stop() {
        setRunning(false);
    }

    @Override
    public boolean isRunning() {
        return running;
    }

    private void setRunning(boolean running) {
        this.running = running;
    }
}


CanalClient

public interface CanalClient {

    /**
     * open the canal client
     * to get the config and connect to the canal server (1 : 1 or 1 : n)
     * and then  transfer the event to the special listener
     * */
    void start();

    /**
     * stop the client
     */
    void stop();

    /**
     * is running
     * @return yes or no
     */
    boolean isRunning();
}

ListenerPoint

import com.yyq.common.canal.annotation.ListenPoint;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;


    public class ListenerPoint {
    private Object target;
    private Map<Method, ListenPoint> invokeMap = new HashMap<>();

    ListenerPoint(Object target, Method method, ListenPoint anno) {
        this.target = target;
        this.invokeMap.put(method, anno);
    }

    public Object getTarget() {
        return target;
    }

    public Map<Method, ListenPoint> getInvokeMap() {
        return invokeMap;
    }
}

SimpleCanalClient


import com.alibaba.otter.canal.client.CanalConnector;
import com.yyq.common.canal.annotation.ListenPoint;
import com.yyq.common.canal.client.transfer.TransponderFactory;
import com.yyq.common.canal.config.CanalConfig;
import com.yyq.common.canal.event.CanalEventListener;
import com.yyq.common.canal.util.BeanUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


public class SimpleCanalClient extends AbstractCanalClient {

    /**
     * executor
     */
    private ThreadPoolExecutor executor;

    /**
     * listeners which are used by implementing the Interface
     */
    private final List<CanalEventListener> listeners = new ArrayList<>();

    /**
     * listeners which are used by annotation
     */
    private final List<ListenerPoint> annoListeners = new ArrayList<>();

    private final static Logger logger = LoggerFactory.getLogger(SimpleCanalClient.class);

    public SimpleCanalClient(CanalConfig canalConfig, TransponderFactory factory) {
        super(canalConfig, factory);
        executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                60L, TimeUnit.SECONDS,
                new SynchronousQueue<>(), Executors.defaultThreadFactory());
        initListeners();
    }

    @Override
    protected void process(CanalConnector connector, Map.Entry<String, CanalConfig.Instance> config) {
        executor.submit(factory.newTransponder(connector, config, listeners, annoListeners));
    }

    @Override
    public void stop() {
        super.stop();
        executor.shutdown();
    }

    /**
     * init listeners
     */
    private void initListeners() {
        logger.info("{}: initializing the listeners....", Thread.currentThread().getName());
        List<CanalEventListener> list = BeanUtil.getBeansOfType(CanalEventListener.class);
        if (list != null) {
            listeners.addAll(list);
        }
        Map<String, Object> listenerMap = BeanUtil.getBeansWithAnnotation(com.yyq.common.canal.annotation.CanalEventListener.class);
        if (listenerMap != null) {
            for (Object target : listenerMap.values()) {
                Method[] methods = target.getClass().getDeclaredMethods();
                if (methods != null && methods.length > 0) {
                    for (Method method : methods) {
                        //只能解析到ListenPointa
//                        ListenPoint l = AnnotationUtils.findAnnotation(method, ListenPoint.class);
                        //子注解属性传递给父注解
                        ListenPoint l = AnnotatedElementUtils.getMergedAnnotation(method, ListenPoint.class);
                        if (l != null) {
                            annoListeners.add(new ListenerPoint(target, method, l));
                        }
                    }
                }
            }
        }
        logger.info("{}: initializing the listeners end.", Thread.currentThread().getName());
        if (logger.isWarnEnabled() && listeners.isEmpty() && annoListeners.isEmpty()) {
            logger.warn("{}: No listener found in context! ", Thread.currentThread().getName());
        }
    }
}

config

CanalClientConfiguration

public class CanalClientConfiguration {

    private final static Logger logger = LoggerFactory.getLogger(CanalClientConfiguration.class);

    @Autowired
    private CanalConfig canalConfig;

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public BeanUtil beanUtil() {
        return new BeanUtil();
    }

    @Bean
    private CanalClient canalClient() {
        ConditionVo conditionVo = new ConditionVo();
        // TODO 目前为SpringBoot 启动需要使用@EnableCanalClient标识启动类
        // TODO 后续可修改为触发执行
        CanalClient canalClient = new SimpleCanalClient(canalConfig, MessageTransponders.defaultMessageTransponder());
        canalClient.start();
        logger.info("Starting canal client....");
        return canalClient;
    }
}

CanalConfig


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;


@Order(Ordered.HIGHEST_PRECEDENCE)
@ConfigurationProperties(prefix = "canal.client")
@Component
@Data
public class CanalConfig {

    /**
     * instance config
     */
    private Map<String, Instance> instances = new LinkedHashMap<>();

    public Map<String, Instance> getInstances() {
        return instances;
    }

    public void setInstances(Map<String, Instance> instances) {
        this.instances = instances;
    }

    /**
     * instance config class
     */
    public static class Instance {

        /**
         * is cluster-mod
         */
        private boolean clusterEnabled;


        /**
         * zookeeper address
         */
        private Set<String> zookeeperAddress = new LinkedHashSet<>();

        /**
         * canal server host
         */
        private String host = "127.0.0.1";

        /**
         * canal server port
         */
        private int port = 11111;

        /**
         * canal user name
         */
        private String userName = "root";

        /**
         * canal password
         */
        private String password = "YYQKitty123";

        /**
         * size when get messages from the canal server
         */
        private int batchSize = 1000;

        /**
         * filter
         */
        private String filter;

        /**
         * retry count when error occurred
         */
        private int retryCount = 5;

        /**
         * interval of the message-acquiring
         */
        private long acquireInterval = 1000;

        public Instance() {}

        public boolean isClusterEnabled() {
            return clusterEnabled;
        }

        public void setClusterEnabled(boolean clusterEnabled) {
            this.clusterEnabled = clusterEnabled;
        }

        public Set<String> getZookeeperAddress() {
            return zookeeperAddress;
        }

        public void setZookeeperAddress(Set<String> zookeeperAddress) {
            this.zookeeperAddress = zookeeperAddress;
        }

        public String getHost() {
            return host;
        }

        public void setHost(String host) {
            this.host = host;
        }

        public int getPort() {
            return port;
        }

        public void setPort(int port) {
            this.port = port;
        }

        public String getUserName() {
            return userName;
        }

        public void setUserName(String userName) {
            this.userName = userName;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public int getBatchSize() {
            return batchSize;
        }

        public void setBatchSize(int batchSize) {
            this.batchSize = batchSize;
        }

        public String getFilter() {
            return filter;
        }

        public void setFilter(String filter) {
            this.filter = filter;
        }

        public int getRetryCount() {
            return retryCount;
        }

        public void setRetryCount(int retryCount) {
            this.retryCount = retryCount;
        }

        public long getAcquireInterval() {
            return acquireInterval;
        }

        public void setAcquireInterval(long acquireInterval) {
            this.acquireInterval = acquireInterval;
        }
    }

}

event

CanalEventListener

import com.alibaba.otter.canal.protocol.CanalEntry;


public interface CanalEventListener {

    /**
     * run when event was fired
     *
     * @param eventType eventType
     * @param rowData rowData
     */
    void onEvent(CanalEntry.EventType eventType, CanalEntry.Entry rowData);

}

DmlCanalEventListener



import com.alibaba.otter.canal.protocol.CanalEntry;

import java.util.Objects;


public interface DmlCanalEventListener extends CanalEventListener {

    @Override
    default void onEvent(CanalEntry.EventType eventType, CanalEntry.Entry rowData) {
        Objects.requireNonNull(eventType);
        switch (eventType) {
            case INSERT:
                onInsert(rowData);
                break;
            case UPDATE:
                onUpdate(rowData);
                break;
            case DELETE:
                onDelete(rowData);
                break;
            default:
                break;
        }
    }

    /**
     * fired on insert event
     *
     * @param rowData rowData
     */
    void onInsert(CanalEntry.Entry rowData);

    /**
     * fired on update event
     *
     * @param rowData rowData
     */
    void onUpdate(CanalEntry.Entry rowData);

    /**
     * fired on delete event
     *
     * @param rowData rowData
     */
    void onDelete(CanalEntry.Entry rowData);

}

listener

CanalDataEventListener


import com.alibaba.fastjson.JSON;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.google.protobuf.InvalidProtocolBufferException;
import com.yyq.common.canal.annotation.CanalEventListener;
import com.yyq.common.canal.annotation.ListenPoint;
import com.yyq.common.canal.util.CanalEventType;
import com.yyq.common.canal.util.RequestUtils;
import com.yyq.common.canal.vo.CanalVO;
import com.yyq.common.canal.vo.ConditionVo;
import org.apache.commons.lang.StringUtils;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


@CanalEventListener
public class CanalDataEventListener {
    private static List<ConditionVo> conditionVoList = new ArrayList<>();
    public static void init(List<ConditionVo> list){
        //先清空
        conditionVoList.clear();
        //再添加
        conditionVoList.addAll(list);
    }
    /**
     * destination 指定监听实例
     * schema	指定监听数据库名称
     * table	指定表名 {"user"} || {"user","user_info"}
     * eventType 指定监听CURD类型
     */
    @ListenPoint(eventType = {CanalEntry.EventType.INSERT,CanalEntry.EventType.UPDATE,CanalEntry.EventType.DELETE})
    public void listen(CanalEntry.EventType eventType, CanalEntry.Entry entry) {
        //修改
        List<ConditionVo> eventUpdateList = conditionVoList.stream().filter(item -> Objects.equals(item.getType(), CanalEventType.UPDATE_VALUE)).collect(Collectors.toList());
        //添加
        List<ConditionVo> eventInsertList = conditionVoList.stream().filter(item -> Objects.equals(item.getType(), CanalEventType.INSERT_VALUE)).collect(Collectors.toList());
        //删除
        List<ConditionVo> eventDeleteList = conditionVoList.stream().filter(item -> Objects.equals(item.getType(), CanalEventType.DELETE_VALUE)).collect(Collectors.toList());
        if (eventType == CanalEntry.EventType.DELETE) { //删除类
            saveDeleteSql(entry,eventDeleteList);
        } else if (eventType == CanalEntry.EventType.UPDATE) { //修改类
            saveUpdateSql(entry,eventUpdateList);
        } else if (eventType == CanalEntry.EventType.INSERT) { // 新增
            saveInsertSql(entry,eventInsertList);
        }
    }

    /**
     * 监听修改事件
     * @param entry
     */
    private void saveUpdateSql(CanalEntry.Entry entry,List<ConditionVo> conditionVoList) {
        try {
            CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
            for (CanalEntry.RowData rowData : rowDatasList) {
                //数据变更前
                List<CanalEntry.Column> beforeColumnsList2 = rowData.getBeforeColumnsList();
                //数据变更后
                List<CanalEntry.Column> afterColumnsList2 = rowData.getAfterColumnsList();
                Map<String, String> data1 = new HashMap<>();
                afterColumnsList2.stream().forEach(item -> {
                    if(StringUtils.isNotEmpty(item.getValue())){
                        data1.put(lineToHump(item.getName()), item.getValue());
                    }
                });
                Map<String, String> data2 = new HashMap<>();
                beforeColumnsList2.stream().forEach(item -> {
                    if(StringUtils.isNotEmpty(item.getValue())){
                        data2.put(lineToHump(item.getName()), item.getValue());
                    }
                });
                //修改后的数据
                List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
                System.out.println("afterColumnsList.get(0).getValue() = " + afterColumnsList.get(0).getValue());
                CanalVO afterUpdate = CanalVO.ok(entry.getHeader().getSchemaName(),entry.getHeader().getTableName(), "UPDATE", data1, afterColumnsList.get(0).getValue());
                //修改前的数据
                List<CanalEntry.Column> oldColumnList = rowData.getAfterColumnsList();
                CanalVO before = CanalVO.ok(entry.getHeader().getSchemaName(),entry.getHeader().getTableName(),"UPDATE", data2, oldColumnList.get(0).getValue());
                //进行规则校验
                RequestUtils.updateRequest(before,afterUpdate,conditionVoList);
                System.out.println(JSON.toJSONString(before));
            }
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }
    /**
     * 监听删除事件
     *
     * @param entry
     */
    private void saveDeleteSql(CanalEntry.Entry entry,List<ConditionVo> conditionVoList) {
        try {
            CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
            for (CanalEntry.RowData rowData : rowDatasList) {
                List<CanalEntry.Column> oldColumnList = rowData.getBeforeColumnsList();
                CanalVO delete = CanalVO.ok(entry.getHeader().getSchemaName(),entry.getHeader().getTableName(), "DELETE", null, oldColumnList.get(0).getValue());
                System.out.println("删除返回 : " + JSON.toJSONString(delete));
            }
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }
    /**
     * 监听插入事件
     *
     * @param entry
     */
    private void saveInsertSql(CanalEntry.Entry entry,List<ConditionVo> conditionVoList) {
        try {
            CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
            for (CanalEntry.RowData rowData : rowDatasList) {
                List<CanalEntry.Column> columnList = rowData.getAfterColumnsList();
                Map<String, String> data = new HashMap<>();
                columnList.stream().forEach(item -> {
                    if(StringUtils.isNotEmpty(item.getValue())){
                        data.put(lineToHump(item.getName()), item.getValue());
                    }
                });
                CanalVO insert = CanalVO.ok(entry.getHeader().getSchemaName(),entry.getHeader().getTableName(), "INSERT", data, null);
                System.out.println("插入返回 : " + JSON.toJSONString(insert));
            }
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }
    private static Pattern linePattern = Pattern.compile("_(\\w)");
    public static String lineToHump(String str) {
        str = str.toLowerCase();
        Matcher matcher = linePattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

}

util

BeanUtil


import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


@Component
public class BeanUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        BeanUtil.applicationContext = applicationContext;
    }

    public static <T> T getBean(Class<T> clazz) {
        T obj;
        try {
            obj = applicationContext.getBean(clazz);
        } catch (Exception e) {
            obj = null;
        }
        return obj;
    }

    public static <T> List<T> getBeansOfType(Class<T> clazz) {
        Map<String, T> map;
        try {
            map = applicationContext.getBeansOfType(clazz);
        } catch (Exception e) {
            map = null;
        }
        return map == null ? null : new ArrayList<>(map.values());
    }

    public static Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> anno) {
        Map<String, Object> map;
        try {
            map = applicationContext.getBeansWithAnnotation(anno);
        } catch (Exception e) {
            map = null;
        }
        return map;
    }

}

CanalEventType

public enum CanalEventType {
    INSERT(0, 1),
    UPDATE(1, 2),
    DELETE(2, 3);

    public static final int INSERT_VALUE = 1;
    public static final Integer UPDATE_VALUE = 2;
    public static final Integer DELETE_VALUE = 3;

    private final Integer index;

    private final Integer value;
    CanalEventType(Integer index, Integer value) {
        this.index = index;
        this.value = value;
    }

    public Integer getIndex() {
        return index;
    }

    public Integer getValue() {
        return value;
    }
}

CanalUtil

import com.yyq.common.canal.client.CanalClient;
import com.yyq.common.canal.client.SimpleCanalClient;
import com.yyq.common.canal.client.transfer.MessageTransponders;
import com.yyq.common.canal.config.CanalConfig;

public class CanalUtil {

    public static void start(CanalConfig canalConfig){

        CanalClient canalClient = new SimpleCanalClient(canalConfig, MessageTransponders.defaultMessageTransponder());
        canalClient.start();
    }
    public static void stop(CanalConfig canalConfig){
        CanalClient canalClient = new SimpleCanalClient(canalConfig, MessageTransponders.defaultMessageTransponder());
        canalClient.stop();
    }
}

CondtionType

public enum ConditionType {
    DA(0, ">"),
    XIAO(1, "<"),
    DENG(2, "="),
    IN(2, "in");

    public static final String DA_VALUE = ">";
    public static final String XIAO_VALUE = "<";
    public static final String DENG_VALUE = "=";
    public static final String IN_VALUE = "in";

    private final Integer index;

    private final String value;
    ConditionType(Integer index, String value) {
        this.index = index;
        this.value = value;
    }

    public Integer getIndex() {
        return index;
    }

    public String getValue() {
        return value;
    }
}

RequestUtils

import com.yyq.common.canal.vo.CanalVO;
import com.yyq.common.canal.vo.ConditionVo;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.yyq.common.canal.util.ConditionType.*;

public class RequestUtils {

    public static void updateRequest(CanalVO before,CanalVO after,List<ConditionVo> conditionVos){

        for (ConditionVo conditionVo : conditionVos) {
            if (conditionVo.getTableName().equals(after.getTableName())){
                if (conditionVo.getId() != null){
                    //ID存在的情况
                     if (after.getId().equals(conditionVo.getId())){
                         String oldValue = before.getData().get(conditionVo.getFieldName());
                         String changeValue = after.getData().get(conditionVo.getFieldName());
                         if (null != changeValue && !oldValue.equals(changeValue)){
                             updateType(conditionVo.getCondition().toString(),changeValue,conditionVo.getChangeFieldValue(),conditionVo.getId());
                         }
                     }
                }else{
                    // ID不存在的情况
                    String oldValue = before.getData().get(conditionVo.getFieldName());
                    String changeValue = after.getData().get(conditionVo.getFieldName());
                    if (null != changeValue && !oldValue.equals(changeValue)){
                        updateType(conditionVo.getCondition().toString(),changeValue,conditionVo.getChangeFieldValue(),null);
                    }
                }
                // 不存在ID的情况
                // if (after.getId().equals(conditionVo.getId())){
                // }
            }
        }
    }

    public static void insertRequest(CanalVO insert,List<ConditionVo> conditionVoList){

    }

    public static void deleteRequest(CanalVO delete,List<ConditionVo> conditionVoList){

    }
    private static void updateType(String condition, String oldValue, String newValue,String id) {
        if (condition.equals(DA_VALUE)){
            if (Integer.parseInt(oldValue) > Integer.parseInt(newValue)){
                //发起请求
            }
        }else if (condition.equals(XIAO_VALUE)){
            if (Integer.parseInt(oldValue) < Integer.parseInt(newValue)){
                System.out.println("发送请求 <<<<<<<<<<");
            }
        }else if (condition.equals(DENG_VALUE)){
            if (oldValue.equals(newValue)){
                System.out.println("发送请求 ==========");
            }
        }else if (condition.equals(IN_VALUE)){
            if (oldValue.contains(newValue)){
                //发起请求
            }
        }
    }
    private static void insertType(String condition, String oldValue, String newValue) {
        if (condition.equals(DA_VALUE)){
            if (Integer.parseInt(oldValue) > Integer.parseInt(newValue)){
                //发起请求
            }
        }else if (condition.equals(XIAO_VALUE)){
            if (Integer.parseInt(oldValue) < Integer.parseInt(newValue)){
                System.out.println("发送请求 <<<<<<<<<<");
            }
        }else if (condition.equals(DENG_VALUE)){
            if (oldValue.equals(newValue)){
                System.out.println("发送请求 ==========");
            }
        }else if (condition.equals(IN_VALUE)){
            if (oldValue.contains(newValue)){
                //发起请求
            }
        }
    }
    private static void deleteType(String condition, String oldValue, String newValue) {
        if (condition.equals(DA_VALUE)){
            if (Integer.parseInt(oldValue) > Integer.parseInt(newValue)){
                //发起请求
            }
        }else if (condition.equals(XIAO_VALUE)){
            if (Integer.parseInt(oldValue) < Integer.parseInt(newValue)){
                System.out.println("发送请求 <<<<<<<<<<");
            }
        }else if (condition.equals(DENG_VALUE)){
            if (oldValue.equals(newValue)){
                System.out.println("发送请求 ==========");
            }
        }else if (condition.equals(IN_VALUE)){
            if (oldValue.contains(newValue)){
                //发起请求
            }
        }
    }
}

Vo

CanalVo

import java.util.Map;

public class CanalVO {
    private String schemaName;
    private String tableName; // 表名
    private String type; // 类型(更新、删除、插入)
    private Map<String,String> data; // 数据JSON  自己转对应表格实体类
    private String id;  // 更新或删除都是根据ID来

    public static CanalVO ok(String schemaName,String tableName,String type,Map<String,String> data,String id){
        CanalVO canalVO=new CanalVO();
        canalVO.setSchemaName(schemaName);
        canalVO.setId(id);
        canalVO.setTableName(tableName);
        canalVO.setType(type);
        canalVO.setData(data);
        return canalVO;
    }

    public String getTableName() {
        return tableName;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Map<String, String> getData() {
        return data;
    }

    public void setData(Map<String, String> data) {
        this.data = data;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getSchemaName() {
        return schemaName;
    }

    public void setSchemaName(String schemaName) {
        this.schemaName = schemaName;
    }
}

CondtionVo

@Data
public class ConditionVo {
    private String schemaName;
    /**
     * 监听的表名
     */
    private String tableName;

    /**
     * 数据列ID 若为空则全监听,不为空则指定监听
     */
    private String id;

    /**
     * 监听字段名
     */
    private String fieldName;

    /**
     * 条件
     */
    private Object condition;

    /**
     * 改变的监听字段值
     */
    private String changeFieldValue;

    /**
     * 增:1,删:2,改:3
     */
    private Integer type;

}

启动类 CanalApplication

@EnableCanal标注在启动类上方则会自动启动Canal监听,当然也可以使用CanalUtil工具类,自定义启动Canal监听

@SpringBootApplication
@ComponentScan("com.yyq")
@EnableCanalClient
public class CanalApplication {
    public static void main(String[] args) {
        SpringApplication.run(CanalApplication.class,args);
    }
}

编码到此结束~

引用博客

你可能感兴趣的:(springboot,java,maven,spring,boot,intellij-idea)