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

    图解微信小程序---添加tabBar底部菜单,添加已做好轮播图操作 什么是tabBar? 顶部或者底部tab栏如果小程序是一个多 tab 应用(客户端窗口的底部或顶部有 tab 栏可以切换页面),可以 ...

  2. http响应总结:常见http响应错误总结

    工作中经常会被同事问这个http请求为什么调不通,我虽然能解释清楚错误是什么,但是没有总结过,想到刚开始时,也是看了别人的文章才会的,所以总结一下,贡献一下自己的经验. http 404 响应 404 ...

  3. python程序设计基础(程序设计基础方法)

    python初学者程序练习题 注:练习题涉及到range()函数的使用方法和python绘制,后面会单独发篇解释说明. 1.字符串拼接.接收用户输入的两个字符串,将它们组合后输出 str1=input ...

  4. javascript中的异步操作以及Promise和异步的关系

    https://segmentfault.com/a/1190000004322358 Promise是异步编程的一种解决方案,比传统的解决方案--回调函数和事件--更合理和强大 https://se ...

  5. win10系统本地iis或nginx服务器部署vue.js项目

    1.前端框架一般依赖node.js,我们首先要安装node.js.请参考: http://www.cnblogs.com/wuac/p/6381819.html to:安装好node.js后npm也安 ...

  6. Python Lab Assignments

    引用: https://github.com/saismaran33/Python-Lab-Assignments/wiki/Python-Lab-Assignment-2 Lab 1 对于任何Web ...

  7. 写css注意的事项

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. make CMake 来龙去脉

    理论上说,任意一个C++程序都可以用g++来编译. 大家都知道,写程序大体步骤如下: 1.用编辑器编写源代码,如.c文件. 2.用编译器编译代码生成目标文件,如.o. 3.用链接器连接目标代码生成可执 ...

  9. Python之路(第四十七篇) 协程:greenlet模块\gevent模块\asyncio模块

    一.协程介绍 协程:是单线程下的并发,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的. 协程相比于线程,最大的区别在于 ...

  10. java LinkedHashMap实现LRUCache缓存

    package java_map; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map ...