|
| 1 | +The `BeanCollection` class in PySpring is designed to help integrate third-party code, especially when developers cannot modify the code directly. Below is a breakdown of its key features and functionality. |
| 2 | + |
| 3 | +### **Purpose and Key Features** |
| 4 | + |
| 5 | +- **Bean Management**: The `BeanCollection` helps organize and manage a collection of beans (components) within a class. It provides a way to access and control these beans efficiently. |
| 6 | + |
| 7 | +- **Scanning for Beans**: The `scan_beans()` method scans the current class for methods that create beans. These methods are identified by their names, which by default start with the identifier `create` (e.g., `createMyBean`). |
| 8 | + |
| 9 | +- **Bean Creation**: Beans are created using methods whose return types are annotated. The return type of a bean creation method indicates the type of the bean being created. |
| 10 | + |
| 11 | +- **BeanView**: Each bean is represented by a `BeanView` object, which holds: |
| 12 | + |
| 13 | + - The bean itself |
| 14 | + - The bean's name |
| 15 | + - The bean creation function |
| 16 | + - A validation method (`is_valid_bean()`) to check if the bean's name matches the name of the class it returns. |
| 17 | +- **Error Handling**: Common errors are handled through exceptions, such as: |
| 18 | + - `BeanConflictError`: Raised when a bean with the same name already exists. |
| 19 | + - `InvalidBeanError`: Raised when the bean's name doesn't match the class name. |
| 20 | + |
| 21 | +* * * * * |
| 22 | + |
| 23 | +### **Properties Integration** |
| 24 | + |
| 25 | +`BeanCollection` can also work with properties, which are loaded **before** the beans are created. This is a key aspect of how `BeanCollection` integrates with the broader PySpring framework. |
| 26 | + |
| 27 | +#### **How Properties Interact with BeanCollection**: |
| 28 | + |
| 29 | +- **Properties Loading**: |
| 30 | + PySpring loads properties from a designated properties file using the `Properties` class and `_PropertiesLoader`. This happens before the initialization of the Inversion of Control (IoC) container. |
| 31 | + |
| 32 | +- **Accessing Properties**: |
| 33 | + Once loaded, properties are accessible via the `get_properties()` method in the `ApplicationContext`. The properties are stored in the `singleton_properties_instance_container`. |
| 34 | + |
| 35 | +- **Dependency Injection**: |
| 36 | + PySpring's dependency injection mechanism ensures that any required properties are injected into a bean before it is created. This is done by inspecting the type annotations of the bean creation function. |
| 37 | + |
| 38 | +#### **How it Works**: |
| 39 | + |
| 40 | +1. **Configuration**: |
| 41 | + The application is configured to load properties from a specific file as defined in `app-config.json`. |
| 42 | + |
| 43 | +2. **Loading Properties**: |
| 44 | + During initialization, `PySpringApplication` uses the `_PropertiesLoader` to load properties into a dictionary. The keys in the dictionary match the keys defined in the `Properties` class. The `load_properties()` method of `ApplicationContext` then makes these properties available. |
| 45 | + |
| 46 | +3. **Bean Creation with Properties**: |
| 47 | + When a bean is created via a method in `BeanCollection`, PySpring checks for any dependencies, including properties. If a property is declared as a dependency, it will be injected into the bean before the bean is created. |
| 48 | + |
| 49 | +#### **Example**: |
| 50 | +Suppose you have a properties class and a bean collection: |
| 51 | +```py |
| 52 | +from py_spring_core import Properties, BeanCollection |
| 53 | + |
| 54 | +class MyProperties(Properties): |
| 55 | + __key__ = "my_properties" |
| 56 | + my_config_value: str |
| 57 | + |
| 58 | +class MyBeanCollection(BeanCollection): |
| 59 | + properties: MyProperties |
| 60 | + @classmethod |
| 61 | + def createMyBean(cls) -> MyBean: |
| 62 | + return MyBean(cls.properties.my_config_value) |
| 63 | + |
| 64 | +class MyBean: |
| 65 | + def __init__(self, config_value: str): |
| 66 | + self.config_value = config_value` |
| 67 | +``` |
| 68 | +In this example: |
| 69 | + |
| 70 | +- `MyBeanCollection` creates a `MyBean` instance and depends on `MyProperties` to fetch a configuration value. |
| 71 | +- When the `ApplicationContext` initializes the bean, it first loads the properties from the properties file and injects the `MyProperties` instance into the `createMyBean` method. |
| 72 | + |
| 73 | +* * * * * |
| 74 | + |
| 75 | +### **How BeanCollection Works** |
| 76 | + |
| 77 | +1. **Identifying Bean Creation Functions**: |
| 78 | + The `scan_beans()` method identifies bean creation methods by checking if their names start with a specific identifier, which is `create` by default. For example, a method named `createMyBean` would be recognized as a bean creation method. |
| 79 | + |
| 80 | +2. **Creating `BeanView` Instances**: |
| 81 | + For each method found, `scan_beans()` creates a `BeanView` instance. This object stores information about the bean, such as the creation function and the bean's class type. |
| 82 | + |
| 83 | +3. **Bean Validation**: |
| 84 | + The `is_valid_bean()` method within `BeanView` checks whether the name of the bean matches the name of the class it is supposed to create. For instance, if a method is annotated to return an object of type `MyBean`, the bean's name must also be `MyBean`. |
| 85 | + |
| 86 | +4. **Dependency Injection**: |
| 87 | + While `BeanCollection` is responsible for creating bean instances, dependency injection ensures that any required dependencies (including properties) are automatically injected into the beans. |
| 88 | + |
| 89 | +* * * * * |
| 90 | + |
| 91 | +### **Integration with PySpring** |
| 92 | + |
| 93 | +- **Registration**: |
| 94 | + `BeanCollection` classes are registered within the `ApplicationContext`. This allows PySpring to manage and inject these beans into other components of the application. |
| 95 | + |
| 96 | +- **Singleton Management**: |
| 97 | + Beans created by `BeanCollection` are treated as singletons by the `ApplicationContext`, meaning that only one instance of each bean is created and reused. |
| 98 | + |
| 99 | +- **Usage**: |
| 100 | + Beans managed by `BeanCollection` can be accessed through the `ApplicationContext` using the `get_bean()` method. |
| 101 | + |
| 102 | +* * * * * |
| 103 | + |
| 104 | +### **Benefits of Using BeanCollection** |
| 105 | + |
| 106 | +- **Loose Coupling**: |
| 107 | + The application code does not need to know how third-party objects are created, promoting separation of concerns and flexibility. |
| 108 | + |
| 109 | +- **Centralized Management**: |
| 110 | + Beans are managed in one central location, making it easier to find and maintain them. |
| 111 | + |
| 112 | +- **Simplified Integration**: |
| 113 | + `BeanCollection` provides a standardized approach to integrating third-party or external code without needing to modify it. |
| 114 | + |
| 115 | +- **Dependency Injection**: |
| 116 | + Beans can take advantage of PySpring's dependency injection, allowing them to access other registered components, controllers, or beans. |
| 117 | + |
| 118 | +- **Properties Support**: |
| 119 | + `BeanCollection` can load properties before bean creation, making it possible to configure beans dynamically using values from external properties files. |
| 120 | + |
| 121 | +* * * * * |
| 122 | + |
| 123 | +### **Beans Interacting with Other Components** |
| 124 | + |
| 125 | +Beans created within a `BeanCollection` can interact with other components and properties defined within the project, not just the properties used during their initial creation. PySpring's dependency injection mechanism allows these beans to access other registered entities (components, beans, and properties) after they are instantiated. |
| 126 | + |
| 127 | +#### **How This Interaction Works**: |
| 128 | + |
| 129 | +- **Dependency Injection**: |
| 130 | + When a bean is created, PySpring examines the type hints of the bean creation method, as well as the class definitions of other components, for type annotations. It uses these annotations to inject the necessary dependencies into the bean, including other beans, properties, and components. |
| 131 | + |
| 132 | +- **Component Interaction**: |
| 133 | + In your example, `ExampleService` has type hints for `ExampleProperties` and `MyBean`. When `ExampleService` is instantiated, PySpring uses dependency injection to inject the correct instances of these dependencies. Additionally, `AnotherExampleService` depends on `ExampleService`, which allows it to access properties via `ExampleService`. |
| 134 | + |
| 135 | +- **Lifecycle Methods**: |
| 136 | + Components like `ExampleService` and `AnotherExampleService` have lifecycle methods (`post_construct` and `pre_destroy`) that PySpring automatically calls. The `post_construct` method is executed after a component is initialized, meaning all its dependencies have been injected. At this point, the component can begin using the injected objects and properties. |
| 137 | + |
| 138 | +```py |
| 139 | +class MyProperties(Properties): |
| 140 | + key = "my_properties" |
| 141 | + my_config_value: str |
| 142 | + |
| 143 | + |
| 144 | +class MyBeanCollection(BeanCollection): |
| 145 | + properties: MyProperties |
| 146 | + |
| 147 | + @classmethod |
| 148 | + def createMyBean(cls) -> MyBean: |
| 149 | + return MyBean(cls.properties.my_config_value) |
| 150 | + |
| 151 | + |
| 152 | +class MyBean: |
| 153 | + def __init__(self, config_value: str): |
| 154 | + self.config_value = config_value |
| 155 | + |
| 156 | + |
| 157 | +class ExampleProperties(Properties): |
| 158 | + key = "example" |
| 159 | + value: str |
| 160 | + |
| 161 | + |
| 162 | +class ExampleService(Component): |
| 163 | + example_properties: ExampleProperties |
| 164 | + my_bean: MyBean |
| 165 | + |
| 166 | + def post_construct(self) -> None: |
| 167 | + logger.info(f"Example value: {self.example_properties.value}") |
| 168 | + |
| 169 | + def pre_destroy(self) -> None: |
| 170 | + logger.info("Pre destroy method called") |
| 171 | + |
| 172 | + |
| 173 | +class AnotherExampleService(Component): |
| 174 | + example_service: ExampleService |
| 175 | + |
| 176 | + def post_construct(self) -> None: |
| 177 | + logger.info("AnotherExampleService post construct called") |
| 178 | + logger.info(f"Example value: {self.example_service.example_properties.value}") |
| 179 | +``` |
| 180 | + |
| 181 | +#### **How It Works in This Example**: |
| 182 | + |
| 183 | +1. **Properties Loading**: |
| 184 | + The application loads properties for `MyProperties` and `ExampleProperties` from the properties file. |
| 185 | + |
| 186 | +2. **Bean Creation**: |
| 187 | + `MyBeanCollection` creates an instance of `MyBean`, injecting `MyProperties` during its creation. |
| 188 | + |
| 189 | +3. **Component Instantiation**: |
| 190 | + PySpring initializes `ExampleService` and `AnotherExampleService`, injecting dependencies via type annotations. |
| 191 | +4. **Dependency Injection**: |
| 192 | + - `ExampleService` receives an instance of `ExampleProperties` and an instance of `MyBean`. |
| 193 | + - `AnotherExampleService` receives an instance of `ExampleService`. |
| 194 | +5. **Lifecycle Hook**: |
| 195 | + - The `post_construct` method in `ExampleService` logs the value of `example_properties.value`, demonstrating that injected properties are available. It also shows that the injected `MyBean` is accessible. |
| 196 | + - The `post_construct` method in `AnotherExampleService` logs the value of the same `example_properties.value` via `example_service`, demonstrating access to an injected component's properties. |
| 197 | + |
| 198 | + |
| 199 | +#### **Key Takeaways**: |
| 200 | +- **Beans Interact with Components**: |
| 201 | + Beans created by a `BeanCollection` can interact with any component registered within the application context. This is because all these classes (beans, components, and controllers) are managed by the application context. |
| 202 | + |
| 203 | +- **Properties Sharing**: |
| 204 | + Properties loaded from a properties file are not only used for bean creation but can also be injected into components. This enables dynamic configuration of the application. |
| 205 | + |
| 206 | +- **Dependency IoC Container**: |
| 207 | + PySpring builds a dependency IoC container for all registered entities. This allows the framework to inject objects into other entities (properties, beans, components) that depend on them. |
| 208 | + |
| 209 | +- **Flexibility**: |
| 210 | + This approach provides a flexible architecture where components and beans can depend on each other, allowing the creation of modular and interconnected applications. |
| 211 | + |
| 212 | +### **Summary** |
| 213 | + |
| 214 | +The `BeanCollection` class provides a structured method to integrate external code into a PySpring application by treating them as managed beans. This approach enhances modularity, maintainability, and reduces tight coupling with third-party code. Additionally, it supports properties loading and dependency injection, allowing for more flexible and configurable bean creation. |
0 commit comments