springboot整合canal (完整版)


引入以下代码不会入侵原项目结构


一、gradle(maven自己去仓库取)

    implementation group: 'com.alibaba.otter', name: 'canal.client', version: '1.1.4'
    implementation group: 'org.springframework.boot', name: 'spring-boot-configuration-processor', version: '2.6.3'

二、监听器目录结构

springboot整合canal (完整版)_第1张图片

三、代码

CanlEventListener

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

import java.lang.annotation.*;

/**
 * inject the present class to the spring context
 * as a listener of the canal event
 *
 * @author jigua
 * @date 2018/3/19
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface CanalEventListener {

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

}

DeleteListenPoint

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

import java.lang.annotation.*;

/**
 * ListenPoint for delete
 *
 * @author jigua
 * @date 2018/3/19
 */

@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.dfjs.canal.config.CanalClientConfiguration;
import com.dfjs.canal.config.CanalConfig;
import com.dfjs.canal.config.CanalClientConfiguration;
import com.dfjs.canal.config.CanalConfig;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * Enables the canal client
 *
 * @author jigua
 * @date 2018/3/19
 */

@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.*;

/**
 * ListenPoint for insert
 *
 * @author jigua
 * @date 2018/3/19
 */

@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 {};

}

ListenPoint

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

import java.lang.annotation.*;

/**
 * used to indicate that method(or methods) is(are) the candidate of the
 * canal event distributor
 *
 * @author jigua
 * @date 2018/3/19
 */

@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 {};

}

UpdateListenPoint

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

import java.lang.annotation.*;

/**
 * ListenPoint for update
 *
 * @author jigua
 * @date 2018/3/19
 */

@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 {};

}

CanalClientException

/**
 * @author jigua
 * @date 2018/3/16
 */
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);
    }
}

AbstractBasicMessageTransponder

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

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

/**
 * @author jigua
 * @date 2018/4/2
 */
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(), rowData);
                //distribute to annotation listener interfaces
                distributeByAnnotation(destination,
                        entry.getHeader().getSchemaName(),
                        entry.getHeader().getTableName(),
                        rowChange.getEventType(),
                        rowData);
            }
        }
    }

    /**
     * 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.RowData 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.RowData 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);
                        method.invoke(point.getTarget(), args);
                    } 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.RowData 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.dfjs.canal.client.ListenerPoint;
import com.dfjs.canal.config.CanalConfig;
import com.dfjs.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;

/**
 * Abstract implements of the MessageTransponder interface.
 *
 * @author jigua
 * @date 2018/3/19
 */
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();
                //empty message
                if (batchId == -1 || size == 0) {
                    Thread.sleep(interval);
                } else {
                    distributeEvent(message);
                }
                // commit ack
                connector.ack(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.dfjs.canal.annotation.ListenPoint;
import com.dfjs.canal.client.ListenerPoint;
import com.dfjs.canal.config.CanalConfig;
import com.dfjs.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;

/**
 * @author jigua
 * @date 2018/3/23
 */
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.RowData rowData) {
        return Arrays.stream(method.getParameterTypes())
                .map(p -> p == CanalEntry.EventType.class ? eventType : p == CanalEntry.RowData.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.dfjs.canal.client.ListenerPoint;
import com.dfjs.canal.config.CanalConfig;
import com.dfjs.canal.event.CanalEventListener;

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

/**
 * @author jigua
 * @date 2018/3/23
 */
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

/**
 * Interface that used to transfer the message from the canal server
 *
 * @author jigua
 * @date 2018/3/19
 */
public interface MessageTransponder extends Runnable {
}

MessageTransponders

/**
 * @author jigua
 * @date 2018/3/23
 */
public class MessageTransponders {

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

}

TransponderFactory

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

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

/**
 * TransponderFactory
 *
 * @author jigua
 * @date 2018/3/23
 */
public interface TransponderFactory {

    /**
     * @param connector connector
     * @param config config
     * @param listeners listeners
     * @param annoListeners annoListeners
     * @return MessageTransponder
     */
    MessageTransponder newTransponder(CanalConnector connector, Map.Entry<String, CanalConfig.Instance> config, List<CanalEventListener> listeners,
                                      List<ListenerPoint> annoListeners);
}

AbstractCanalClient

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.dfjs.canal.client.exception.CanalClientException;
import com.dfjs.canal.client.transfer.TransponderFactory;
import com.dfjs.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;

/**
 * Abstract implements of the CanalClient interface
 * It help to initialize the canal connector and so on..
 *
 * @author jigua
 */
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 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

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

/**
 * CanalClient interface
 *
 * @authorjigua
 * @date 2018/3/16
 */
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.dfjs.canal.annotation.ListenPoint;

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

/**
 * ListenerPoint
 * save the information of listener's method-info
 *
 * @author jigua
 * @date 2018/3/23
 */
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.dfjs.canal.annotation.ListenPoint;
import com.dfjs.canal.client.transfer.TransponderFactory;
import com.dfjs.canal.config.CanalConfig;
import com.dfjs.canal.event.CanalEventListener;
import com.dfjs.canal.util.BeanUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;

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;

/**
 * SimpleCanalClient
 * use cache thread pool to handle the event
 *
 * @author jigua
 * @date 2018/3/16
 */
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.dfjs.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) {
                        //只能解析到ListenPoint
//                        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());
        }
    }
}

CanalClientConfiguration

import com.dfjs.canal.client.CanalClient;
import com.dfjs.canal.client.SimpleCanalClient;
import com.dfjs.canal.client.transfer.MessageTransponders;
import com.dfjs.canal.util.BeanUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;


/**
 * @author jigua
 * @date 2018/3/19
 */
@Service
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() {
        CanalClient canalClient = new SimpleCanalClient(canalConfig, MessageTransponders.defaultMessageTransponder());
        canalClient.start();
        logger.info("Starting canal client....");
        return canalClient;
    }
}

CanalConfig

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;

/**
 * @author jigua
 * @date 2018/3/16
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
@ConfigurationProperties(prefix = "canal.client")
@Component
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 = 10001;

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

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

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

        /**
         * filter
         */
        private String filter;

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

        /**
         * 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;
        }
    }

}

CanalEventListener

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

/**
 * @author jigua
 * @date 2018/3/19
 */
public interface CanalEventListener {

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

}

DmlCanalEventListener

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

import java.util.Objects;

/**
 * DmlCanalEventListener
 *
 * @author jigua
 * @date 2018/3/19
 */
public interface DmlCanalEventListener extends CanalEventListener {

    @Override
    default void onEvent(CanalEntry.EventType eventType, CanalEntry.RowData 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.RowData rowData);

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

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

}

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;

/**
 * @author jigua
 * @date 2018/3/16
 */
@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;
    }
}

CanalDataEventListener

import com.alibaba.otter.canal.protocol.CanalEntry;
import com.dfjs.bean.BaseConfig;
import com.dfjs.canal.annotation.*;
import com.dfjs.constant.LawyerConstants;
import com.dfjs.dao.LawyerDao;
import com.dfjs.dao.UserDao;
import com.dfjs.entity.Lawyer;
import com.dfjs.entity.User;
import com.dfjs.enums.AddressEnum;
import com.dfjs.service.DataChangeMonitorService;
import com.dfjs.service.RedisLawyerInfoService;
import com.dfjs.service.RedisService;
import com.dfjs.service.UserService;
import io.swagger.models.auth.In;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

/**
 * @author jigua
 * @version 1.0
 * @className CanalDataEventListener
 * @description
 * @create 2022/3/1 18:40
 */
@CanalEventListener
public class CanalDataEventListener {
	/**
	 * 数据增量同步
	 * 实现业务逻辑
	 */
    @UpdateListenPoint
    public void test(CanalEntry.EventType eventType, CanalEntry.RowData rowData){
        //数据变更前
        List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
        for (CanalEntry.Column column : beforeColumnsList) {
            System.out.println(column.getName() + ":" + column.getValue());
        }
        //数据变更后
        List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
        for (CanalEntry.Column column : afterColumnsList) {
            System.out.println(column.getName() + ":" + column.getValue());
        }
    }

    @InsertListenPoint
    public void test1(CanalEntry.EventType eventType, CanalEntry.RowData rowData){
        //数据变更前
        List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
        for (CanalEntry.Column column : beforeColumnsList) {
            System.out.println(column.getName() + ":" + column.getValue());
        }
    }

    @DeleteListenPoint
    public void test2(CanalEntry.EventType eventType, CanalEntry.RowData rowData){
        //数据变更前
        List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
        for (CanalEntry.Column column : beforeColumnsList) {
            System.out.println(column.getName() + ":" + column.getValue());
        }
    }
    
    /**
     * destination 指定监听实例
     * schema	指定监听数据库名称 
     * table	指定表名 {"user"} || {"user","user_info"}
     * eventType 指定监听CURD类型
	 */
    @ListenPoint(destination = "example", schema = "数据库名", table = {"表名1","表名2"}
    , eventType = {CanalEntry.EventType.INSERT,CanalEntry.EventType.UPDATE})
    public void test3(CanalEntry.EventType eventType, CanalEntry.RowData rowData){
		
    }

}

四、启动类添加注解

在这里插入图片描述

五、yml配置文件

springboot整合canal (完整版)_第2张图片

你可能感兴趣的:(spring,springboot,java)