基于AOP的插件化(扩展)方案

Posted lichmama

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于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;

 

处理插件业务的切面类:

技术图片
@Aspect
@Component
public class PluginServiceAspect 
    private static Logger logger = LoggerFactory.getLogger(PluginServiceAspect.class);
    private static ConcurrentHashMap<String, PluginService> pluginConfigMap = new ConcurrentHashMap<String, PluginService>();
    private static volatile Boolean initStatus = false;

    @Around("execution(* com.lichmama.demo..service.*Service.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable 
        PluginService pluginService = getPluginService(point);
        if (pluginService == null) 
            return point.proceed();
        
        PluginPoint pluginPoint = pluginService.getClass().getAnnotation(PluginPoint.class);
        
        Object result = null;
        
        // before
        if (pluginPoint.type() == PluginType.BEFORE) 
            pluginService.process(point.getArgs(), null);
        
        // override
        if (pluginPoint.type() == PluginType.OVERRIDE) 
            result = pluginService.process(point.getArgs(), null);
        
        // after
        if (pluginPoint.type() == PluginType.AFTER) 
            result = pluginService.process(point.getArgs(), point.proceed());
        
        
        return result;
    

    private PluginService getPluginService(ProceedingJoinPoint point) 
        // initialize pluginConfigMap first
        initPluginConfigMap();
        
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        String className = method.getDeclaringClass().getName();
        String methodName = method.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) 
            Iterator<Map.Entry<String, Object>> iter = plugins.entrySet().iterator();
            while (iter.hasNext()) 
                Map.Entry<String, Object> entry = iter.next();
                PluginService pluginService = (PluginService) entry.getValue();
                PluginPoint pluginPoint = pluginService.getClass().getAnnotation(PluginPoint.class);
                Class<?> service = pluginPoint.service();
                String method = pluginPoint.method();
                String serviceMethod = service.getName() + "#" + method;
                pluginConfigMap.put(serviceMethod, pluginService);
            
        
        logger.info("pluginConfigMap initialized ...");
    

    private void initPluginConfigMap() 
        if (initStatus == false) 
            synchronized (initStatus) 
                if (initStatus == false) 
                    loadPluginService();
                    initStatus = true;
                
            
        
    
View Code

 

当然还有最重要的插件实现类:

@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的插件化(扩展)方案的主要内容,如果未能解决你的问题,请参考以下文章

golang插件化方案

Android 插件化现有的针对插件化恶意应用的解决方案 | 插件化应用开发推荐方案

Android 插件化Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )

插件化换肤方案

Android 插件化多开原理 | 使用插件化技术的恶意应用 | 插件化的其它风险 | 应用开发推荐方案

插件化架构设计:插件化从设计到实践该考量的问题汇总