在项目迭代开发中经常会遇到对已有功能的改造需求,尽管我们可能已经预留了扩展点,并且尝试通过接口或扩展类完成此类任务。可是,仍然有很多难以预料的场景无法通过上述方式解决。修改原有代码当然能够做到,但是这会增加许多附加成本,回归测试带来大量工作和一些潜在的未知风险。特别是一些极其重要的公共模块,可谓牵一发而动全身,稍有不慎都将引发重大的故障。这里分享一下自己在项目开发中的一点实践,一种基于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方法时,返回的就是脱敏后的人员信息了。
一点总结:
软件开发过程中需求难免会发生变化,今日精巧的设计,明天就可能变成拦路的淤泥。