You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Spring AOP方法拦截器的接口级策略模式实现方案咨询

基于Spring AOP实现角色维度策略路由的优化需求

我们需要在方法维度应用策略模式:定义包含业务方法的接口或抽象类,根据用户的安全角色调用对应的实现逻辑。目前我们通过Spring AOP注解实现路由逻辑,判断应调用的类与功能,现有实现如下:

注解类

/**
 * 标注在默认类的方法上的注解
 * 该默认类可视为返回默认值的抽象类,存在多个根据令牌中所属公司/产品线区分的实现子类
 * 这些子类继承默认类的所有方法并实现自定义逻辑,和策略模式的设计思路一致
 *
 * 示例结构:
 *              TestClass
 *              /       |       \
 *   AIPTestClass  AIITestClass  AIFTestClass
 *
 * 要求:
 * 1) 存在一个基础名称的默认类,例如TestClass
 * 2) 每个可选实现对应一个子类,例如AIPTestClass
 * 3) 将该注解添加到需要绑定产品线/公司实现的默认类方法上
 * 4) 注解需要标注在@Component及其衍生注解(@Service、@Controller等)修饰的类中
 *
 * 具体处理逻辑可参考ProductCompanyBoundImplSelectionInterceptor
 */
@Inherited
@Target({METHOD})
@Retention(RUNTIME)
public @interface ProductCompanyImplSelection {

}

派生标准方法的基础类

@Component
public class StrategyPattern {

    @ProductCompanyImplSelection
    public String executeMethod(TestObject value, int primitive, String valueString) {
        return null;
    }

}

多个策略实现类

@Component
public class AIPStrategyPattern {

    public String executeMethod(TestObject value, int primitive, String valueString) {
        return "AIP";
    }

}

@Component
public class AIFStrategyPattern {

    public String executeMethod(TestObject value, int primitive, String valueString) {
        return "AIF";
    }

}

实现路由逻辑的拦截器

@Aspect
@Slf4j
public class ProductCompanyBoundImplSelectionInterceptor implements MethodInterceptor {

    private final ApplicationContext applicationContext;

    public ProductCompanyBoundImplSelectionInterceptor(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        String productCompany = getDivision();

        // 类路由逻辑
        Object executionClass = methodInvocation.getThis();
        Object productCompanySpecificInstance;
        Class<?> productCompanySpecificClass;
        try {
            productCompanySpecificInstance = applicationContext.getBean(
                    productCompany + executionClass.getClass().getSimpleName());
            productCompanySpecificClass = productCompanySpecificInstance.getClass();
        } catch (Exception e) {
            throw new ProductCompanySelectionClassMissingException(
                    "未找到类" + executionClass.getClass().getSimpleName() + "对应产品线/公司" + productCompany + "的实现");
        }
        // 方法路由逻辑
        String methodName = methodInvocation.getMethod().getName();
        Class<?>[] paramClasses =
                new Class<?>[methodInvocation.getMethod().getParameterTypes().length];
        for (int paramIndex = 0; paramIndex < methodInvocation.getMethod()
                .getParameterTypes().length; paramIndex++) {
            Class<?> parameterType = methodInvocation.getMethod().getParameterTypes()[paramIndex];
            if (parameterType.isPrimitive()) {
                paramClasses[paramIndex] = parameterType;
            } else {
                Class<?> paramClass = Class.forName(parameterType.getName());
                paramClasses[paramIndex] = paramClass;
            }
        }
        Method productCompanySpecificMethod =
                productCompanySpecificClass.getMethod(methodName, paramClasses);
        return productCompanySpecificMethod.invoke(productCompanySpecificInstance,
                methodInvocation.getArguments());

    }

    private String getDivision() {
        UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext()
                        .getAuthentication();
        AuthenticationDetails details = (AuthenticationDetails) authentication.getDetails();
        return details.getDivision();
    }
}

配置类

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.stackoverflowmvce.strategypatternaop.*")
public class SpringSecurityAOPConfig {

    @Bean
    public Advisor productCompanyBoundImplSelectionAdvisor(ApplicationContext applicationContext) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(
                "@annotation(com.stackoverflowmvce.strategypatternaop.annotations.ProductCompanyImplSelection)");
        DefaultPointcutAdvisor pointcutAdvisor =
                new DefaultPointcutAdvisor(pointcut, new ProductCompanyBoundImplSelectionInterceptor(
                        applicationContext));
        return pointcutAdvisor;
    }

}

测试类

@SpringBootTest
class StrategyPatternAopApplicationTests {

    @Autowired
    private StrategyPattern baseStrategyPattern;

    @Test
    void whenDivisionAIP_returnAIPResult() {
        this.assertDivisionStategyIsOk("AIP");
    }

    @Test
    void whenDivisionAIF_returnAIFResult() {
        this.assertDivisionStategyIsOk("AIF");
    }

    @Test
    void whenDivisionAII_notFound_returnException() {
        Assertions.assertThrows(ProductCompanySelectionClassMissingException.class, () -> {
            this.assertDivisionStategyIsOk("AII");
        });

    }

    private void assertDivisionStategyIsOk(String division) {
        this.setupSecurityContext(division);
        String strategyResult =
                this.baseStrategyPattern.executeMethod(new TestObject("test"), 0, "TEST");
        assertThat(division).isEqualTo(strategyResult);

    }

    private void setupSecurityContext(String division) {
        SecurityContext context = SecurityContextHolder.getContext();
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken("oid", null);
        AuthenticationDetails authenticationDetails = new AuthenticationDetails(division);
        authentication.setDetails(authenticationDetails);
        context.setAuthentication(authentication);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
}
现有问题

当前我们使用无实际业务逻辑的默认类作为基础类,代码结构冗余不够优雅,期望将该StrategyPattern类替换为接口或抽象类,同时保证原有功能正常运行。但当前注解仅能作用于会被实际执行的方法,因此寻求可行的实现方案。

2021年10月22日更新

已调整代码适配Spring @Component自动扫描与ApplicationContext机制,现有测试全部运行通过,核心诉求是将StrategyPattern类改为接口后仍能正常运行。


内容的提问来源于stack exchange,提问作者Niko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 08:54:03