[SPRING] @PathVariable을 사용한 스프링 MVC 주석 컨트롤러 인터페이스
SPRING@PathVariable을 사용한 스프링 MVC 주석 컨트롤러 인터페이스
컨트롤러를 인터페이스로 매핑하지 않는 이유가 있습니까?
주변 컨트롤러를 보는 모든 예제와 질문에서 모두 구체적인 클래스입니다. 이것에 대한 이유가 있습니까? 요청 매핑을 구현과 분리하고 싶습니다. 내 구체적인 클래스에서 매개 변수로 @PathVariable을 가져 오려고했지만 벽을 때렸다.
내 컨트롤러 인터페이스는 다음과 같습니다.
@Controller
@RequestMapping("/services/goal/")
public interface GoalService {
@RequestMapping("options/")
@ResponseBody
Map<String, Long> getGoals();
@RequestMapping(value = "{id}/", method = RequestMethod.DELETE)
@ResponseBody
void removeGoal(@PathVariable String id);
}
그리고 구현 클래스 :
@Component
public class GoalServiceImpl implements GoalService {
/* init code */
public Map<String, Long> getGoals() {
/* method code */
return map;
}
public void removeGoal(String id) {
Goal goal = goalDao.findByPrimaryKey(Long.parseLong(id));
goalDao.remove(goal);
}
}
getGoals () 메서드는 훌륭하게 작동합니다. removeGoal (String id) 예외를 throw합니다.
ExceptionHandlerExceptionResolver - Resolving exception from handler [public void
todo.webapp.controllers.services.GoalServiceImpl.removeGoal(java.lang.String)]:
org.springframework.web.bind.MissingServletRequestParameterException: Required
String parameter 'id' is not present
@PathVariable 주석을 콘크리트 클래스에 추가하면 모든 것이 예상대로 작동하지만 왜 이것을 구체적인 클래스에서 다시 선언해야합니까? @Controller 어노테이션이있는 것은 무엇이든 처리해야하지 않습니까?
해결법
-
==============================
1.분명히, 요청 패턴이 @RequestMapping 주석을 통해 메소드에 매핑 될 때, 그것은 구체적인 메소드 구현으로 매핑됩니다. 따라서 선언과 일치하는 요청은 원래 @RequestMapping, 즉 GoalService.removeGoal ()을 선언 한 메서드가 아니라 GoalServiceImpl.removeGoal ()을 직접 호출합니다.
분명히, 요청 패턴이 @RequestMapping 주석을 통해 메소드에 매핑 될 때, 그것은 구체적인 메소드 구현으로 매핑됩니다. 따라서 선언과 일치하는 요청은 원래 @RequestMapping, 즉 GoalService.removeGoal ()을 선언 한 메서드가 아니라 GoalServiceImpl.removeGoal ()을 직접 호출합니다.
인터페이스, 인터페이스 메소드 또는 인터페이스 메소드 매개 변수의 주석은 구현으로 이어지지 않으므로 구현 클래스가 명시 적으로 선언하지 않는 한 Spring MVC가 이것을 @PathVariable로 인식 할 수 없습니다. 그것없이 @PathVariable 매개 변수를 대상으로하는 AOP 조언은 실행되지 않습니다.
-
==============================
2.최신 버전의 Spring에서 작동합니다.
최신 버전의 Spring에서 작동합니다.
import org.springframework.web.bind.annotation.RequestMapping; public interface TestApi { @RequestMapping("/test") public String test(); }
컨트롤러에 인터페이스 구현하기
@RestController @Slf4j public class TestApiController implements TestApi { @Override public String test() { log.info("In Test"); return "Value"; } }
그것은 다음과 같이 사용될 수 있습니다 : 휴식 클라이언트
-
==============================
3.나는이 문제를 해결했다.
나는이 문제를 해결했다.
고객 측 :
이 라이브러리 https://github.com/ggeorgovassilis/spring-rest-invoker/를 사용하고 있습니다. 이 라이브러리는 스프링 휴식 서비스를 호출하기 위해 인터페이스에서 프록시를 생성합니다.
이 라이브러리를 확장했습니다.
주석과 공장 클라이언트 클래스를 만들었습니다.
스프링 레스트 서비스 확인
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SpringRestService { String baseUri(); }
이 클래스는 인터페이스에서 클라이언트 레스트를 생성합니다.
public class RestFactory implements BeanFactoryPostProcessor,EmbeddedValueResolverAware { StringValueResolver resolver; @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { this.resolver = resolver; } private String basePackage = "com"; public void setBasePackage(String basePackage) { this.basePackage = basePackage; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { createBeanProxy(beanFactory,SpringRestService.class); createBeanProxy(beanFactory,JaxrsRestService.class); } private void createBeanProxy(ConfigurableListableBeanFactory beanFactory,Class<? extends Annotation> annotation) { List<Class<Object>> classes; try { classes = AnnotationUtils.findAnnotatedClasses(basePackage, annotation); } catch (Exception e) { throw new BeanInstantiationException(annotation, e.getMessage(), e); } BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; for (Class<Object> classType : classes) { Annotation typeService = classType.getAnnotation(annotation); GenericBeanDefinition beanDef = new GenericBeanDefinition(); beanDef.setBeanClass(getQueryServiceFactory(classType, typeService)); ConstructorArgumentValues cav = new ConstructorArgumentValues(); cav.addIndexedArgumentValue(0, classType); cav.addIndexedArgumentValue(1, baseUri(classType,typeService)); beanDef.setConstructorArgumentValues(cav); registry.registerBeanDefinition(classType.getName() + "Proxy", beanDef); } } private String baseUri(Class<Object> c,Annotation typeService){ String baseUri = null; if(typeService instanceof SpringRestService){ baseUri = ((SpringRestService)typeService).baseUri(); }else if(typeService instanceof JaxrsRestService){ baseUri = ((JaxrsRestService)typeService).baseUri(); } if(baseUri!=null && !baseUri.isEmpty()){ return baseUri = resolver.resolveStringValue(baseUri); }else{ throw new IllegalStateException("Impossibile individuare una baseUri per l'interface :"+c); } } private static Class<? extends FactoryBean<?>> getQueryServiceFactory(Class<Object> c,Annotation typeService){ if(typeService instanceof SpringRestService){ return it.eng.rete2i.springjsonmapper.spring.SpringRestInvokerProxyFactoryBean.class; }else if(typeService instanceof JaxrsRestService){ return it.eng.rete2i.springjsonmapper.jaxrs.JaxRsInvokerProxyFactoryBean.class; } throw new IllegalStateException("Impossibile individuare una classe per l'interface :"+c); } }
내 공장 구성 :
<bean class="it.eng.rete2i.springjsonmapper.factory.RestFactory"> <property name="basePackage" value="it.giancarlo.rest.services" /> </bean>
휴식 서비스 서명
다음은 예제 인터페이스입니다.
package it.giancarlo.rest.services.spring; import ... @SpringRestService(baseUri="${bookservice.url}") public interface BookService{ @Override @RequestMapping("/volumes") QueryResult findBooksByTitle(@RequestParam("q") String q); @Override @RequestMapping("/volumes/{id}") Item findBookById(@PathVariable("id") String id); }
휴식 서비스 구현
서비스 구현
@RestController @RequestMapping("bookService") public class BookServiceImpl implements BookService { @Override public QueryResult findBooksByTitle(String q) { // TODO Auto-generated method stub return null; } @Override public Item findBookById(String id) { // TODO Auto-generated method stub return null; } }
매개 변수에 대한 주석을 해결하기 위해 @SpringRestService로 주석 된 모든 인터페이스를 찾는 사용자 정의 RequestMappingHandlerMapping을 만듭니다.
public class RestServiceRequestMappingHandlerMapping extends RequestMappingHandlerMapping{ public HandlerMethod testCreateHandlerMethod(Object handler, Method method){ return createHandlerMethod(handler, method); } @Override protected HandlerMethod createHandlerMethod(Object handler, Method method) { HandlerMethod handlerMethod; if (handler instanceof String) { String beanName = (String) handler; handlerMethod = new RestServiceHandlerMethod(beanName,getApplicationContext().getAutowireCapableBeanFactory(), method); } else { handlerMethod = new RestServiceHandlerMethod(handler, method); } return handlerMethod; } public static class RestServiceHandlerMethod extends HandlerMethod{ private Method interfaceMethod; public RestServiceHandlerMethod(Object bean, Method method) { super(bean,method); changeType(); } public RestServiceHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException { super(bean,methodName,parameterTypes); changeType(); } public RestServiceHandlerMethod(String beanName, BeanFactory beanFactory, Method method) { super(beanName,beanFactory,method); changeType(); } private void changeType(){ for(Class<?> clazz : getMethod().getDeclaringClass().getInterfaces()){ if(clazz.isAnnotationPresent(SpringRestService.class)){ try{ interfaceMethod = clazz.getMethod(getMethod().getName(), getMethod().getParameterTypes()); break; }catch(NoSuchMethodException e){ } } } MethodParameter[] params = super.getMethodParameters(); for(int i=0;i<params.length;i++){ params[i] = new RestServiceMethodParameter(params[i]); } } private class RestServiceMethodParameter extends MethodParameter{ private volatile Annotation[] parameterAnnotations; public RestServiceMethodParameter(MethodParameter methodParameter){ super(methodParameter); } @Override public Annotation[] getParameterAnnotations() { if (this.parameterAnnotations == null){ if(RestServiceHandlerMethod.this.interfaceMethod!=null) { Annotation[][] annotationArray = RestServiceHandlerMethod.this.interfaceMethod.getParameterAnnotations(); if (this.getParameterIndex() >= 0 && this.getParameterIndex() < annotationArray.length) { this.parameterAnnotations = annotationArray[this.getParameterIndex()]; } else { this.parameterAnnotations = new Annotation[0]; } }else{ this.parameterAnnotations = super.getParameterAnnotations(); } } return this.parameterAnnotations; } } } }
구성 클래스를 만들었습니다.
@Configuration public class WebConfig extends WebMvcConfigurationSupport{ @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping() { RestServiceRequestMappingHandlerMapping handlerMapping = new RestServiceRequestMappingHandlerMapping(); handlerMapping.setOrder(0); handlerMapping.setInterceptors(getInterceptors()); handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager()); PathMatchConfigurer configurer = getPathMatchConfigurer(); if (configurer.isUseSuffixPatternMatch() != null) { handlerMapping.setUseSuffixPatternMatch(configurer.isUseSuffixPatternMatch()); } if (configurer.isUseRegisteredSuffixPatternMatch() != null) { handlerMapping.setUseRegisteredSuffixPatternMatch(configurer.isUseRegisteredSuffixPatternMatch()); } if (configurer.isUseTrailingSlashMatch() != null) { handlerMapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch()); } if (configurer.getPathMatcher() != null) { handlerMapping.setPathMatcher(configurer.getPathMatcher()); } if (configurer.getUrlPathHelper() != null) { handlerMapping.setUrlPathHelper(configurer.getUrlPathHelper()); } return handlerMapping; } }
나는 그것을 구성했다.
<bean class="....WebConfig" />
from https://stackoverflow.com/questions/8002514/spring-mvc-annotated-controller-interface-with-pathvariable by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] 인터페이스에서 주석과 일치하는 Spring AOP pointcut (0) | 2018.12.19 |
---|---|
[SPRING] Spring JdbcTemplate을 통해 동적 "in (...)"SQL 목록을 생성하는 방법은 무엇입니까? (0) | 2018.12.18 |
[SPRING] Spring annotation 기반 DI 대 XML 구성? (0) | 2018.12.18 |
[SPRING] LogBack appender를 프로그래밍 방식으로 구성 (0) | 2018.12.18 |
[SPRING] 서로 다른 인수를 갖는 동일한 URL 패턴에 대해 두 개의 메소드를 작성하십시오. (0) | 2018.12.18 |