Spring - 拦截bean创建和注入自定义代理

Sot*_*lis 13 java spring spring-mvc spring-aop

我有一个@Controller带有@Autowired字段和处理程序方法,我想用自定义注释进行注释.

例如,

@Controller
public class MyController{
    @Autowired
    public MyDao myDao;

    @RequestMapping("/home")
    @OnlyIfXYZ
    public String onlyForXYZ() {
        // do something
        return "xyz";
    }
}
Run Code Online (Sandbox Code Playgroud)

@OnlyIfXYZ自定义注释的示例在哪里.我以为我会拦截Controller bean的创建,传递我自己的CGLIB代理,Spring可以在其上设置属性,比如autowired字段.

我尝试使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这个解决方案使用反射将目标bean的所有字段复制到代理bean(对我来说有点hacky).但是如果那些不是我正在拦截的方法中的参数,我就无法访问HttpServletRequestHttpServletResponse对象.

我是否可以在Spring填充其属性之前注入Spring bean创建逻辑以注入我自己的代理控制器?我需要能够访问HttpServletRequestHttpServletResponse对象,无论Controller处理程序方法在其定义中是否具有它,即.作为参数.

NB@Autowired字段也是一个代理,它用@TransactionalSpring 注释代理它.

编辑: AOP解决方案很好地拦截方法调用,但我找不到访问HttpServletRequestHttpServletResponse对象的方法,如果它们不是方法参数.

我可能最终会使用HandlerInterceptorAdapter,但我希望我能用OOP来做到这一点,以免将开销增加到不需要它的方法.

nic*_*ild 7

看看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;
    }
}
Run Code Online (Sandbox Code Playgroud)

值得注意的是,Spring只会将代理应用于属于其应用程序上下文的类.(在你的例子中出现的情况)

您还可以使用Spring AOP将参数绑定到aspect方法.这可以通过各种方式完成,但您所追求的可能是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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这方面应该是你所追求的一部分.它将标注了代理方法@OnlyIfXYZALSO在采取HttpServletRequest作为参数.此外,它将它HttpServletRequest作为传入参数绑定到Aspect方法中.

据我所知,你是后两者可能HttpServletRequestHttpServletResponse,所以你应该能够修改args表达式采取请求和响应.


Mat*_*ski 6

考虑到您在问题下的评论,您只需要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>
Run Code Online (Sandbox Code Playgroud)

此接口提供方法preHanlde,它具有请求,响应和HandlerMethod.要检查方法是否已注释,请尝试以下操作:

HandlerMethod method = (HandlerMethod) handler;
OnlyIfXYZ customAnnotation = method.getMethodAnnotation(OnlyIfXYZ.class);
Run Code Online (Sandbox Code Playgroud)