Spring BPPで優雅に動的エージェントビーンを作成します.

20872 ワード

はじめに
この記事ではAspectjに基づいていないが、CglibおよびProxyFactoryBeanを通じて直接代理店を作成します.以下の例では、Cglib方式で作成されたプロキシビーコンとProxyFactoryBeanで作成されたプロキシビーコンの違いが見られます.
二、基本テストコード
テストエンティティクラスは、BPPでBppTest DepBeanタイプのプロキシBeanを作成します.
@Component
public static class BppTestBean {
    @Autowired
    private BppTestDepBean depBean;

    public void test1() {
        depBean.testDep();
    }

    public void test2() {
        depBean.testDep();
    }

    @TestMethod
    public void test3() {
        depBean.testDep();
    }
}

@Component
public static class BppTestDepBean {
    public void testDep() {
        System.out.println("HEHE");
    }
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestMethod {
}
テストクラス
@RunWith(SpringRunner.class)
@SpringBootTest
public class BppTest {

    @Autowired
    private BppTestBean bppTestBean;

    @Test
    public void test() {
        bppTestBean.test1();
        bppTestBean.test2();
        bppTestBean.test3();
    }
}
三、Cglibを使って代理ビーンを作成する
public class ProxyBpp1 implements BeanPostProcessor {
    private static final Logger LOGGER = LoggerFactory.getLogger(ProxyBpp1.class);

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof BppTestBean) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(bean.getClass());
            //  Spring-generated proxies
            enhancer.setInterfaces(new Class[]{SpringProxy.class});
            //    
            enhancer.setCallback((MethodInterceptor) (target, method, args, methodProxy) -> {
                if ("test1".equals(method.getName())) {
                    LOGGER.info("ProxyBpp1     ...");
                    Object result = methodProxy.invokeSuper(target, args);
                    LOGGER.info("ProxyBpp1     ...");
                    return result;
                }
                return method.invoke(target, args);
            });

            return enhancer.create();
        }
        return bean;
    }
}
主に代理です. Bpp TestBenのtest 1方法です.実はこのように作成されたエージェントBeanは問題を使って、@Autowiredフィールドに注入されていないので、NPEが現れます.methodProxy.invokeSuper(target,args)は、この行のコードに問題があります.targeは代理の対象です.実際の対象はpostProcess Before Initialization(Object bean,String beanment Name)のbeanオブジェクトです.この時、beanオブジェクト@Autowiredフィールドに登録されました.したがって、methodProxy.invokeSuperをmethod.invokeフィールドに注入できない問題を解決するために修正することができます.
四、ProxyFactoryBenを使用して代理Beanを作成する.
public class ProxyBpp2 implements BeanPostProcessor {
    private static final Logger LOGGER = LoggerFactory.getLogger(ProxyBpp2.class);

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof BppTestBean) {
            ProxyFactoryBean pfb = new ProxyFactoryBean();
            pfb.setTarget(bean);
            pfb.setAutodetectInterfaces(false);
            NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
            advisor.addMethodName("test1");
            advisor.setAdvice((MethodInterceptor) invocation -> {
                LOGGER.info("ProxyBpp2     ...");
                Object result = invocation.getMethod().invoke(invocation.getThis(), invocation.getArguments());
                LOGGER.info("ProxyBpp2     ...");
                return result;
            });
            pfb.addAdvisor(advisor);

            return pfb.getObject();
        }
        return bean;
    }
}
ProxyFactoryBeanを使って代理Beanを作成する場合は、必ず1つのオブジェクトが必要です.Advisorは切り込み時に、Adviceを一つずつ実行します.invocation.get Thisとは、ProxyFactoryBernを通じて代理Beanを作成する際に伝えられたtargetのオブジェクトです.targetオブジェクトはpostProcess Before Initialization(Object bean、String beanName)のbeanオブジェクトであるため、@Autowiredフィールドも既に注入されている.
五、@Autowired注釈はいつ処理されますか?
ご存知のように、@Autowiredフィールドの処理も一つのBPPを通していますが、このBPPは私達が普段使っているものより高級です.それはInstantiationAwareBeanPostProcessorです.このBPPはBeanの作成、属性の注入と解析(例えば@Autowired、@Value、@Resourceなど)を実現できますので、Common AnnotationBeanPostProcessor(JSR-250に関する注釈を処理する)、AutowiredAnnotationBeanPostProcessor(処理@Autowired、Inject@Inject関連注)を参照してください.
InstantiationAwareBeanPostProcessorには、AutowiredAnnotationBeanPostProcessorという方法があります.これをカバーすることで、関連する注釈属性を持つ自動注入が可能になります.
@Nullable
default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
        throws BeansException {

    return null;
}
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        metadata.inject(bean, beanName, pvs);
    }
    catch (BeanCreationException ex) {
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    return pvs;
}
InstantiationAwareBeanPostProcessorのpostProcessメソッドは本当にSpringです. AbstractAutowireCable BeanFactoryのpoputlateBeanメソッドで呼び出されます.AbstractAutowireCable BeanFactoryのdoCreateBanには次のようなコードがあります.
// Initialize the bean instance.
Object exposedObject = bean;#
try {
    populateBean(beanName, mbd, instanceWrapper);
    exposedObject = initializeBean(beanName, exposedObject, mbd);
}
つまり、まずビーンの属性を充填し、ビーンの初期化を行います.initializaBen方法では主に四つのことをしました.
1、invoke AwareMethods 2、appyBenPostProcessors Before Initialization 3、invoke Init Methods 4、appyBenPostProcess AfterInitiazation
その中の2と4はそれぞれ呼び出した普通のBPPの中のpostProcess BeforeInitialization方法とpostProcess After Initialization方法です.
これはなぜBPPにプロキシビーンを作成する際に、対応するターゲットビーンに関する@Autowiredフィールドが注入されているのかという理由です.
六、InstantiationAwareBeanPostProcessor方式のダイナミックプロキシビーンを作成する
InstantiationAwareBeanPostProcessorインターフェイスの中にpostProcess BeforeInstantiationの方法があります.私達自身がBeanを実例化することができます.AbstractAutowireCable BeanFactoryを見ることにより、メソッド呼び出し:createBeanメソッド-> resoveBefore Instantiation方法-> appyBenPostProcessors Before Instantiation方法->InstantiationAwareBenPostProcessor菁postProcess Before Instantiation方法は、最終的に非nullのインスタンスに戻ると、doCrateBean方法を実行しない.これはビーン属性の充填と初期化の流れがないということですが、AbstractAutowireCable BeanFactoryによって実現してくれます.
public  T postProcess(T object) {
    if (object == null) {
        return null;
    }
    T result;
    try {
        //     autowireBeanFactory        autowireBean()   object       
        this.autowireBeanFactory.autowireBean(object);
        //     autowireBeanFactory       initializeBean()      object
        result = (T) this.autowireBeanFactory.initializeBean(object,
                object.toString());
    } catch (RuntimeException e) {
        Class> type = object.getClass();
        throw new RuntimeException(
                "Could not postProcess " + object + " of type " + type, e);
    }
    return result;
}
上の図のコードは、非Spring容器Beanの自動注入と初期化の機能を実現するためにグループを支援することができます.Spring securityを使ったことがありますが、内部もこの方法で対象の属性注入問題を解決しました.もしSpring securityのソースコードを読んだら、WebSecurity、Provider Manager、各セキュリティFilterなどのオブジェクトがたくさん発見されます.これらのオブジェクトの作成はbean定義の形でコンテナに発見されたり、spring容器に登録されたりするのではなく、直接newでできます.Spring securityが提供するAutowireBenFactory Object PostProcessorというツール類は、これらのオブジェクトにコンテナbeanと同じライフサイクルを持たせても、それに応じた依存を注入して、準備ができた状態に入ることができます.
Cglibを使用してInstantiationAwareBenPostProcessorに動的エージェントBeanを作成します.
public class ProxyBpp3 implements InstantiationAwareBeanPostProcessor {
    private static final Logger LOGGER = LoggerFactory.getLogger(ProxyBpp3.class);

    private final AutowireCapableBeanFactory autowireBeanFactory;

    ProxyBpp3(AutowireCapableBeanFactory autowireBeanFactory) {
        this.autowireBeanFactory = autowireBeanFactory;
    }

    @Override
    public Object postProcessBeforeInstantiation(Class> beanClass, String beanName) throws BeansException {
        if (beanClass.equals(BppConfig.BppTestBean.class)) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(beanClass);
            //  Spring-generated proxies
            enhancer.setInterfaces(new Class[]{SpringProxy.class});
            //    
            enhancer.setCallback((MethodInterceptor) (target, method, args, methodProxy) -> {
                if ("test1".equals(method.getName())) {
                    LOGGER.info("ProxyBpp3     ...");
                    Object result = methodProxy.invokeSuper(target, args);
                    LOGGER.info("ProxyBpp3     ...");
                    return result;
                }
                return methodProxy.invokeSuper(target, args);
            });

            return this.postProcess(enhancer.create());
        }
        return null;
    }

    ...
}
ProxyFactoryBeanを使用してInstantiationAwareBeanPostProcessorに動的エージェントBeanを作成します.
public class ProxyBpp4 implements InstantiationAwareBeanPostProcessor {
    private static final Logger LOGGER = LoggerFactory.getLogger(ProxyBpp4.class);

    private final AutowireCapableBeanFactory autowireBeanFactory;

    ProxyBpp4(AutowireCapableBeanFactory autowireBeanFactory) {
        this.autowireBeanFactory = autowireBeanFactory;
    }

    @Override
    public Object postProcessBeforeInstantiation(Class> beanClass, String beanName) throws BeansException {
        if (beanClass.equals(BppConfig.BppTestBean.class)) {
            ProxyFactoryBean pfb = new ProxyFactoryBean();
            pfb.setTarget(this.postProcess(BeanUtils.instantiateClass(beanClass)));
            pfb.setAutodetectInterfaces(false);
            NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
            advisor.addMethodName("test1");
            advisor.setAdvice((MethodInterceptor) invocation -> {
                LOGGER.info("ProxyBpp4     ...");
                Object result = invocation.getMethod().invoke(invocation.getThis(), invocation.getArguments());
                LOGGER.info("ProxyBpp4     ...");
                return result;
            });
            pfb.addAdvisor(advisor);

            return pfb.getObject();
        }
        return null;
    }
    ...
}
上記の2つの方法については、beanを例示した後、自発的にpostProcess方法を通じてAbstractAutowireCable BeanFactoryを介してオブジェクト関連属性の注入およびオブジェクトの初期化プロセスを完了することに注意してください.
七、ソースシェア
ソースコードを確認します.何か質問があれば、公衆番号を見てから相談してください.