基于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
相关产品推荐
相关产品推荐

