Beanのライフサイクル-BeanPostProcessorの落とし穴

ピット1:依存BeanはBeanPostProcessorを使用しません

その他のURL

SpringのBeanPostProcessorインターフェースについて話す

前書き

        BeanPostProcessorの実装クラスで、他のBeanが依存している場合、依存Beanが作成されると、BeanPostProcessorの実装クラスによって実装されたメソッドは実行されません。

        BeanPostProcessorの実装クラスは他のBeanに依存しているため、このBeanはPostBeanの前に作成する必要があります。つまり、このBeanが作成されたとき、BeanPostProcessorの実装クラスはまだ初期化されておらず、そのメソッドは呼び出されません。

検証

BeanPostProcessor実装クラス

package com.example.processor;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class MyProcessor implements BeanPostProcessor {
    @Autowired
    MyBean myBean;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof MyBean) {
            System.out.println("postProcessBeforeInitialization==> " + "This is MyBean");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof MyBean) {
            System.out.println("postProcessAfterInitialization==> " + "This is MyBean");
        }
        return bean;
    }
}

依存豆

package com.example.processor;

import org.springframework.stereotype.Component;

@Component
public class MyBean {
}

テスト

起動後、関連する印刷はありません。

@Autowired MyBean myBeanが削除されると、起動後に次のように出力されます。

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.0.RELEASE)

2021-03-05 21:39:47.861  INFO 16100 --- [           main] com.example.DemoApplication              : Starting DemoApplication on DESKTOP-QI6B9ME with PID 16100 (E:\work\Idea_proj\demo_JAVA\demo_SpringBoot\target\classes started by Liu in E:\work\Idea_proj\demo_JAVA\demo_SpringBoot)
2021-03-05 21:39:47.864  INFO 16100 --- [           main] com.example.DemoApplication              : No active profile set, falling back to default profiles: default
2021-03-05 21:39:48.645  INFO 16100 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-03-05 21:39:48.652  INFO 16100 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-03-05 21:39:48.652  INFO 16100 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.35]
2021-03-05 21:39:48.730  INFO 16100 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-03-05 21:39:48.731  INFO 16100 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 825 ms
postProcessBeforeInitialization==> This is MyBean
postProcessAfterInitialization==> This is MyBean
2021-03-05 21:39:48.865  INFO 16100 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-03-05 21:39:48.998  INFO 16100 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-03-05 21:39:49.007  INFO 16100 --- [           main] com.example.DemoApplication              : Started DemoApplication in 1.557 seconds (JVM running for 2.638)

ピット2:AOPを使用できません

その他のURL

SpringBeanPostProcessorインターフェースで話す-Angによるエージェント-自動配線されたブログ
BeanPostProcessorは失敗につながるAOP | Big Box

BeanPostProcessorが信頼を開始するとき「フレンドリーファイア」トラップのBean(すべての人が処理する資格はありません...)_ YourBatman-CSDNブログ
は春の構成事故を覚えています-フライアウンスノー-ブログパーク
ビジネスクラスはAOPプロキシの問題にはなり得ません

理由

簡単な説明

BeanPostProcessorおよび依存BeanはAOPを使用できない場合があります。

現時点では、次のような出力メッセージが表示されます。trationDelegate$ BeanPostProcessorChecker:タイプ[com.example.processor.MyBean]のBean'myBean 'は、すべてのBeanPostProcessorsによる処理の対象ではありません(例:auto-の対象外)プロキシ)

ソースの場所

これはBeanPostProcessorCheckerクラスによって出力されます。ソースコードで検索すると、このクラスはPostProcessorRegistrationDelegateの静的内部クラスであることがわかります。

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) &&
            this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) {
        if (logger.isInfoEnabled()) {
            logger.info("Bean '" + beanName + "' of type [" + bean.getClass().getName() +
                    "] is not eligible for getting processed by all BeanPostProcessors " +
                    "(for example: not eligible for auto-proxying)");
        }
    }
    return bean;
}

このログを印刷する理由

beanFactoryに登録されているBeanPostProcessorsの数がBeanPostProcessorsの総数より少ない場合。つまり、準備ができていない他のポストプロセッサがある場合、このBean(ここではmyBean)がインスタンス化されます。このBeanがインスタンス化される理由は、一般的に次のとおりです。BeanPostProcessorの実装クラスがこのBeanを参照し、これがこのBeanのインスタンス化につながります。

春の公式文書の原文:

BeanPostProcessorインスタンスとAOP自動プロキシ

BeanPostProcessorインターフェースを実装するクラスは特別であり、コンテナーによって異なる方法で処理されます。直接参照するすべてのBeanPostProcessorインスタンスとBeanは、ApplicationContextの特別な起動フェーズの一部として、起動時にインスタンス化されます。次に、すべてのBeanPostProcessorインスタンスがソートされた方法で登録され、コンテナー内の以降のすべてのBeanに適用されます。AOP自動プロキシはBeanPostProcessor自体として実装されているため、BeanPostProcessorインスタンスも、それらが直接参照するBeanも自動プロキシの対象にはならず、したがって、アスペクトが組み込まれていません。

このようなBeanについては、情報ログメッセージが表示されます。BeansomeBeanは、すべてのBeanPostProcessorインターフェースで処理される資格がありません(たとえば、自動プロキシの資格がありません)。

翻訳:

        BeanPostProcessorインターフェースを実装するクラスは特別であり、コンテナーによって異なる方法で処理されます。すべてのBeanPostProcessorインスタンスとそれらの直接参照されるBeanは、ApplicationContextの特別な起動フェーズの一部として、起動時にインスタンス化されます。次に、すべてのBeanPostProcessorインスタンスがソートされた方法で登録され、次のBeanに適用されます。SpringのAOP自動プロキシはBeanPostProcessorインターフェースを実装することによって行われるため、BeanPostProcessorの実装クラスとそれらが直接参照するBeanはAOP自動プロキシの条件を満たしていないため、AOPに組み込むことはできません

        これらのBeanについては、いくつかのINFOレベルの情報が表示されます。BeansomeBeanは、すべてのBeanPostProcessorインターフェースによって処理される資格がありません(たとえば、自動プロキシの資格がありません)。

インスタンス

再発

 BeanPostProcessor実装クラス

        Orderedインターフェースを意図的に実装して、他のBeanPostProcessor実装クラスよりも早くMyProcessorの初期化をシミュレートします。そうしないと、再現が容易になりません。ここでテストしたところ、最高優先または最低優先に指定しましたが、効果は同じでした。優先度については、以下を参照してください。Beanライフサイクル-BeanPostProcessorの概要_feiying0canglangのブログ-CSDNブログ
        さらに、順序を指定するには、ここにOrderedインターフェイスを実装する必要があります。@ Orderを使用すると、無効になります。推測:@OrderもBeanPostProcessorインターフェースを実装することによって行われるため、これも無効です。

package com.example.processor;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
public class MyProcessor implements BeanPostProcessor, Ordered {
    @Autowired
    private MyBean myBean;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof MyBean) {
            System.out.println("postProcessBeforeInitialization==> " + "This is MyBean");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof MyBean) {
            System.out.println("postProcessAfterInitialization==> " + "This is MyBean");
        }
        return bean;
    }

    // 此方法用来测试AOP,作为切点
    public void testAOP() {
        System.out.println("testAOP方法(MyProcessor)");
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

package com.example.processor;

import org.springframework.stereotype.Component;

@Component
public class MyBean {
    // 此方法用来测试AOP,用作切点
    public void testAOP() {
        System.out.println("testAOP方法(MyBean)");
    }
}

セクション

package com.example.processor;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class BeanPostProcessorAspect {
    
	// 此方法织入PostBean的testAOP方法
    @Before("execution(* com.example.processor.MyProcessor.testAOP(..))")
    public void before() {
        System.out.println("before MyProcessor#testAOP");
    }

    // 此方法织入MyBean的testAOP方法
    @Before("execution(* com.example.processor.MyBean.testAOP(..))")
    public void before2() {
        System.out.println("before MyBean#testAOP");
    }
}

ApplicationContextHolderツールクラス

package com.example.processor;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
 
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;
 
    public void setApplicationContext(ApplicationContext context)
            throws BeansException {
        ApplicationContextHolder.context = context;
    }
 
    public static ApplicationContext getContext() {
        return context;
    }
}

テストクラス

package com.example.controller;

import com.example.processor.MyBean;
import com.example.processor.MyProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @Autowired
    MyProcessor myProcessor;

    @Autowired
    MyBean myBean;

    @GetMapping("/test1")
    public String test1() {
        myProcessor.testAOP();
        myBean.testAOP();

        return "test1 success";
    }

    // @GetMapping("/test1")
    // public String test1() {
    //     MyProcessor myProcessor = ApplicationContextHolder.getContext().getBean(MyProcessor.class);
    //     myProcessor.testAOP();
    //     MyBean myBean = ApplicationContextHolder.getContext().getBean(MyBean.class);
    //     myBean.testAOP();
    //     return "test1 success";
    // }
}

テスト

1.AOPができないという現象を再現します

起動:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.0.RELEASE)

2021-03-06 12:23:36.622  INFO 14368 --- [           main] com.example.DemoApplication              : Starting DemoApplication on DESKTOP-QI6B9ME with PID 14368 (E:\work\Idea_proj\demo_JAVA\demo_SpringBoot\target\classes started by Liu in E:\work\Idea_proj\demo_JAVA\demo_SpringBoot)
2021-03-06 12:23:36.625  INFO 14368 --- [           main] com.example.DemoApplication              : No active profile set, falling back to default profiles: default
2021-03-06 12:23:37.237  INFO 14368 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'myBean' of type [com.example.processor.MyBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-03-06 12:23:37.492  INFO 14368 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-03-06 12:23:37.500  INFO 14368 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-03-06 12:23:37.500  INFO 14368 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.35]
2021-03-06 12:23:37.590  INFO 14368 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-03-06 12:23:37.590  INFO 14368 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 927 ms
2021-03-06 12:23:37.718  INFO 14368 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-03-06 12:23:37.847  INFO 14368 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-03-06 12:23:37.855  INFO 14368 --- [           main] com.example.DemoApplication              : Started DemoApplication in 1.553 seconds (JVM running for 2.454)

訪問:http:// localhost:8080 / test1

結果:(BeanPostProcessor実装クラスによって注入された通常のBeanもBeanPostProcessor実装クラスもAOPではありません)説明:コントローラーの2番目のメソッドも同じ結果です。 

testAOP方法(MyProcessor)
testAOP方法(MyBean)

 

2.BeanPostProcessorをAOPに削除します

BeanPostProcessor実装クラスの実装BeanPostProcessorを削除し、オーバーライドされたメソッドを削除します。(その他は同じままです。たとえば、次のようになります。引き続きOrderedインターフェイスを実装します)

結果は次のとおりです(BeanPostProcessor実装クラスとBeanPostProcessor実装クラスによって注入される通常のBeanは両方ともAOPです)

before MyProcessor#testAOP
testAOP方法(MyProcessor)
before MyBean#testAOP
testAOP方法(MyBean)

追跡

このオフィスは上記のコードをトレースします1.AOPが再現できない現象を再現します」、入り口:インスタンス化されているMyBeanのソースコード

Beanのインスタンス化エントリポイントは次のとおりです。AbstractBeanFactory#doGetBean。次の場所に条件付きブレークポイントを作成します。

プロジェクトを開始し、このブレークポイントに3回到達したことを確認します。つまり、MyBeanをインスタンス化する場所が3つあります。

初めて:MyBeanはBeanPostProcessor実装クラスによって参照されます

2回目:HelloControllerはMyBeanを参照します

3回目:MyBean自体がコンテナに参加します。もちろん、インスタンス化する必要があります

解決する

方法1:遅延初期化を使用する

コード

BeanPostProcessorの実装クラスは、次のようにMyBeanを導入します。

@Lazy
@Autowired
private MyBean myBean;

テスト

起動時:

postProcessBeforeInitialization==> This is MyBean
postProcessAfterInitialization==> This is MyBean

訪問:http:// localhost:8080 / test1

結果(BeanPostProcessor実装クラスによって注入された通常のBeanはAOPになる可能性がありますが、BeanPostProcessor実装クラスはAOPではありません)

testAOP方法(MyProcessor)
before MyBean#testAOP
testAOP方法(MyBean)

方法2:ApplicationContextを使用する 

もちろん、この方法をここで適用するのは簡単ではありません。Shiroを構成するときにサービスを注入するために使用できます。

private UserService getUserService() {
    return (UserService) applicationContext.getBean(UserService.class);
}

ピット3:登録方法と制限 

その他のURL

SpringのBeanPostProcessorインターフェースについて話す

前書き

SpringコンテナにBeanPostProcessorを登録する方法は?主に2つの方法があります。

  1. 通常のBeanとして、Spring構成クラスまたはxmlファイルで宣言し、ApplicationContextオブジェクトにロードさせて、コンテナーに自動的に登録されるようにします。また、Springコンテナは、BeanPostProcessorの実装クラスに対して特別な処理を行います。つまり、それらが選択され、BeanPostProcessorの実装クラスが最初にロードされてから、他のBeanがロードされます。
  2. 手動で追加するには、ConfigurableBeanFactoryインターフェースのaddBeanPostProcessorメソッドを使用します。ConfigurableBeanFactoryの実装クラスオブジェクトは、ApplicationContextオブジェクトに結合されます。ただし、このようにBeanPostProcessorを追加すると、次のようないくつかの欠点があります。
    1. Springコンテナーが作成されると、構成ファイル内のシングルトンBeanがロードされます。この時点では、addBeanPostProcessorメソッドは実行されておらず、手動で追加されたBeanPostProcessorはこれらのBeanに作用できないため、手動で追加されたBeanPostProcessorはにのみ作用します。遅延ロードされたBean。、または非シングルトンBean。
    2. Orderedインターフェースの役割は無効になりますが、登録順に実行されます。前述のように、Orderedインターフェースは、複数のBeanPostProcessorsによって実装されるメソッドの実行順序を指定するために使用されます。これは、Springの公式ドキュメントに記載されています。BeanPostProcessor登録の推奨アプローチは(上記のように)ApplicationContext自動検出によるものですが、addBeanPostProcessorメソッドを使用してConfigurableBeanFactoryに対してプログラムで登録することもできます。これは必要な場合に役立ちます。登録前に条件付きロジックを評価するため、または階層内のコンテキスト間でBeanポストプロセッサをコピーする場合。ただし、プログラムで追加されたBeanPostProcessorは、Orderedインターフェイスを尊重しないことに注意してください。ここでは、実行の順序を決定するのは登録の順序です。プログラムで登録されたBeanPostProcessorは、明示的な順序に関係なく、自動検出によって登録されたものよりも常に前に処理されます。

ピット4:@Bean構成の使用に関する制限

その他のURL

SpringのBeanPostProcessorインターフェースについて話す

前書き

JavaクラスでSpringを構成し、@ Beanを使用してBeanインスタンスを返すファクトリメソッドを宣言する場合、戻り値はBeanPostProcessor型、またはBeanPostProcessorよりも小さい型である必要があります。 

デモ 

package com.example.processor;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
public class MyProcessor implements BeanPostProcessor, Ordered {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization==> " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization==> " + beanName);
        return bean;
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

構成クラスでは、MyProcessorを宣言する方法がいくつかあります。 

@Configuration
public class BeanConfig {

	// 方式1:MyProcessor
    @Bean
    public MyProcessor myProcessor() {
        return new MyProcessor();
    }
    
    // 方式2:返回值为BeanPostProcessor
    @Bean
    public BeanPostProcessor myProcessor() {
        return new MyProcessor();
    }
    
    // 方式3:返回值为Ordered
    @Bean
    public Ordered postBean() {
        return new MyProcessor();
    }
}

        上記の3つのメソッドを使用すると、SpringコンテナでMyProcessorインスタンスオブジェクトを作成できます。MyProcessorはBeanPostProcessorインターフェイスとOrderedインターフェイスを実装しているため、そのオブジェクトもこれら2つのタイプのオブジェクトです。ただし、上記の3つのメソッドのうち、1番目と2番目のメソッドのみがSpringコンテナにMyProcessorをBeanPostProcessorとして処理させ、3番目のメソッドはBeanPostProcessorを実装するための通常のBeanとして処理されることに注意してください。どちらのメソッドも呼び出されません。 。MyProcessorの継承システムでは、OrderedとBeanPostProcessorが同じレベルにあるため、SpringはこのOrderedオブジェクト(これもBeanPostProcessorオブジェクト)を認識できませんが、MyProcessorタイプはBeanPostProcessorのサブタイプであるため、MyProcessorを使用できます。したがって、@ Bean宣言ファクトリメソッドを使用してBeanPostProcessor実装クラスオブジェクトを返す場合、戻り値はBeanPostProcessor型または下位レベルの型である必要があります。Springの公式ドキュメントでは、この部分の内容は次のとおりです。

構成クラスで@Beanファクトリメソッドを使用してBeanPostProcessorを宣言する場合、ファクトリメソッドの戻り値の型は実装クラス自体または少なくともorg.springframework.beans.factory.config.BeanPostProcessorインターフェイスである必要があります。そのBeanのポストプロセッサの性質。そうしないと、ApplicationContextは、完全に作成する前にタイプごとに自動検出できません。コンテキスト内の他のBeanの初期化に適用するには、BeanPostProcessorを早期にインスタンス化する必要があるため、この早期の型検出は重要です。

 

おすすめ

転載: blog.csdn.net/feiying0canglang/article/details/114297964
おすすめ