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

    Sitecore的®体验平台™ 8.2是最全面的更新最新的一个,平衡增强现有客户,而在同一时间提供了引人注目的新功能.你可以在这里阅读新闻稿,但我想对8.2中的一些重大变化给予一些额外的关注.作为奖励 ...

  2. 【杂文】NOIP2018 蒟蒻自闭记

    [杂文]NOIP2018 蒟蒻自闭记 都 \(9102\) 年了,谁还记得 \(2018\) 年的事啊 \(QAQ\) . 还有两个月就要去参加首届 \(CSP\) 了. 想着如果再不记下去年那些事儿 ...

  3. navicat for mongodb12破解

    网上搜了一圈,都不管用.大多都有病毒,最后还是通过搜索github解决问题. 破解文件:https://github.com/DoubleLabyrinth/navicat-keygen/releas ...

  4. MD5加密实现方法

    在这里给大家分享一个超级简单的md5加密实现方法 如下: 引用命名空间 using System.Security.Cryptography; using System.Text; C#代码 publ ...

  5. C# 实体对象作为参数统一去除空格

    /** * ------------------------------------------------------------------------------ * @Copyright in ...

  6. 前端Q的小小小里程碑

    很多关注我的人都不太了解前端Q这个名字的由来,这篇文章就来讲讲前端Q的前世今生,顺便送大大福利(文末有惊喜),哈哈-- 恭喜!前端Q总用户数突破千啦~~ 前端Q前世 前端Q这个公众号,其实很早很早的时 ...

  7. JS 树形结构与数组结构相互转换、在树形结构中查找对象

    总是有很多需求是关于处理树形结构的,所以不得不总结几个常见操作的写法.¯\_(ツ)_/¯ 首先假设有一个树形结构数据如下 var tree=[ { 'id': '1', 'name': '教学素材管理 ...

  8. XSS漏洞初窥(通过dvwa平台进测试)

    xss的全称是:Cross Site Script,中文名叫“跨站脚本攻击”,因为和CSS重名,所以改名XSS.作为一个网站是肯定要和用户有交互的,那么肯定就伴随着信息的输入输出,而利用xss就是通过 ...

  9. Docker 的操作命令记录

    docker ps:列出正在运行的 container docker ps -a:列出所有的 container docker rm [containerid]:移除 container(可并列多个, ...

  10. 尚硅谷MySQL高级学习笔记

    目录 数据库MySQL学习笔记高级篇 写在前面 1. mysql的架构介绍 mysql简介 mysqlLinux版的安装 mysql配置文件 mysql逻辑架构介绍 mysql存储引擎 2. 索引优化 ...