Spring - Перехват bean создания и ввода пользовательского прокси
У меня есть поля @Controller
с @Autowired
и методы обработчика, которые я хочу аннотировать с помощью специальных аннотаций.
Например,
@Controller
public class MyController{
@Autowired
public MyDao myDao;
@RequestMapping("/home")
@OnlyIfXYZ
public String onlyForXYZ() {
// do something
return "xyz";
}
}
Где @OnlyIfXYZ
- пример пользовательской аннотации. Я думал, что перехватил создание Controller bean, передал свой собственный прокси CGLIB, на котором Spring может затем установить свойства, такие как поле с автоопределением.
Я попробовал использовать InstantiationAwareBeanPostProcessor
, но это решение не работает отлично, потому что postProcessBeforeInstantiation()
замыкает всю оставшуюся часть процесса. Я попытался с postProcessAfterInitialization()
, как показано ниже
public class MyProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// Here the bean autowired fields are already set
return bean;
}
@Override
public Object postProcessAfterInitialization(Object aBean, String aBeanName) throws BeansException {
Class<?> clazz = aBean.getClass();
// only for Controllers, possibly only those with my custom annotation on them
if (!clazz.isAnnotationPresent(Controller.class))
return aBean;
Object proxy = Enhancer.create(clazz, new MyMethodInterceptor());
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
try {
// get the field and copy it over to the proxy
Object objectToCopy = field.get(aBean);
field.set(proxy, objectToCopy);
} catch (IllegalArgumentException | IllegalAccessException e) {
return aBean;
}
}
return proxy;
}
}
Это решение использует отражение для копирования по всем полям целевого bean в прокси bean (вроде хаки для моего вкуса). Но у меня нет доступа к объектам HttpServletRequest
и HttpServletResponse
, если они не являются аргументами в методе, который я перехватываю.
Есть ли еще один обратный вызов, который я могу ввести в логику создания Spring bean для ввода моего собственного прокси-контроллера до того, как Spring заполнит его свойства? Мне нужно иметь доступ к объектам HttpServletRequest
и HttpServletResponse
независимо от того, имеет ли метод обработчика контроллера его определение, т.е. как аргументы.
N.B Поле @Autowired
также является прокси, оно аннотируется с помощью @Transactional
, поэтому Spring проксирует его.
EDIT: Решение AOP прекрасно работает для перехвата вызова метода, но я не могу найти способ доступа к объектам HttpServletRequest
и HttpServletResponse
, если они еще не являются методами аргументы.
Возможно, мне удастся использовать HandlerInterceptorAdapter, но я надеялся, что смогу сделать это с помощью ООП, чтобы не добавлять накладные расходы к методам, которые ему не нужны.
Ответы
Ответ 1
Принимая во внимание ваш комментарий по вопросу, который вам нужен, это HandlerInterceptor.
http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/web/servlet/HandlerInterceptor.html
Вам необходимо реализовать этот интерфейс и добавить его в свою конфигурацию, например:
<mvc:interceptors>
<bean id="customInterceptor" class="com.example.interceptors.CustomInterceptor"/>
</mvc:interceptors>
Этот интерфейс предоставляет метод preHanlde, который имеет запрос, ответ и HandlerMethod. Чтобы проверить, является ли метод аннотированным, просто попробуйте следующее:
HandlerMethod method = (HandlerMethod) handler;
OnlyIfXYZ customAnnotation = method.getMethodAnnotation(OnlyIfXYZ.class);
Ответ 2
Посмотрите Spring AOP. У него есть именно то, что вам нужно. Например, вы можете сделать что-то вроде этого:
@Aspect
@Component
public class MyAspect {
@Around("@annotation(path.to.your.annotation.OnlyIfXYZ)")
public Object onlyIfXyz(final ProceedingJoinPoint pjp) throws Exception {
//do some stuff before invoking methods annotated with @OnlyIfXYZ
final Object returnValue = pjp.proceed();
//do some stuff after invoking methods annotated with @OnlyIfXYZ
return returnValue;
}
}
Стоит отметить, что Spring применит только прокси к классам, которые являются частью его контекста приложения. (как представляется, в вашем примере)
Вы также можете использовать Spring AOP для привязки параметров к вашему аспекту. Это можно сделать по-разному, но тот, который вам нужен, вероятно, args(paramName)
.
@Aspect
@Component
public class MyAspect2 {
@Around("@annotation(path.to.your.annotation.OnlyIfXYZ) && " +
"args(..,request,..)")
public Object onlyIfXyzAndHasHttpServletRequest(final ProceedingJoinPoint pjp,
final HttpServletRequest request) throws Exception {
//do some stuff before invoking methods annotated with @OnlyIfXYZ
//do something special with your HttpServletRequest
final Object returnValue = pjp.proceed();
//do some stuff after invoking methods annotated with @OnlyIfXYZ
//do more special things with your HttpServletRequest
return returnValue;
}
}
Этот аспект должен сделать часть того, что вам нужно. Это будут прокси-методы, аннотированные с помощью @OnlyIfXYZ
, которые ALSO принимают HttpServletRequest
как параметр. Кроме того, он привяжет этот HttpServletRequest
к методу Aspect как переданный параметр.
Я понимаю, что вы, возможно, оба HttpServletRequest
и HttpServletResponse
, так что вы должны иметь возможность изменять выражение args
, чтобы принимать как запрос, так и ответ.
Ответ 3
Я думаю, что нет, но я полагаю, что вы могли бы автопроксировать прокси после его создания.
public class MyProcessor extends InstantiationAwareBeanPostProcessorAdapter
implements BeanFactoryAware {
private AutowireCapableBeanFactory beanFactory;
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
// This is where I thought I would do it, but it then skips setting fields alltogether
if (beanClass.isAnnotationPresent(Controller.class)) {
Object proxy = Enhancer.create(beanClass, new MyInterceptor());
// autowire
beanFactory.autowireBean(proxy);
return proxy;
}
return null;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (AutowireCapableBeanFactory) beanFactory;
}
}
Другой альтернативой является создание прокси-сервера Spring AOP (с использованием ProxyFactory
) в методе postProcessAfterInitialization
. Для этого может оказаться полезным AbstractAutoProxyCreator
. См. Пример BeanNameAutoProxyCreator. Но imho, pointcutcut (Nicholas answer) делает то же самое и проще.
Ответ 4
InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation
приведет к короткому замыканию метода создания bean. Используемая только обработка postProcessAfterInitialization
. Это означает, что autowiring не произойдет, потому что AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues
никогда не будет вызван. Таким образом, вы должны вручную вводить или автоопределять свойства проксированного метода beans в postProcessAfterInitialization
.
Вопрос: влияет ли перемещение логики проксирования в метод postProcessAfterInitialization
на ваши бизнес-требования? Если нет, я предлагаю вам проксировать там.
FYI: если вы не создаете API, используйте подход аннотации, предложенный @nicholas.hauschild.