Подтвердить что ты не робот

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, но я надеялся, что смогу сделать это с помощью ООП, чтобы не добавлять накладные расходы к методам, которые ему не нужны.

4b9b3361

Ответ 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.