簡體   English   中英

春季-攔截Bean創建並注入自定義代理

[英]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)。 但是,如果這些不是我要攔截的方法中的參數,則我無權訪問HttpServletRequestHttpServletResponse對象。

在Spring填充其屬性之前,是否可以在Spring bean創建邏輯中注入另一個回調以注入自己的代理控制器? 我需要能夠訪問HttpServletRequestHttpServletResponse對象,而不管Controller處理程序方法的定義中是否包含它。 作為參數。

注意 @Autowired字段也是一個代理,用@Transactional注釋,因此Spring會對其進行代理。

編輯: AOP解決方案很好地攔截了方法調用,但是如果它們還不是方法參數,我找不到找到HttpServletRequestHttpServletResponse對象的方法。

我可能最終會使用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參數作為傳入參數。

我了解您可能同時使用了HttpServletRequestHttpServletResponse ,因此您應該能夠修改args表達式以接受請求和響應。

考慮到您對問題的評論,您需要的只是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);

我認為不是,但是我認為您可以在創建代理后自動連接代理。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM