在项目迭代开发中经常会遇到对已有功能的改造需求,尽管我们可能已经预留了扩展点,并且尝试通过接口或扩展类完成此类任务。可是,仍然有很多难以预料的场景无法通过上述方式解决。修改原有代码当然能够做到,但是这会增加许多附加成本,回归测试带来大量工作和一些潜在的未知风险。特别是一些极其重要的公共模块,可谓牵一发而动全身,稍有不慎都将引发重大的故障。这里分享一下自己在项目开发中的一点实践,一种基于AOP的插件化(扩展)方案。

假设一个场景:

现有一个可获取人员信息的服务:UserService。

public class UserService {

    public User getUserById(String id) {
// ...
}
}

该服务作为一个基础服务一直运行良好,但是客户出于对数据安全的考虑需要对人员信息中某些属性进行脱敏。该需求总体来说不是很复杂,解决方案也不止一个,那么来看看基于AOP的实现方式是怎么样的。

插件点注解:

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface PluginPoint { Class<?> service(); String method(); PluginType type(); enum PluginType {
BEFORE, AFTER, OVERRIDE;
}
}

插件服务接口(后期所有的插件都必须实现这个接口):

public interface PluginService {

    Object process(Object[] args, Object result) throws Exception;
}

PluginServiceHolder.java:

public class PluginServiceHolder {
private PluginService before; private PluginService override; private PluginService after; public PluginService getBefore() {
return before;
} public void setBefore(PluginService before) {
this.before = before;
} public boolean hasBefore() {
return before != null;
} public void doBefore(Object[] args) throws Exception {
before.process(args, null);
} public PluginService getOverride() {
return override;
} public void setOverride(PluginService override) {
this.override = override;
} public boolean hasOverride() {
return override != null;
} public Object doOverride(Object[] args) throws Exception {
return override.process(args, null);
} public PluginService getAfter() {
return after;
} public void setAfter(PluginService after) {
this.after = after;
} public boolean hasAfter() {
return after != null;
} public Object doAfter(Object[] args, Object result) throws Exception {
return after.process(args, result);
}
}

PluginServiceAspect:

@Aspect
@Component
public class PluginServiceAspect {
private static Logger logger = LoggerFactory.getLogger(PluginServiceAspect.class);
private static ConcurrentHashMap<String, PluginServiceHolder> pluginConfigMap = new ConcurrentHashMap<String, PluginServiceHolder>();
private static volatile Boolean initStatus = false; @Pointcut("execution(* com.kongdl.demo..service..*Service.*(..))")
public void service() {
} @Pointcut("execution(* com.kongdl.demo..web..*Action.*(..))")
public void action() {
} @Around("service() || action()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
PluginServiceHolder pluginServiceHolder = getPluginService(pjp);
if (pluginServiceHolder == null) {
return pjp.proceed();
} Object result = null;
Object[] args = pjp.getArgs();
// before
if (pluginServiceHolder.hasBefore()) {
pluginServiceHolder.doBefore(args);
}
// override
if (pluginServiceHolder.hasOverride()) {
result = pluginServiceHolder.doOverride(args);
} else {
result = pjp.proceed(args);
}
// after
if (pluginServiceHolder.hasAfter()) {
result = pluginServiceHolder.doAfter(args, result);
}
return result;
} private PluginServiceHolder getPluginService(ProceedingJoinPoint pjp) {
initPluginConfigMap(); Signature signature = pjp.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
String serviceMethod = className + "#" + methodName;
return pluginConfigMap.get(serviceMethod);
} private void loadPluginService() {
Map<String, Object> plugins = SpringContextHolder.getBeansWithAnnotation(PluginPoint.class);
if (plugins == null && plugins.size() == 0) { // no plugins
return;
}
for (Object value : plugins.values()) {
PluginService pluginService = (PluginService) value;
PluginPoint pluginPoint = pluginService.getClass().getAnnotation(PluginPoint.class);
Class<?> service = pluginPoint.service();
String method = pluginPoint.method();
String serviceMethod = service.getName() + "#" + method;
PluginServiceHolder pluginServiceHolder;
if (pluginConfigMap.containsKey(serviceMethod)) {
pluginServiceHolder = pluginConfigMap.get(serviceMethod);
} else {
pluginServiceHolder = new PluginServiceHolder();
}
if (pluginPoint.type() == PluginType.BEFORE) {
pluginServiceHolder.setBefore(pluginService);
} else if (pluginPoint.type() == PluginType.OVERRIDE) {
pluginServiceHolder.setOverride(pluginService);
} else if (pluginPoint.type() == PluginType.AFTER) {
pluginServiceHolder.setAfter(pluginService);
}
pluginConfigMap.put(serviceMethod, pluginServiceHolder);
}
logger.info("initialize plugin success");
} private void initPluginConfigMap() {
if (initStatus == false) {
synchronized (this) {
if (initStatus == false) {
loadPluginService();
initStatus = true;
}
}
}
}
}

当然还有最重要的插件实现类:

@PluginPoint(service = UserService.class, method = "getUserById", type=PluginType.AFTER)
public class UserServicePlugin implements PluginService { @Override
public Object process(Object[] args, Object result) throws Exception {
User user = (User) result;
// 增加脱敏的业务逻辑
desensitive(user);
return user;
} private void desensitive(User user) {
//TODO ...
}
}

至此我们的工作基本就算完成了,启动项目时只要UserServicePlugin被加载到,那么再次调用getUserById方法时,返回的就是脱敏后的人员信息了。

一点总结:

软件开发过程中需求难免会发生变化,今日精巧的设计,明天就可能变成拦路的淤泥。

基于AOP的插件化(扩展)方案的更多相关文章

  1. Android 插件化开发(四):插件化实现方案

    在经过上面铺垫后,我们可以尝试整体实现一下插件化了.这里我们先介绍一下最简单的实现插件化的方案. 一.最简单的插件化实现方案 最简单的插件化实现方案,对四大组件都是适用的,技术面涉及如下: 1). 合 ...

  2. Android基于代理的插件化思路分析

    前言 正常的App开发流程基本上是这样的:开发功能-->测试--->上线,上线后发现有大bug,紧急修复---->发新版本---->用户更新----->bug修复.从发现 ...

  3. 基于Fragment的插件化

    --<摘自android插件化开发指南> 1.有些项目,整个app只有一个Activity,切换页面全靠Fragment,盛行过一时,但有点极端 2.Activity切换fragment页 ...

  4. android 基于dex的插件化开发

    安卓里边可以用DexClassLoader实现动态加载dex文件,通过访问dex文件访问dex中封装的方法,如果dex文件本身还调用了native方法,也就间接实现了runtime调用native方法 ...

  5. WebApi 插件式构建方案

    WebApi 插件式构建方案 WebApi 插件式构建方案 公司要推行服务化,不可能都整合在一个解决方案内,因而想到了插件式的构建方案.最终定型选择基于 WebApi 构建服务化,之所以不使用 WCF ...

  6. Android插件化-RePlugin项目集成与使用

    前言:前一段时间新开源了一种全面插件化的方案-- RePlugin,之前一种都在关注 DroidPlugin 并且很早也在项目中试用了,但最终没有投入到真正的生产环节,一方面是项目中没有特别需要插件化 ...

  7. Android Small插件化框架源码分析

    Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github ...

  8. JavaScript插件化开发

    大熊君JavaScript插件化开发 一,开篇分析 Hi,大家好!大熊君又和大家见面了,还记得昨天的那篇文章吗------这个系列的开篇(第一季).主要讲述了以“jQuery的方式如何开发插件”, 那 ...

  9. 热门前沿知识相关面试问题-android插件化面试问题讲解

    插件化由来: 65536/64K[技术层面上]随着代码越来越大,业务逻辑越来繁杂,所以很容易达到一个65536的天花板,其65536指的是整个项目中的方法总数如果达到这个数量时则不无法创建新的方法了, ...

随机推荐

  1. nginx产生【413 request entity too large】错误的原因与解决方法

    项目上在做上传文件(清单导入)的时候产生了这个错误: 从字面上看,说的是请求的实体太大的问题,那么可以联想到是HTTP请求中的Body大小被限制了的原因. Nginx中的[client_max_bod ...

  2. GT性能测试Android版使用说明

    1 GT简介 GT(随身调) Android版是腾讯 MIG 专项测试组自行研发的 Android APP 随身调测平台,它是直接运行在手机上的“集成调测环境”(ITE, Integrated Tes ...

  3. 【02】Kubernets:使用 kubeadm 部署 K8S 集群

    写在前面的话 通过上一节,知道了 K8S 有 Master / Node 组成,但是具体怎么个组成法,就是这一节具体谈的内容.概念性的东西我们会尽量以实验的形式将其复现. 部署 K8S 集群 互联网常 ...

  4. 关于 部署方面研究 Visual Studio 2013

    InstallShield2013LimitedEdition 下载地址 http://download.csdn.net/download/yuanyuan_186/7681385

  5. windows 10 配置Java 环境变量(5步骤)

    前提:1.windows 10 系统(不是win8,也不是win7)2.安装JDK步骤 1. 打开 环境变量窗口右键 This PC(此电脑) -> Properties(属性) -> A ...

  6. ASP.NET MVC IOC 之 Autofac(二)

    在上一章节,我们已经知道了再控制器中如何注入以及使用了.这一章,我们重点讲解下,如何在服务层中使用. 我们新定义一个教师类,在服务层中,通过这个教师类服务层,获取学生的年龄.实现在教师类的服务层中调用 ...

  7. javascript DOM拓展

    针对JS高级程序设计这本书,主要是理解概念,大部分要点源自书内.写这个主要是当个笔记加总结 存在的问题请大家多多指正! DOM拓展 1选择符 API 1.1 querySelector() 由docu ...

  8. SSIS-WMI监视文件夹

    在文档交互数据时,通常会排个job每隔几分钟执行来解析文档,但是jOb不能排的太频繁了,所以文档不能及时的被解析. 在SSIS中可以使用WMI这个组件来监视文件夹,一旦有新文档丢入就马上执行解析程序, ...

  9. Visual Studio调试到OpenCV源码中

    TL;DR VS2015下,build-farm/vs2015-x64/bin/Debug/目录,*.pdb文件,都拷贝到install/x64/vc14/bin目录,就可以调试进去opencv源码了 ...

  10. 并发编程(六)--进程/线程池、协程、gevent第三方库

    一.进程/线程池 1.进程池 (1)什么是进程池 如果需要创建的子进程数量不大,可以直接利用multiprocess中的Process来创建.但是当需要创建上百个或上千个,手动创建就较为繁琐,这时就可 ...