CONTEXTO: Me gustaría crear una anotación personalizada en Spring Boot y agregar lógica adicional para el procesamiento. Doy un ejemplo con una anotación bastante simplista, pero quiero tener varias de esas anotaciones con un control más detallado.
Hay varios enfoques para este problema:
Tengo que moverme con el último ya que los dos anteriores no funcionan con mi caso de uso.
TEMA:
Tengo una anotación personalizada en Kotlin y quiero que se registre y se verifique en el tiempo de ejecución.
@Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION) annotation class OfflineProcessing(val allow: Bool)El controlador REST está a continuación:
@RestController class ApiController { @GetMapping("/api/version") @OfflineProcessing(true) fun apiVersion(): String { return "0.1.0" } } La idea es tener una anotación por método y hacer una lógica condicional basada en OflineProcessing permitido o no.
He intentado crear PostBeanProcessor elemental:
@Component class OfflineProcessingAnnotationProcessor @Autowired constructor( val configurableBeanFactory: ConfigurableListableBeanFactory ) : BeanPostProcessor { @Throws(BeansException::class) override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? { println("Before processor. Bean name: $beanName, Bean: $bean. Bean factory: $configurableBeanFactory.") return super.postProcessBeforeInitialization(bean, beanName) } @Throws(BeansException::class) override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? { println("After processor. Bean name: $beanName, Bean: $bean. Bean factory: $configurableBeanFactory.") return super.postProcessAfterInitialization(bean, beanName) } }Aparentemente, la anotación no se registra entre otras anotaciones en BeanPostProcessor y confundí cómo acceder a ella, hasta ahora no encontré ningún otro buen ejemplo sin BeanPostProcessor.
Dependencias:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency>¿Hay algo que haga mal? ¿O estoy tratando de usar un método incorrecto para la tarea?
No es una respuesta directa a su pregunta, pero simplemente usaría Spring AOP en ese caso en lugar de implementar BeanPostProcessor. Para hacerlo, puede definir la siguiente anotación y un aspecto correspondiente, por ejemplo:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) annotation class OfflineProcessing(val allowed: Boolean = true) @Aspect @Component class OfflineProcessingAspect { @Pointcut("@annotation(<path.to.annotation.package>.OfflineProcessing)") fun offlineProcessingPointcut(offlineProcessing: OfflineProcessing?) { } @Around("offlineProcessingPointcut(offlineProcessing)") @Throws(Throwable::class) fun around(pjp: ProceedingJoinPoint, offlineProcessing: OfflineProcessing): Object { if (offlineProcessing.allowed()) { // your pre-processing logic here val result = pjp.proceed() // post-processing logic here return result } // do the same for non-allowed offline-processing ... } }Por último, agregue la siguiente dependencia:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>Esta es una pregunta general y no específica de Kotlin.
Creo que la idea errónea en su intento de resolver esto es el hecho de que está confiando en BeanPostProcessors. El bean se crea en una etapa temprana y probablemente sea un singleton, por lo que no se llamará cuando ejecute una solicitud de descanso. Esto significa que deberá verificar si hay un bean que tenga su anotación y luego, de alguna manera, crear un bean proxy sobre ellos con su lógica incrustada en ese proxy.
Esto es muy similar a lo que hace AOP y luego el enfoque de @eol es coincidir con la Pascua.
Me gustaría sugerir usar un interceptor pero no un interceptor de creación de frijoles.
Mi respuesta se inspiró enSpring Boot Adding Http Request Interceptors
Definir la anotación
@Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION) annotation class OfflineProcessing(val allow: Boolean)Definir el interceptor
@Component class CustomRestAnnotationInterceptor:HandlerInterceptor { private val logger: Logger = LoggerFactory.getLogger(this.javaClass) override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean { if (handler is HandlerMethod) { var isOffline: OfflineProcessing? = handler.getMethodAnnotation(OfflineProcessing::class.java) if (null == isOffline) { isOffline = handler.method.declaringClass .getAnnotation(OfflineProcessing::class.java) } if (null != isOffline) { logger.info("method called has OfflineProcessing annotation with allow=${isOffline.allow}" ) } } return true } }Agregar el interceptor a la ruta
@Configuration class WebConfig : WebMvcConfigurer { @Autowired lateinit var customRestAnnotationInterceptor: CustomRestAnnotationInterceptor override fun addInterceptors(registry: InterceptorRegistry) { // Custom interceptor, add intercept path and exclude intercept path registry.addInterceptor(customRestAnnotationInterceptor).addPathPatterns("/**") } }Use la anotación en su controlador
el registro se mostrará
2022-04-14 08:48:58.785 INFO 32595 --- [nio-8080-exec-1] .hskqCustomRestAnnotationInterceptor : method called has OfflineProcessing annotation with allow=true