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

    这是松哥之前一个零散的笔记,整理出来分享给大伙! MySQL 读写分离在互联网项目中应该算是一个非常常见的需求了.受困于 Linux 和 MySQL 版本问题,很多人经常会搭建失败,今天松哥就给大伙举 ...

  2. lombok的@Accessors注解3个属性说明

    https://www.cnblogs.com/kelelipeng/p/11326936.html https://www.cnblogs.com/kelelipeng/p/11326621.htm ...

  3. 有关Oracle 查询时间的记录 (1)

    目录 写在前面 一.年.月.日.季.周 二.EXTRACT 年.月.日 三.上个星期一到星期天 四.1分钟前.1小时前.1月前.1年前 五.当月.上月.当天.前天 写在前面 在使用Oracle数据开发 ...

  4. 华为方舟编译器 下载 和 LiteOS Studio Setup 2019-04-16.exe SDK下载

    华为方舟编译器是首个取代Android虚拟机模式的静态编译器,可供开发者在开发环境中一次性将高级语言编译为机器码.此外,方舟编译器未来将支持多语言统一编译,可大幅提高开发效率. 编译器下载 [Ark] ...

  5. 面试官:你知道Spring中有哪些可以让我们扩展的地方么

    大家都知道我这段时间陆续更新了Spring系列源码分析以及各种扩展点的文章,到了今天可以总算可以更新这篇文章了 首先列举一下一个经典的面试题:Spring中Bean的生命周期: 开始初始化容器 加载B ...

  6. TinyMCE常用插件

    Advanced Tables 基于table插件的增强表格插件,添加了排序功能. tinymce.init({ plugins: 'table advtable', menubar: 'table' ...

  7. Vue.js最佳实践--VueRouter的beforeEnter与beforeRouteLeave冲突解决

    用Vue做应用管理系统,通常会在离开某个页面的时候,需要检测用户是否有修改,询问用户需要不需要保存之类的需求 这时候,在读VueRouter文档:组件内的守卫 的时候,发现beforeRouteLea ...

  8. vue+element拖动排序功能

    项目中老大心血来潮设计了一可以拖动达到排序的功能,感觉没什么用,但是没办法,实现吧! 这功能肯定不会手撸了,直接上插件 使用Sortable.js,对vue不友好,拖拽有时候乱跳;改用vuedragg ...

  9. javascript中事件总结&通用的事件侦听器函数封装&事件委托

    前言: JAVASCRIPT与HTML之间的交互是通过事件来实现的.事件,就是文档或浏览器窗口中发生的一些特定交互瞬间.可以使用侦听器( 或处理程序 )来预定事件,以便事件发生时执行相应的代码.这种在 ...

  10. Nginx+Tomcat实现动静分离和负载均衡

    一.什么是动静分离? Nginx动静分离简单来说就是把动态和静态请求分开,不能理解成只是单纯的把动态页面和静态页面物理分离.严格意义上说应该是将动态请求和静态请求分开,可以理解成使用Nginx处理静态 ...