Canal消息转JavaBean实现参考

Canal消息转JavaBean实现参考 - 简书

参考 Canal消息转JavaBean实现参考

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;

import org.apache.commons.lang.StringUtils;
import org.assertj.core.util.Lists;
import org.springframework.cglib.core.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.CollectionUtils;

import com.alibaba.otter.canal.protocol.*;
import com.google.common.collect.Maps;

public abstract class AbstractCanalLogMsgProcessor {
private DefaultConversionService conversionService = new DefaultConversionService() {
        {
            addConverter(new Converter<String, Date>() {
                @Override
                public Date convert(String source) {
                    if (StringUtils.isBlank(source)) {
                        return null;
                    }
                    return DateUtils.convertStringToDate(source);
                }
            });
        }
    };

    private ConcurrentHashMap<String, Map<String, Field>> cachedClzFields = new ConcurrentHashMap<>();


    /**
     * 获取改变<b>前后</b>的数据,将canal消息数据转为bean(只赋值private、public、protected属性,不赋值static、final等其他属性)
     * 注意:属性名不能包含特殊字符
     *
     * @param rowChange
     * @param clz
     * @return
     * @throws IllegalAccessException 参数为空时会抛异常
     * @throws InstantiationException
     */
    public <T> List<RowDataPair<T>> getChanges(CanalEntry.RowChange rowChange, Class<T> clz) throws InstantiationException, IllegalAccessException {
        if (rowChange == null || clz == null || rowChange.getRowDatasList() == null) {
            throw new IllegalArgumentException("rowChange or clz can't be empty.");
        }
        Map<String, Field> beanFields = getClzFields(clz);
        List<RowDataPair<T>> result = Lists.newArrayList();
        for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
            T dataBefore = convertRowData(rowData.getBeforeColumnsList(), beanFields, clz);
            T dataAfter = convertRowData(rowData.getAfterColumnsList(), beanFields, clz);
            result.add(new RowDataPair<>(dataBefore, dataAfter));
        }
        return result;
    }

    /**
     * 获取改变<b>前</b>的数据,将canal消息数据转为bean(只赋值private、public、protected属性,不赋值static、final等其他属性)
     * 注意:属性名不能包含特殊字符
     *
     * @param rowChange
     * @param clz
     * @return
     * @throws IllegalAccessException 参数为空时会抛异常
     * @throws InstantiationException
     */
    public <T> List<T> getChangesBefore(CanalEntry.RowChange rowChange, Class<T> clz) throws IllegalAccessException, InstantiationException {
        return getChangesBeforeOrAfter(rowChange, clz, true);
    }

    /**
     * 获取改变<b>后</b>的数据,将canal消息数据转为bean(只赋值private、public、protected属性,不赋值static、final等其他属性)
     * 注意:属性名不能包含特殊字符
     *
     * @param rowChange
     * @param clz
     * @return
     * @throws IllegalAccessException 参数为空时会抛异常
     * @throws InstantiationException
     */
    public <T> List<T> getChangesAfter(CanalEntry.RowChange rowChange, Class<T> clz) throws IllegalAccessException, InstantiationException {
        return getChangesBeforeOrAfter(rowChange, clz, false);
    }

    /**
     * bean初始化完成后注册需要的转换器,已默认添加Date(格式:yyyy-MM-dd HH:mm:ss)转换器
     * 在方法体内,如下使用:
     * <pre>
     *    addConverter(new Converter<String, Date>() {
     *
     *    });
     * </pre>
     */
    @PostConstruct
    protected void registerConverters() {

    }

    /**
     * 给class对应field设置别名
     */
    @PostConstruct
    protected void aliasClzFields() {

    }

    /**
     * 给field名称设置别名,将忽略大小写并去除下划线
     * 不建议业务逻辑中设置别名,请重写aliasClzFields方法
     * <pre>
     * 如:canal msg:  {“birth_day”:"1970-01-01 00:00:00"}
     *    bean中属性为 birth
     *    那么 aliasField(Bean.class, birth, birth_day) 或者 aliasField(Bean.class, birth, birthday)等
     * </pre>
     *
     * @param clz
     * @param originName
     * @param aliasName
     * @param <T>
     */
    protected <T> void aliasField(Class<T> clz, String originName, String aliasName) {
        Map<String, Field> clzFields = getClzFields(clz);
        aliasName = aliasName.toLowerCase().replace("_", "");
        clzFields.put(aliasName, clzFields.get(originName.toLowerCase()));
    }

    /**
     * 注册自定义配置
     * 不可直接调用,请重写registerConverters()
     *
     * @param converter
     * @see com.tqmall.lsc.mq_canallog.impl.AbstractCanalLogMsgProcessor#registerConverters()
     */
    protected void addConverter(Converter converter) {
        conversionService.addConverter(converter);
    }

    private <T> List<T> getChangesBeforeOrAfter(CanalEntry.RowChange rowChange, Class<T> clz, boolean isBefore) throws InstantiationException, IllegalAccessException {
        if (rowChange == null || clz == null || rowChange.getRowDatasList() == null) {
            throw new IllegalArgumentException("rowChange or clz can't be empty.");
        }
        Map<String, Field> beanFields = getClzFields(clz);
        List<T> result = Lists.newArrayList();
        for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
            List<CanalEntry.Column> columnsList = isBefore ? rowData.getBeforeColumnsList() : rowData.getAfterColumnsList();
            T data = convertRowData(columnsList, beanFields, clz);
            result.add(data);
        }
        return result;
    }

    private <T> Map<String, Field> getClzFields(Class<T> clz) {
        Map<String, Field> beanFields = cachedClzFields.get(clz.getName());
        if (beanFields == null || beanFields.size() <= 0) {
            beanFields = getAllFieldsForBean(clz);
            cachedClzFields.putIfAbsent(clz.getName(), beanFields);
            beanFields = cachedClzFields.get(clz.getName());
        }
        return beanFields;
    }

    private <T> T convertRowData(List<CanalEntry.Column> cols, Map<String, Field> beanFields, Class<T> clz) throws IllegalAccessException, InstantiationException {
        if (CollectionUtils.isEmpty(cols)) {
            return null;
        }
        T bean = clz.newInstance();
        for (CanalEntry.Column col : cols) {
            String name = col.getName().toLowerCase().replace("_", "");
            String value = col.getValue();
            Field field = beanFields.get(name);
            if (field == null) {
                continue;
            }
            field.set(bean, value == null ? null : conversionService.convert(value, field.getType()));
        }
        return bean;
    }

    private <T> Map<String, Field> getAllFieldsForBean(Class<T> clz) {
        Map<String, Field> result = Maps.newHashMap();
        Class tmpClz = clz;
        // 不获取Object层的属性
        String finalParent = "java.lang.object";
        while (tmpClz != null && !tmpClz.getName().toLowerCase().equals(finalParent)) {
            // 只获取bean普通属性
            for (Field field : tmpClz.getDeclaredFields()) {
                // 不在设置数据时设置访问权限
                field.setAccessible(true);
                int modifiers = field.getModifiers();
                if (modifiers == Modifier.PUBLIC || modifiers == Modifier.PRIVATE || modifiers == Modifier.PROTECTED) {
                    result.put(field.getName().toLowerCase(), field);
                }
            }
            tmpClz = tmpClz.getSuperclass();
        }
        return result;
    }


    public static class RowDataPair<T> {
        private T before;
        private T after;

        public T getBefore() {
            return before;
        }

        public void setBefore(T before) {
            this.before = before;
        }

        public T getAfter() {
            return after;
        }

        public void setAfter(T after) {
            this.after = after;
        }

        public RowDataPair(T before, T after) {
            this.before = before;
            this.after = after;
        }
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值