springspring-bootspring-aopspring-elspring-aspects

Spring SPEL error: SpelEvaluationException: EL1057E: No bean resolver registered in the context to resolve access to bean 'validatorService'


When doing a POC, we use a custom annotation with a SPEL expression. The SPEL expression contains a reference to a bean method:

@CheckEntity(keyPath = "@validatorService.isColorRed(#color)")

The custom annotation is integrated with with AOP (Aspects). When executing the Aspect method gives on evaluating the SPEL expression the following error:

org.springframework.expression.spel.SpelEvaluationException: EL1057E: No bean resolver registered in the context to resolve access to bean 'validatorService'

Can you help solving the access to the bean ?

First I tried to inject the validatorService (@Service) at different locations in the code. Below you find the simplified but essential code.

The custom annotation is:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckEntity {
  String message() default "Check color msg";
  String keyPath() default "";
}

Using the custom annotation with a SPEL expression using a bean.

@Service
public class ColorService {
  ColorIsRedValidatorService validatorService;

  public ColorService(ColorIsRedValidatorService validatorService) {
    this.validatorService = validatorService;
  }

  @CheckEntity(keyPath = "@validatorService.isColorRed(#color)")
  public String checkColorIsRed( String color) {
    return "ok::" + color;
  }
}

The Aspect code is:

@Component
@Aspect
public class CheckEntityAspect {
  private Logger logger = LoggerFactory.getLogger(CheckEntityAspect.class);
  private final ColorIsRedValidatorService validatorService;

  public CheckEntityAspect(ColorIsRedValidatorService validatorService) {
    this.validatorService = validatorService;
  }

  @Before("execution(* *.*(..)) && @annotation(checkEntity)")
  public void checkEntity(JoinPoint joinPoint, CheckEntity checkEntity) {
    Object[] args = joinPoint.getArgs();
    ExpressionParser elParser = new SpelExpressionParser();
    Expression expression = elParser.parseExpression(checkEntity.keyPath());
    logger.info( "Within entity color Check: " + (String) expression.getValue(args));
    // gives EXCEPTION: EL1057E: No bean resolver registered in the context to resolve access to bean 'validatorService'
    // ... 
  }
}

The validatorService is also simplified:

@Service
public class ColorIsRedValidatorService {
  public void isColorRed( String color) throws ColorIsNotRedException {
    if( ! color.toLowerCase().matches( "red")) {
      throw new ColorIsNotRedException( "Invalid red color: " + color);
    }
  }
}

As marked in the code, evaluating the SPEL expression gives the "No bean resolver registered" error.

In the approach above I autowired a validatorService at different spots in the code.

Alternatively, I created a config file launching the validator bean. Also without success.

@Configuration
public class ColorBeanResolved {
  @Bean
  ColorIsRedValidatorService validatorService() {
    return new ColorIsRedValidatorService();
  }
}

Solution

  • First, the parameter value passes to Expression#getValue() should be the EvaluationContext but not the join point 's argument.

    Second, to make the SPEL can refer to a spring bean , you have to configure a BeanResolver for the EvaluationContext (See the docs for details).

    Third , @validatorService will look for a bean which the name is validatorService from the context resolved by the BeanResolver.But you do not define any beans with this name. You only has a bean called colorIsRedValidatorService but not validatorService.

    So change to the followings should fix your problem :

    
    @Component
    @Aspect
    public class CheckEntityAspect {
     
      @Autowired
      private ApplicationContext context;
    
      @Before("execution(* *.*(..)) && @annotation(checkEntity)")
      public void checkEntity(JoinPoint joinPoint, CheckEntity checkEntity) {
        Object[] args = joinPoint.getArgs();
        
        ExpressionParser elParser = new SpelExpressionParser();
        Expression expression = elParser.parseExpression(checkEntity.keyPath());
        
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("color", args[0]);
        context.setBeanResolver(new BeanFactoryResolver(this.context));
        
    
        logger.info( "Within entity color Check: " + (String) expression.getValue(context));
    
      }
    }
    

    And explicitly configure the bean name of ColorIsRedValidatorService to validatorService

    @Service("validatorService")
    public class ColorIsRedValidatorService {
     
    }