[英]Spring - Intercepting bean creation and injecting custom proxy
我有一個@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()
因為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(根據我的喜好,有點hacky)。 但是,如果這些不是我要攔截的方法中的參數,則我無權訪問HttpServletRequest
和HttpServletResponse
對象。
在Spring填充其屬性之前,是否可以在Spring bean創建邏輯中注入另一個回調以注入自己的代理控制器? 我需要能夠訪問HttpServletRequest
和HttpServletResponse
對象,而不管Controller處理程序方法的定義中是否包含它。 作為參數。
注意 @Autowired
字段也是一個代理,用@Transactional
注釋,因此Spring會對其進行代理。
編輯: AOP解決方案很好地攔截了方法調用,但是如果它們還不是方法參數,我找不到找到HttpServletRequest
和HttpServletResponse
對象的方法。
我可能最終會使用HandlerInterceptorAdapter,但我希望我可以使用OOP來做到這一點,以免給不需要它的方法增加開銷。
看一下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
也需要在HttpServletRequest
作為參數。 此外,它將將此HttpServletRequest
綁定為Aspect參數作為傳入參數。
我了解您可能同時使用了HttpServletRequest
和HttpServletResponse
,因此您應該能夠修改args
表達式以接受請求和響應。
考慮到您對問題的評論,您需要的只是HandlerInterceptor。
您需要實現該接口並將其添加到您的配置中,例如:
<mvc:interceptors>
<bean id="customInterceptor" class="com.example.interceptors.CustomInterceptor"/>
</mvc:interceptors>
該接口提供了preHanlde方法,該方法具有請求,響應和HandlerMethod。 要檢查該方法是否帶有注釋,請嘗試以下操作:
HandlerMethod method = (HandlerMethod) handler;
OnlyIfXYZ customAnnotation = method.getMethodAnnotation(OnlyIfXYZ.class);
我認為不是,但是我認為您可以在創建代理后自動連接代理。
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;
}
}
另一種選擇是在postProcessAfterInitialization
方法中創建一個Spring AOP代理(使用ProxyFactory
)。 通過這種方式, AbstractAutoProxyCreator
可能會有用。 請參見BeanNameAutoProxyCreator作為示例。 但是,恕我直言,注釋切入點(Nicholas的回答)也一樣並且更簡單。
InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation
將短路bean創建方法。 應用的唯一處理是postProcessAfterInitialization
。 這意味着將不會發生自動裝配,因為永遠不會調用AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues
。 因此,您應該在postProcessAfterInitialization
方法中手動注入或自動連接代理bean的屬性。
問題:在postProcessAfterInitialization
方法中移動代理邏輯是否對您的業務需求有影響? 如果沒有,我建議您在那里進行代理。
僅供參考:如果您沒有構建API,請按照@ nicholas.hauschild建議的方法進行注釋。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.