java Spring - 拦截 bean 创建和注入自定义代理
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15594071/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring - Intercepting bean creation and injecting custom proxy
提问by Sotirios Delimanolis
I have a @Controller
with @Autowired
fields and handler methods that I want to annotate with custom annotations.
我有一个@Controller
具有@Autowired
字段和处理方法,我想用自定义的注解注释。
For example,
例如,
@Controller
public class MyController{
@Autowired
public MyDao myDao;
@RequestMapping("/home")
@OnlyIfXYZ
public String onlyForXYZ() {
// do something
return "xyz";
}
}
Where @OnlyIfXYZ
is an example of a custom annotation. I was thinking I would intercept the Controller bean creation, pass my own CGLIB proxy on which Spring can then set properties, like the autowired field.
@OnlyIfXYZ
自定义注释的示例在哪里。我想我会拦截控制器 bean 的创建,传递我自己的 CGLIB 代理,然后 Spring 可以在其上设置属性,例如自动装配的字段。
I tried using a InstantiationAwareBeanPostProcessor
but that solution doesn't work great because postProcessBeforeInstantiation()
short-circuits the rest of the process. I tried with postProcessAfterInitialization()
, like below
我尝试使用 aInstantiationAwareBeanPostProcessor
但该解决方案效果不佳,因为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;
}
}
This solution uses reflection to copy over all the fields of the target bean to the proxy bean (kind of hacky for my taste). But I don't have access to the HttpServletRequest
and HttpServletResponse
objects if those aren't arguments in the method I'm intercepting.
此解决方案使用反射将目标 bean 的所有字段复制到代理 bean(对我来说有点 hacky)。但是HttpServletRequest
,HttpServletResponse
如果和对象不是我拦截的方法中的参数,我将无权访问它们。
Is there another callback I can inject into Spring bean creation logic to inject my own Proxy Controller before Spring populates its properties? I need to be able to access the HttpServletRequest
and HttpServletResponse
objects regardless of if the Controller handler method has it in its definition, ie. as arguments.
在 Spring 填充其属性之前,是否有另一个回调我可以注入 Spring bean 创建逻辑以注入我自己的代理控制器?无论 Controller 处理程序方法是否在其定义中,我都需要能够访问HttpServletRequest
和HttpServletResponse
对象,即。作为论据。
N.BThe @Autowired
field is also a proxy, it is annotated with @Transactional
so Spring proxies it up.
NB该@Autowired
字段也是一个代理,它被注解,@Transactional
所以 Spring 代理起来。
EDIT:The AOP solution works nicely for intercepting the method invocation, but I can't find a way to access the HttpServletRequest
and HttpServletResponse
objects, if they aren't already method arguments.
编辑:AOP 解决方案可以很好地拦截方法调用,但我找不到访问HttpServletRequest
和HttpServletResponse
对象的方法,如果它们还不是方法参数。
I'm probably going to end up using HandlerInterceptorAdapter, but I was hoping I can do it with OOP so as to not add the overhead to methods that don't need it.
我可能最终会使用 HandlerInterceptorAdapter,但我希望我可以用 OOP 来做到这一点,以免给不需要它的方法增加开销。
采纳答案by Mateusz Mrozewski
Taking into account your comment under the question all you need is HandlerInterceptor.
考虑到您在问题下的评论,您只需要 HandlerInterceptor。
You need to implement that interface and add it to your configuration, for example:
您需要实现该接口并将其添加到您的配置中,例如:
<mvc:interceptors>
<bean id="customInterceptor" class="com.example.interceptors.CustomInterceptor"/>
</mvc:interceptors>
This interface provides method preHanlde, which has request, response and HandlerMethod. To check if the method is annotated just try this:
该接口提供了方法preHanlde,它有请求、响应和HandlerMethod。要检查该方法是否已注释,请尝试以下操作:
HandlerMethod method = (HandlerMethod) handler;
OnlyIfXYZ customAnnotation = method.getMethodAnnotation(OnlyIfXYZ.class);
回答by nicholas.hauschild
Take a look at Spring AOP. It has exactly the facilities you are after. For your example, you could do something like this:
看看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;
}
}
It is worth noting that Spring will only apply the proxy to classes that are a part of its application context. (which it appears is the case in your example)
值得注意的是,Spring 只会将代理应用于作为其应用程序上下文一部分的类。(在您的示例中似乎就是这种情况)
You can also use Spring AOP to bind parameters to your aspect method. This can be done in various ways, but the one you are after is probably args(paramName)
.
您还可以使用 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;
}
}
This aspect should do a part of what you are after. It will proxy methods annotated with @OnlyIfXYZ
that ALSOtake in a HttpServletRequest
as a parameter. Further, it will bind this HttpServletRequest
into the Aspect method as a passed in parameter.
这方面应该做你所追求的一部分。它将标注了代理方法@OnlyIfXYZ
是ALSO在采取HttpServletRequest
作为参数。此外,它会将 thisHttpServletRequest
作为传入的参数绑定到 Aspect 方法中。
I understand that you are after potentially both HttpServletRequest
and HttpServletResponse
, so you should be able to modify the args
expression to take in both request and response.
我知道您可能同时追求HttpServletRequest
和HttpServletResponse
,因此您应该能够修改args
表达式以同时接收请求和响应。
回答by Jose Luis Martin
I think that not, but I supose that you could autowire the proxy after creating it.
我认为不是,但我认为您可以在创建代理后自动装配它。
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;
}
}
Other alternative is to create a Spring AOP Proxy (using ProxyFactory
) in postProcessAfterInitialization
method. For this way AbstractAutoProxyCreator
could be useful. See BeanNameAutoProxyCreator as sample. But imho, an annotation pointcut (Nicholas answer) do the same and is simpler.
其他替代方法是ProxyFactory
在postProcessAfterInitialization
方法中创建一个 Spring AOP 代理(使用)。因为这种方式AbstractAutoProxyCreator
可能很有用。请参阅 BeanNameAutoProxyCreator 作为示例。但是恕我直言,注释切入点(尼古拉斯的回答)做同样的事情并且更简单。
回答by ramirezag
InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation
will short-circuit the bean creation approach. The only processing applied is postProcessAfterInitialization
. Which means that, autowiring won't happen because AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues
will never be called. Therefore, you should manually inject or autowire the properties of the proxied beans in postProcessAfterInitialization
method.
InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation
将使 bean 创建方法短路。应用的唯一处理是postProcessAfterInitialization
. 这意味着,自动装配不会发生,因为AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues
永远不会被调用。因此,您应该在postProcessAfterInitialization
method 中手动注入或自动装配代理 bean 的属性。
Question: Does moving the proxying logic in postProcessAfterInitialization
method have an impact to your business requirements? If none, I suggest you do the proxying there.
问题:在postProcessAfterInitialization
方法中移动代理逻辑是否会影响您的业务需求?如果没有,我建议你在那里做代理。
FYI: If you are not building an API, do the annotation approach as suggested by @nicholas.hauschild.
仅供参考:如果您没有构建 API,请按照@nicholas.hauschild 的建议执行注释方法。