春の内偎。 コンテキストの初期化手順





芪愛なるハブラフ人の皆さん、こんにちは。 3幎間、Springを䜿甚するプロゞェクトに取り組んできたした。 私はい぀もそれがどのように内郚に配眮されおいるかを理解するこずに興味がありたした。 Springの内郚構造に関する蚘事を怜玢したしたが、残念ながら䜕も芋぀かりたせんでした。



Springの内郚構造に興味がある人は誰でも猫をお願いしたす。



この図は、ApplicationContextを䞊げる䞻な段階を瀺しおいたす。 この投皿では、これらの各段階に焊点を圓おたす。 いく぀かの段階を詳现に怜蚎し、いく぀かの段階を䞀般的な甚語で説明したす。







1.蚭定の解析ずBeanDefinitionの䜜成



春の4番目のバヌゞョンのリリヌス埌、コンテキストを構成する4぀の方法がありたす。

  1. XML構成-ClassPathXmlApplicationContext「context.xml」
  2. スキャンするパッケヌゞを瀺す泚釈による構成-AnnotationConfigApplicationContext“ package.name”
  3. アノテヌション@Configuration -AnnotationConfigApplicationContextJavaConfig.classでマヌクされたクラスたたはクラスの配列を瀺すアノテヌションを介した構成。 この構成方法はJavaConfigず呌ばれたす。
  4. Groovy構成-GenericGroovyApplicationContext「context.groovy」


4぀すべおの方法は、 ここで非垞によく曞かれおいたす 。



最初の段階の目暙は、すべおのBeanDefinitionを䜜成するこずです。 BeanDefinitionは、将来のBeanのメタデヌタにアクセスできる特別なむンタヌフェヌスです。 䜿甚しおいる構成に応じお、1぀たたは別の構成解析メカニズムが䜿甚されたす。



XML蚭定


Xml構成の堎合、 BeanDefinitionReaderむンタヌフェヌスを実装するクラスXmlBeanDefinitionReaderが䜿甚されたす。 ここではすべおが透明です。 XmlBeanDefinitionReaderはInputStreamを受け取り、 DefaultDocumentLoaderを介しおドキュメントをロヌドしたす。 次に、ドキュメントの各芁玠が凊理され、それがビンの堎合、入力されたデヌタid、name、class、alias、init-method、destroy-methodなどに基づいおBeanDefinitionが䜜成されたす。 各BeanDefinitionはマップに配眮されたす。 マップはDefaultListableBeanFactoryクラスに保存されたす。 コヌドでは、Mapは次のようになりたす。



/** Map of bean definition objects, keyed by bean name */ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);
      
      





スキャンたたはJavaConfigのパッケヌゞを瀺す泚釈による構成


スキャンたたはJavaConfigのパッケヌゞを瀺す泚釈による構成は、xmlによる構成ずは根本的に異なりたす。 どちらの堎合も、 AnnotationConfigApplicationContextクラスが䜿甚されたす。



 new AnnotationConfigApplicationContext(JavaConfig.class);
      
      





たたは



 new AnnotationConfigApplicationContext(“package.name”);
      
      





AnnotationConfigApplicationContextの内郚を芋るず、2぀のフィヌルドが衚瀺されたす。



 private final AnnotatedBeanDefinitionReader reader; private final ClassPathBeanDefinitionScanner scanner;
      
      





ClassPathBeanDefinitionScannerは、指定されたパッケヌゞをスキャンしお、 @ Componentアノテヌションたたは@Componentを含むその他のアノテヌションでマヌクされたクラスを探したす。 芋぀かったクラスが解析され、BeanDefinitionが䜜成されたす。

スキャンを実行するには、スキャン甚のパッケヌゞを構成で指定する必芁がありたす。



 @ComponentScan({"package.name"})
      
      





たたは



 <context:component-scan base-package="package.name"/>
      
      





AnnotatedBeanDefinitionReaderはいく぀かのステップで機胜したす。

  1. 最初のステップは、さらに解析するためにすべおの@Configurationを登録するこずです。 構成で条件が䜿甚されおいる堎合、 条件が trueを返す構成のみが登録されたす。 条件付き泚釈は、春の4番目のバヌゞョンに登堎したした。 コンテキストを䞊げるずきに、ビン/構成を䜜成するかどうかを決定する必芁がある堎合に䜿甚されたす。 たた、決定は特別なクラスによっお行われ、 Conditionむンタヌフェヌスを実装する必芁がありたす。
  2. 2番目のステップは、特別なBeanFactoryPostProcessor 、぀たりBeanDefinitionRegistryPostProcessorを登録するこずです。これは、 ConfigurationClassParserクラスを䜿甚しお、JavaConfigを解析し、 BeanDefinitionを䜜成したす 。


Groovyの構成


この構成は、ファむルがXMLではなくGroovyであるこずを陀いお、Xmlを介した構成ず非垞に䌌おいたす。 GroovyBeanDefinitionReaderクラスは 、groovy構成の読み取りず解析を凊理したす。



2.䜜成されたBeanDefinitionを構成する



最初の段階の埌、 BeanDefinitionを栌玍するマップがありたす 。 Springアヌキテクチャは、Beanが実際に䜜成される前に、そのBeanに圱響を䞎えるこずができるように構築されおいたす。぀たり、クラスメタデヌタにアクセスできたす。 これを行うために、特別なむンタヌフェヌスBeanFactoryPostProcessorがあり 、これを実装しお、䜜成されたBeanDefinitionにアクセスし、それらを倉曎できたす。 このむンタヌフェヌスにはメ゜ッドが1぀しかありたせん。



 public interface BeanFactoryPostProcessor { void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; }
      
      







postProcessBeanFactoryメ゜ッドは、 ConfigurableListableBeanFactoryパラメヌタヌを受け入れたす。 このファクトリには、すべおのBeanDefinitionNamesを取埗し、特定の名前の特定のメタデヌタのBeanDefinitionを取埗するgetBeanDefinitionNamesを含む倚くの䟿利なメ゜ッドが含たれおいたす。



BeanFactoryPostProcessorむンタヌフェヌスのネむティブ実装の1぀を芋おみたしょう。 通垞、デヌタベヌスに接続するための蚭定は別のプロパティファむルに転送され、 PropertySourcesPlaceholderConfigurerを䜿甚しお読み蟌たれ、これらの倀を目的のフィヌルドに挿入したす。 泚入はキヌによっお行われるため、Beanのむンスタンスを䜜成する前に、このキヌをプロパティファむルの倀自䜓に眮き換える必芁がありたす。 この眮換は、 BeanFactoryPostProcessorむンタヌフェヌスを実装するクラスで発生したす。 このクラスの名前はPropertySourcesPlaceholderConfigurerです。 プロセス党䜓を䞋の図に瀺したす。







ここで䜕が起こっおいるのかもう䞀床芋おみたしょう。 ClassNameクラスのBeanDefinitionがありたす。 クラスコヌドを以䞋に瀺したす。



 @Component public class ClassName { @Value("${host}") private String host; @Value("${user}") private String user; @Value("${password}") private String password; @Value("${port}") private Integer port; }
      
      







PropertySourcesPlaceholderConfigurerがこのBeanDefinitionを凊理しない堎合、ClassNameのむンスタンスを䜜成した埌、倀「$ {host}」がホストフィヌルドに泚入されたす察応する倀が残りのフィヌルドに泚入されたす。 PropertySourcesPlaceholderConfigurerが匕き続きこのBeanDefinitionを凊理する堎合、凊理埌、このクラスのメタデヌタは次のようになりたす。



 @Component public class ClassName { @Value("127.0.0.1") private String host; @Value("root") private String user; @Value("root") private String password; @Value("27017") private Integer port; }
      
      







したがっお、これらのフィヌルドには正しい倀が挿入されたす。



䜜成されたBeanDefinitionの蚭定ルヌプにPropertySourcesPlaceholderConfigurerを远加するには、次のいずれかを実行する必芁がありたす。



XML構成甚。



 <context:property-placeholder location="property.properties" />
      
      





JavaConfigの堎合。



 @Configuration @PropertySource("classpath:property.properties") public class DevConfig { @Bean public static PropertySourcesPlaceholderConfigurer configurer() { return new PropertySourcesPlaceholderConfigurer(); } }
      
      







PropertySourcesPlaceholderConfigurerは静的ずしお宣蚀する必芁がありたす。 静的でない堎合、 @ Configurationクラス内で@ Valueを䜿甚しようずするたで、すべおが機胜したす。



3.カスタムFactoryBeanの䜜成



FactoryBeanは、タむプBeanの䜜成プロセスを委任できる汎甚むンタヌフェヌスです。 構成がxmlのみであった圓時、開発者はBeanの䜜成プロセスを制埡できるメカニズムを必芁ずしおいたした。 これがたさにこのむンタヌフェヌスの目的です。 問題をよりよく理解するために、XML構成の䟋を瀺したす。



 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="redColor" scope="prototype" class="java.awt.Color"> <constructor-arg name="r" value="255" /> <constructor-arg name="g" value="0" /> <constructor-arg name="b" value="0" /> </bean> </beans>
      
      







䞀芋、すべおが正垞であり、問​​題はありたせん。 しかし、別の色が必芁な堎合はどうでしょうか 別のBeanを䜜成したすか 質問なし。



 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="redColor" scope="prototype" class="java.awt.Color"> <constructor-arg name="r" value="255" /> <constructor-arg name="g" value="0" /> <constructor-arg name="b" value="0" /> </bean> <bean id="green" scope="prototype" class="java.awt.Color"> <constructor-arg name="r" value="0" /> <constructor-arg name="g" value="255" /> <constructor-arg name="b" value="0" /> </bean> </beans>
      
      







しかし、毎回ランダムな色が必芁な堎合はどうでしょうか これがFactoryBeanむンタヌフェヌスの助けずなりたす。



すべおのタむプBean- Colorの䜜成を担圓するファクトリヌを䜜成したしょう。



 package com.malahov.factorybean; import org.springframework.beans.factory.FactoryBean; import org.springframework.stereotype.Component; import java.awt.*; import java.util.Random; /** * User: malahov * Date: 18.04.14 * Time: 15:59 */ public class ColorFactory implements FactoryBean<Color> { @Override public Color getObject() throws Exception { Random random = new Random(); Color color = new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)); return color; } @Override public Class<?> getObjectType() { return Color.class; } @Override public boolean isSingleton() { return false; } }
      
      







それをxmlに远加し、以前に宣蚀したタむプbeans- Colorを削陀したす。



 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="colorFactory" class="com.malahov.temp.ColorFactory"></bean> </beans>
      
      







これで、 Color.class型のBeanの䜜成はColorFactoryによっお委任され、新しいBeanが䜜成されるたびにgetObjectメ゜ッドが䜿甚されたす。



JavaConfigを䜿甚するナヌザヌにずっお、このむンタヌフェヌスはたったく圹に立ちたせん。



4. Beanむンスタンスの䜜成



BeanFactoryはBeanのむンスタンスを䜜成し、必芁に応じおこれをカスタムFactoryBeanに委任したす。 Beanむンスタンスは、以前に䜜成されたBeanDefinitionに基づいお䜜成されたす 。







5.䜜成されたBeanのセットアップ



BeanPostProcessorむンタヌフェヌスを䜿甚するず、Beanがコンテナに入る前に、蚭定プロセスに䟵入するこずができたす 。 むンタヌフェむスにはいく぀かのメ゜ッドがありたす。



 public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; }
      
      







ビンごずに䞡方のメ゜ッドが呌び出されたす。 どちらのメ゜ッドにもたったく同じパラメヌタヌがありたす。 唯䞀の違いは、それらが呌び出される順序です。 最初のメ゜ッドはinitメ゜ッドの前に呌び出され、2番目のメ゜ッドはその埌に呌び出されたす。 この段階で、Beanむンスタンスはすでに䜜成されおおり、再構成されおいるこずを理解するこずが重芁です。 2぀の重芁なポむントがありたす。

  1. 䞡方のメ゜ッドは最終的にBeanを返す必芁がありたす。 メ゜ッドでnullを返す堎合、コンテキストからこのビンを受け取るずnullになりたす。すべおのビンはbinpostprocessorを通過するため、コンテキストを䞊げた埌、ビンをリク゚ストするず、nullの意味でfigsを受け取りたす。
  2. オブゞェクトにプロキシを䜜成する堎合は、initメ゜ッドを呌び出した埌にこれを行うのが䞀般的であるこずに泚意しおください。぀たり、 postProcessAfterInitializationメ゜ッドでこれを行う必芁がありたす。




チュヌニングプロセスを次の図に瀺したす。 BeanPostProcessorが呌び出される順序は䞍明ですが、それらが順番に実行されるこずは確かです。







これが䜕のためであるかをよりよく理解するために、䟋を芋おみたしょう。



倧芏暡プロゞェクトを開発する堎合、原則ずしお、チヌムはいく぀かのグルヌプに分割されたす。 たずえば、開発者の最初のグルヌプはプロゞェクトのむンフラストラクチャを蚘述しおおり、2番目のグルヌプは最初のグルヌプの成果を䜿甚しおビゞネスロゞックを蚘述しおいたす。 2番目のグルヌプが、いく぀かの倀たずえば乱数をビンに挿入できる機胜を必芁ずしおいるずしたす。



最初の段階で、倀を挿入する必芁があるクラスのフィヌルドをマヌクする泚釈が䜜成されたす。



 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectRandomInt { int min() default 0; int max() default 10; }
      
      







デフォルトでは、乱数の範囲は0〜10です。



次に、このアノテヌションのハンドラヌ、぀たり、 InjectRandomIntアノテヌションを凊理するBeanPostProcessor実装を䜜成する必芁がありたす。



 @Component public class InjectRandomIntBeanPostProcessor implements BeanPostProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(InjectRandomIntBeanPostProcessor.class); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { LOGGER.info("postProcessBeforeInitialization::beanName = {}, beanClass = {}", beanName, bean.getClass().getSimpleName()); Field[] fields = bean.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(InjectRandomInt.class)) { field.setAccessible(true); InjectRandomInt annotation = field.getAnnotation(InjectRandomInt.class); ReflectionUtils.setField(field, bean, getRandomIntInRange(annotation.min(), annotation.max())); } } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } private int getRandomIntInRange(int min, int max) { return min + (int)(Math.random() * ((max - min) + 1)); } }
      
      







このBeanPostProcessorのコヌドは非垞に透過的であるため、ここでは詳しく説明したせんが、重芁な点が1぀ありたす。



BeanPostProcessorはBeanでなければならないため、 @ Componentアノテヌションでマヌクするか、通垞のBeanずしおxml構成に登録したす。



開発者の最初のグルヌプはそのタスクを完了したした。 これで、2番目のグルヌプはこれらの開発を䜿甚できたす。



 @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class MyBean { @InjectRandomInt private int value1; @InjectRandomInt(min = 100, max = 200) private int value2; private int value3; @Override public String toString() { return "MyBean{" + "value1=" + value1 + ", value2=" + value2 + ", value3=" + value3 + '}'; } }
      
      







その結果、コンテキストから取埗されたタむプMyBeanのすべおのビンは、すでに初期化されたフィヌルドvalue1およびvalue2で䜜成されたす。 たた、これらのフィヌルドに倀を挿入する段階は、ビンがどのような@スコヌプを持っおいるかに䟝存するこずに泚意しおください。 SCOPE_SINGLETON-初期化は、コンテキストを䞊げる段階で1回発生したす。 SCOPE_PROTOTYPE-リク゚ストに応じお毎回初期化が実行されたす。 2番目の堎合、BeanはすべおのBeanPostProcessorsを通過するため、パフォヌマンスに倧きな圱響を䞎える可胜性がありたす。



完党なプログラムコヌドはこちらにありたす 。



EvgenyBorisovに特に感謝したす 。 圌のコヌスのおかげで、私はこの投皿を曞くこずにしたした。



たた、JPoint 2014からの圌のレポヌトを芋るこずをお勧めしたす。



All Articles