基于AOP的插件化(扩展)方案
在项目迭代开发中经常会遇到对已有功能的改造需求,尽管我们可能已经预留了扩展点,并且尝试通过接口或扩展类完成此类任务。可是,仍然有很多难以预料的场景无法通过上述方式解决。修改原有代码当然能够做到,但是这会增加许多附加成本,回归测试带来大量工作和一些潜在的未知风险。特别是一些极其重要的公共模块,可谓牵一发而动全身,稍有不慎都将引发重大的故障。这里分享一下自己在项目开发中的一点实践,一种基于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的插件化(扩展)方案的更多相关文章
- Android 插件化开发(四):插件化实现方案
在经过上面铺垫后,我们可以尝试整体实现一下插件化了.这里我们先介绍一下最简单的实现插件化的方案. 一.最简单的插件化实现方案 最简单的插件化实现方案,对四大组件都是适用的,技术面涉及如下: 1). 合 ...
- Android基于代理的插件化思路分析
前言 正常的App开发流程基本上是这样的:开发功能-->测试--->上线,上线后发现有大bug,紧急修复---->发新版本---->用户更新----->bug修复.从发现 ...
- 基于Fragment的插件化
--<摘自android插件化开发指南> 1.有些项目,整个app只有一个Activity,切换页面全靠Fragment,盛行过一时,但有点极端 2.Activity切换fragment页 ...
- android 基于dex的插件化开发
安卓里边可以用DexClassLoader实现动态加载dex文件,通过访问dex文件访问dex中封装的方法,如果dex文件本身还调用了native方法,也就间接实现了runtime调用native方法 ...
- WebApi 插件式构建方案
WebApi 插件式构建方案 WebApi 插件式构建方案 公司要推行服务化,不可能都整合在一个解决方案内,因而想到了插件式的构建方案.最终定型选择基于 WebApi 构建服务化,之所以不使用 WCF ...
- Android插件化-RePlugin项目集成与使用
前言:前一段时间新开源了一种全面插件化的方案-- RePlugin,之前一种都在关注 DroidPlugin 并且很早也在项目中试用了,但最终没有投入到真正的生产环节,一方面是项目中没有特别需要插件化 ...
- Android Small插件化框架源码分析
Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github ...
- JavaScript插件化开发
大熊君JavaScript插件化开发 一,开篇分析 Hi,大家好!大熊君又和大家见面了,还记得昨天的那篇文章吗------这个系列的开篇(第一季).主要讲述了以“jQuery的方式如何开发插件”, 那 ...
- 热门前沿知识相关面试问题-android插件化面试问题讲解
插件化由来: 65536/64K[技术层面上]随着代码越来越大,业务逻辑越来繁杂,所以很容易达到一个65536的天花板,其65536指的是整个项目中的方法总数如果达到这个数量时则不无法创建新的方法了, ...
随机推荐
- 使用eclipse git插件合并merge部分代码方法
当有一个父项目,它的下面有多个子项目:或者一个项目下边,只想合并部分路径,甚至部分文件的内容,使用下边的方法可以达到目的,特此记录: 1.主项目右键 -> team -> remove f ...
- 02 .NET CORE 2.2 使用OCELOT -- 路由
继续学习.NET CORE 2.2 使用OCELOT https://www.jianshu.com/p/05ccf87a3091 https://www.jianshu.com/p/585396dc ...
- css+js实现自动伸缩导航栏
用css+js实现自动伸缩导航栏 需要达到的效果: 默认首页选中样式 设置鼠标滑过效果:颜色变化(#f60),宽度变化,字体变化 所涉及的知识点: 布局:float css: 元素状态切换(displ ...
- 349套HTML5+CSS3各行各业网站模板免费下载
场景 349套HTML5+CSS3各行各业网站模板. Web前端开发是从网页制作演变而来的,名称上有很明显的时代特征.在互联网的演化进程中,网页制作是Web1.0时代产物,那时网站的主要内容是静态的, ...
- 在centos下安装rar解压.rar压缩包
CentOS本身不自带rar环境,因此对于rar文件无法直接解压,需要先配置rar环境. 首先需要确定自己的系统是64位还是32位的,通过这个命令: [root@localhost]# uname - ...
- Java 之 Properties 集合
一.Properties 概述 Properties 是Hashtable的子类,不允许key和value是null,并且它的key和value的类型都是String. 二.常用方法 1.构造方法 P ...
- flink dataset join笔记
1.dataset的join连接,通过key进行关联,一般情况下的join都是inner join,类似sql里的inner join key包括以下几种情况: a key expression a ...
- 彻底搞懂JVM类加载器:基本概念
本文阅读时间大约9分钟. 写在前面 在Java面试中,在考察完项目经验.基础技术后,我会根据候选人的特点进行知识深度的考察,如果候选人简历上有写JVM(Java虚拟机)相关的东西,那么我常常会问一些J ...
- elasticsearch查询所有数据restful api以及java代码实现
原文:http://blog.java1234.com/blog/articles/366.html restful api实现如下: get http://192.168.1.111:9200/fi ...
- linux下使用selenium
安装chromedriver 1.安装chrome 用下面的命令安装最新的 Google Chrome yum install https://dl.google.com/linux/direct/g ...