Spring類掃描+fastJson實(shí)現(xiàn)返回數(shù)據(jù)處理

我們在開發(fā)中, 會經(jīng)常需要需要對返回數(shù)據(jù)做處理的情況, 比如數(shù)據(jù)脫敏, 字段轉(zhuǎn)換等
由于項(xiàng)目使用的序列化工具為fastJson, 固可以使用fastJson的SerializeFilter接口來實(shí)現(xiàn)返回時的數(shù)據(jù)修改
配合自定義的注解, 可以實(shí)現(xiàn)對制定字段的處理
為了提高代碼效率, 使用Spring的自定義類掃描器在項(xiàng)目啟動時對所有實(shí)體掃描, 并緩存需要處理的field

定義注解
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CovertUser {
    String source() default "";
}
定義處理器接口
public interface IAkScannerConsumer {
    /**
     * 包掃描回調(diào)接口
     *
     * @param clazz      Class定義
     * @param name       屬性名
     * @param annotation 注解
     */
    void accept(Class<?> clazz, String name, Annotation annotation);
}
定義注解處理類
public class CoverUserScannerConsumer implements IAkScannerConsumer {
    @Override
    public void accept(Class<?> clazz, String name, Annotation annotation) {
        String key = clazz.getName() + ":" + name;
        JsonMessageConverterConfig.CACHE.put(key, (CovertUser) annotation);
    }
}
定義啟動類注解

啟動類注解用于配置掃描的包和注解, 并設(shè)置對應(yīng)的處理類

@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AkScannerConfig.class)
public @interface AkScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    /**
     * 掃描注解配置
     */
    Matcher[] matches() default {};

    @interface Matcher {
        /**
         * 需要掃描的注解
         */
        @AliasFor("types")
        Class<?>[] value() default {};

        /**
         * 需要掃描的注解
         */
        @AliasFor("value")
        Class<?>[] types() default {};

        /**
         * 注解處理類
         */
        Class<? extends IAkScannerConsumer> consumer();
    }
}
定義類掃描器
@Slf4j
public class AkScannerConfig implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanFactoryAware {
    private ResourceLoader resourceLoader;
    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(@NonNull AnnotationMetadata annotationMetadata, @NonNull BeanDefinitionRegistry registry) {
        Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(AkScan.class.getName());
        if (Objects.isNull(attributes)) {
            log.warn("get AkScan attributes failed");
            return;
        }
        // 讀取AkScan的配置
        String[] basePackages = (String[]) attributes.get("basePackages");
        AnnotationAttributes[] matches = (AnnotationAttributes[]) attributes.get("matches");
        if (ArrayUtil.isEmpty(matches)) {
            log.warn("scanner types is empty");
            return;
        }
        Map<Class<?>, IAkScannerConsumer> cache = Maps.newHashMap();
        for (AnnotationAttributes match : matches) {
            Class<?>[] types = match.getClassArray("types");
            Class<?> consumer = match.getClass("consumer");
            for (Class<?> type : types) {
                if (!registry.containsBeanDefinition(consumer.getName())) {
                    // 初始化對應(yīng)的處理器
                    RootBeanDefinition beanDefinition = new RootBeanDefinition(consumer);
                    registry.registerBeanDefinition(consumer.getName(), beanDefinition);
                }
                IAkScannerConsumer bean = (IAkScannerConsumer) beanFactory.getBean(consumer);
                cache.put(type, bean);
            }
        }
        AkClassPathBeanDefinitionScanner scanner = new AkClassPathBeanDefinitionScanner(registry, cache);
        scanner.setResourceLoader(resourceLoader);
        scanner.scan(basePackages);
    }

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

    @Override
    public void setBeanFactory(@NonNull BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    // 掃描器的實(shí)現(xiàn)
    public static class AkClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
        private final Map<Class<?>, IAkScannerConsumer> cache;
        private final Set<Class<?>> scannerTypes;

        public AkClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, Map<Class<?>, IAkScannerConsumer> cache) {
            super(registry, false);
            scannerTypes = cache.keySet();
            this.cache = cache;
        }

        @NonNull
        @Override
        protected Set<BeanDefinitionHolder> doScan(@NonNull String... basePackages) {
            // 掃描包下所有的類
            addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
            return super.doScan(basePackages);
        }

        @Override
        protected void postProcessBeanDefinition(@NonNull AbstractBeanDefinition beanDefinition, @NonNull String beanName) {
            super.postProcessBeanDefinition(beanDefinition, beanName);
            String className = beanDefinition.getBeanClassName();
            Class<?> clazz;
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
                log.error("load class [" + className + "] error");
                return;
            }
            // 獲取所有的類屬性
            List<Field> fields = BeanUtil.getAllFields(clazz, false);
            for (Field field : fields) {
                Annotation[] annotations = field.getAnnotations();
                if (ArrayUtil.isEmpty(annotations)) {
                    continue;
                }
                for (Annotation annotation : annotations) {
                    Class<? extends Annotation> a = annotation.annotationType();
                    if (!scannerTypes.contains(a)) {
                        continue;
                    }
                    IAkScannerConsumer consumer = cache.get(a);
                    consumer.accept(clazz, field.getName(), annotation);
                }
            }
        }

        @Override
        protected void registerBeanDefinition(@NonNull BeanDefinitionHolder definitionHolder, @NonNull BeanDefinitionRegistry registry) {
            // 禁止將掃描到的類注冊到IOC容器中
        }
    }
}
啟動類添加掃描注解
@AkScan(basePackages = "com.ak.model", matches = {
        @AkScan.Matcher(types = CovertUser.class, consumer = CoverUserScannerConsumer.class)
})
@SpringBootApplication
public class ServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceApplication.class);
    }
}
自定義fastJson Filter
@Slf4j
@RequiredArgsConstructor
public class UserCovertSerializer implements ValueFilter {
    private final IUserService userService;

    @Override
    public Object process(Object object, String name, Object value) {
        Class<?> clazz = object.getClass();
        String key = clazz.getName() + ":" + name;
        CovertUser annotation = CACHE.get(key);
        if (Objects.isNull(annotation)) {
            return value;
        }
        try {
            String source = annotation.source();
            if (StrUtil.isBlank(source)) {
                source = name;
            }
            Field field = ReflectionUtils.findField(clazz, source);
            if (Objects.isNull(field)) {
                log.warn("no field name is " + source);
                return value;
            }
            field.setAccessible(true);
            Object account = field.get(object);
            return userService.getNameByAccount(String.valueOf(account));
        } catch (IllegalAccessException e) {
            log.error("convert userName error", e);
            return value;
        }
    }
}
注冊filter到fastJson中
public class JsonMessageConverterConfig implements WebMvcConfigurer {
    private final IUserService userService;
    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
    private String dateFormat;

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
                SerializerFeature.WriteNullStringAsEmpty,
                SerializerFeature.WriteNullListAsEmpty,
                SerializerFeature.WriteMapNullValue,
                SerializerFeature.DisableCircularReferenceDetect,
                SerializerFeature.WriteEnumUsingToString);
        fastJsonConfig.setCharset(StandardCharsets.UTF_8);
        fastJsonConfig.setDateFormat(dateFormat);
        fastJsonConfig.setSerializeFilters(new UserCovertSerializer(userService));
        fastConverter.setFastJsonConfig(fastJsonConfig);
        converters.add(4, fastConverter);
    }
}

如果有多個注解需要處理, 修改AkScan的配置即可

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腹纳,一起剝皮案震驚了整個濱河市言蛇,隨后出現(xiàn)的幾起案子史侣,更是在濱河造成了極大的恐慌返敬,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谴分,死亡現(xiàn)場離奇詭異鸠天,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)手形,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門啥供,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人库糠,你說我怎么就攤上這事伙狐。” “怎么了瞬欧?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵贷屎,是天一觀的道長。 經(jīng)常有香客問我艘虎,道長唉侄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任野建,我火速辦了婚禮属划,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘候生。我一直安慰自己同眯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布唯鸭。 她就那樣靜靜地躺著嗽测,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唠粥,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天疏魏,我揣著相機(jī)與錄音,去河邊找鬼晤愧。 笑死大莫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的官份。 我是一名探鬼主播只厘,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼舅巷!你這毒婦竟也來了羔味?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤钠右,失蹤者是張志新(化名)和其女友劉穎赋元,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體飒房,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡搁凸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了狠毯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片护糖。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖嚼松,靈堂內(nèi)的尸體忽然破棺而出嫡良,到底是詐尸還是另有隱情,我是刑警寧澤献酗,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布寝受,位于F島的核電站,受9級特大地震影響凌摄,放射性物質(zhì)發(fā)生泄漏羡蛾。R本人自食惡果不足惜漓帅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一锨亏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧忙干,春花似錦器予、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春反浓,著一層夾襖步出監(jiān)牢的瞬間萌丈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工雷则, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辆雾,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓月劈,卻偏偏與公主長得像度迂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子猜揪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容