Chapter 02 @Conditional,@Import,@FactoryBean
Section 01 – @Conditional
@Conditional: Optionally inject beans based on conditions
ConditionalBeanConfig added a configuration class ConditionalBeanConfig under config
@Configuration
public class ConditionalBeanConfig {
@Bean("stark")
public Person stark(a){
System.out.println("Stark is instantiated.");
Person person = new Person();
person.setName("stark");
person.setAge(40);
return person;
}
@Bean("peter")
public Person peter(a){
System.out.println("Peter is instantiated");
Person person = new Person();
person.setName("peter");
person.setAge(18);
return person;
}
@Bean("thor")
public Person thor(a){
System.out.println("Thor is instantiated.");
Person person = new Person();
person.setName("thor");
person.setAge(3000);
returnperson; }}Copy the code
ConditionalBeanTest added
public class ConditionalBeanTest {
@Test
public void testConditionalBean(a){
ApplicationContext context = new AnnotationConfigApplicationContext(ConditionalBeanConfig.class);
System.out.println("IoC container initialization completed"); }}Copy the code
Run the test and the console output is as follows
How does the test run instantiate different beans depending on the operating system? That is, how can I optionally inject beans, which requires the @Conditional annotation, and define the selection Condition by implementing the Condition interface
Add two custom Condition classes WinCondition and MacCondition to the config package, which implement the matches method inCondition
public class WinCondition implements Condition {
/** * Filter criteria, return true or false *@paramContext Determines the context in which the condition can be used@paramMetadata annotation information *@return* /
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Check whether it is Windows
// Get the beanFactory being used by the IoC container
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
System.out.println(osName);
// If the condition is met, return true
if (osName.contains("Windows")) {return true;
}
return false; }}Copy the code
public class MacCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name"); System.out.println(osName); if (osName.contains("Mac")){ return true; } return false; }}Copy the code
Tips FactoryBean is different from BeanFactory. BeanFactory: Used to get instantiated beans from the container. ApplicationContext indirectly inherits the BeanFactory
To register into the container in the ConditionalBeanConfig Bean increased conditions @ Conditional comments, said, according to the conditions of selective injection by whatever system into stark, without any conditions, Peter injection will determine the operating system before, Thor is injected only if the operating system is Win and Thor is injected only if the operating system is Mac
@Configuration public class ConditionalBeanConfig { @Bean("stark") public Person stark(){ System.out.println(" Stark is instantiated "); Person person = new Person(); person.setName("stark"); person.setAge(40); return person; } @conditional (wincondition.class) @bean (" Peter ") public Person Peter (){system.out.println (" Peter instantiated ");} @conditional (wincondition.class) @bean (" Peter ") public Person Peter (){system.out.println (" Peter instantiated "); Person person = new Person(); person.setName("peter"); person.setAge(18); return person; } @conditional (macconditional.class) @bean ("thor") public Person thor(){system.out.println (" Thor instantiated ");} @conditional (maccondition.class) @bean ("thor") public Person thor(){system.out.println (" Thor instantiated "); Person person = new Person(); person.setName("thor"); person.setAge(3000); return person; }}Copy the code
Perform the test and check the console print. The conditional injection is successful and only Stark and Thor are injected
Section 02 – @Import
@Import
- Manually add components to the IoC container
- Use ImportSelector to customize the return component, which is the Bean to inject into the container
- Use custom components ImportBeanDefinitionRegistrar return, return the component is to injection container Bean
Several ways to register beans into an IoC container in a configuration class
- The method returns the type of the Bean and the method name defaults to the Bean ID. The Bean ID can also be changed by @bean (“Bean ID”), usually used to import third-party components
- Registered in IoC containers with @ComponentScan + @Component annotations (including @Controller, @Service, @Repository), usually used to import Controllers, The Controller and Service classes in the Service package are commonly used in SSM frameworks to replace the @ComponentScan annotation with a configuration file
- @ Import can quickly into the container, the component registration using the @ Import after Import, the Bean’s default ID for the full path name of a class, can also custom implementations ImportSelector, ImportBeanDefinitionRegistrar will Bean manually register into the container, All registered Bean can use BeanDefinitionRegistry interface, the realization of his class DefaultListableBeanFactory implements registerBeanDefinition () method, the beans into a Map
Add the entity class Role, User to the Entity package, using Lombok’s @data annotation to automatically generate getter/setter/toString methods
@Data
public class User {
private String username;
private String password;
}
Copy the code
@Data
public class Role {
private Long id;
private Long roleId;
private String roleName;
private String roleDesc;
}
Copy the code
Add ImportBeanConfig to the config package. @import is used in config classes where value is an array that can add bytecode to multiple classes
@Configuration
@Import(value = {Role.class, User.class})
public class ImportBeanConfig {
@Bean("stark")
public Person stark(a){
System.out.println("Stark is instantiated.");
Person person = new Person();
person.setName("stark");
person.setAge(40);
returnperson; }}Copy the code
Add the test class ImportBeanTest to get the IDS of all beans in the container and perform tests to view beans in the container
public class ImportBeanTest { @Test public void getBeanByImport(){ ApplicationContext context = new AnnotationConfigApplicationContext(ImportBeanConfig.class); System.out.println("IoC container initialization complete "); String[] beanDefinitionNames = context.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); }}}Copy the code
The console printed successfully, and User and Role were successfully injected into the IoC container
ImportSelectors return an array of the full pathnames of the components to be registered with the IoC container. You need to customize the implementation so you implement selectImports directly in the ImportSelector class without changing anything
public class CustImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return null; }}Copy the code
Add CustImportSelector. Class to the @import annotation in ImportBeanConfig
@Configuration
@Import(value = {Role.class, User.class, CustImportSelector.class})
public class ImportBeanConfig {
@Bean("stark")
public Person stark(a){
System.out.println("Stark is instantiated.");
Person person = new Person();
person.setName("stark");
person.setAge(40);
returnperson; }}Copy the code
Execute ImportBeanTest to see the console print
Plus: Debug Error cause
To set breakpointsStart Debug, then step into
Step into twice in a row. In the figure below, classNames is the array of classNames to return. This is empty because null is returned in custom classes
Step Into twice in a row, and you get to the error,
It can be seen that the root cause of the error is the return of null in the custom class. Therefore, the return of importSelectors cannot be used correctly for the null ImportSelector interface
Modify the importSelectors method in CustImportSelector
public class CustImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.citi.entity.Product"."com.citi.entity.Category"}; }}Copy the code
When executing ImportBeanTest again, Stark is registered into the container with the @bean annotation, Role and User are injected into the container with @import, and Product and Category are registered with the custom class ImportSelect interface
Using a custom class implements ImportBeanDefinitionRegistrar interface into the new one Order entity Bean classes
@Data
public class Order {
private Integer id;
private String orderNo;
private Integer userId;
private Integer totalPrice;
}
Copy the code
Custom class implements ImportBeanDefinitionRegistrar, registerBeanDefinition () method needs to pass in a BeanDefination, BeanDefination is an interface, RootBeanDefinition indirectly inherits BeanDefinition, so you can instantiate a RootBeanDefinition into the registerBeanDefinition() method
public class CustImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
boolean containProduct = registry.containsBeanDefinition("com.citi.entity.Product");
boolean containCategory = registry.containsBeanDefinition("com.citi.entity.Category");
// If both are true, Order is injected
if (containCategory && containProduct){
BeanDefinition orderDefinition = new RootBeanDefinition(Order.class);
registry.registerBeanDefinition("order",orderDefinition); }}}Copy the code
Source DefaultListableBeanFactory realized registerBeanDefinition, will Bean put into a beanDefinitionMapBeanDefinitionMap is a map-type data structure
Use custom CustImportBeanDefinitionRegistrar classes in ImportBeanConfig @ Import annotation on the class to add a custom value in the array
@Configuration
@Import(value = {Role.class, User.class, CustImportSelector.class,CustImportBeanDefinitionRegistrar.class})
public class ImportBeanConfig {
@Bean("stark")
public Person stark(a){
System.out.println("Stark is instantiated.");
Person person = new Person();
person.setName("stark");
person.setAge(40);
returnperson; }}Copy the code
Execute the test that order exists when the instantiated objects of Product and Category exist
Remove the custom ImportSelect class from the value array in the @import annotation on the ImportBeanConfig class, that is, no longer register products and categories in the container
@Configuration
@Import(value = {Role.class, User.class,CustImportBeanDefinitionRegistrar.class})
public class ImportBeanConfig {
@Bean("stark")
public Person stark(a){
System.out.println("Stark is instantiated.");
Person person = new Person();
person.setName("stark");
person.setAge(40);
returnperson; }}Copy the code
At this point, the test is performed to see if there is an order instantiation object in the container
Can be seen that the container does not exist in the Product Category and instantiate objects, also there is no Order to instantiate objects, explain CustImportBeanDefinitionRegistrar conditions of success
Section 03 – FactoryBean
The FactoryBean interface can also implement registering beans to the container, but to implement the interface’s methods,Person is the Bean to import
public class CustFactoryBean implements FactoryBean<Person> {
@Override
public Person getObject(a) throws Exception {
Person person = new Person();
person.setName("peter");
person.setAge(18);
return person;
}
@Override
publicClass<? > getObjectType() {return Person.class;
}
@Override
public boolean isSingleton(a) {
return true; }}Copy the code
There are two ways to use CustFactoryBean, either in the vlaue array of the @import annotation, or by adding methods to the configuration class that return CustFactoryBean, Adding @beans to the method is to inject the custom CustFactoryBean into the container, get the CustFactoryBean from the container, and call the getObejct() method to get the Person Bean
@configuration public class ImportBeanConfig {@bean ("stark") public Person Stark (){system.out.println ("stark instantiated "); Person person = new Person(); person.setName("stark"); person.setAge(40); return person; } @Bean("custFactoryBean") public CustFactoryBean custFactoryBean(){ return new CustFactoryBean(); }}Copy the code
or
@Configuration @Import(value = {CustFactoryBean.class}) public class ImportBeanConfig { @Bean("stark") public Person Stark (){system.out.println (" Stark is instantiated "); Person person = new Person(); person.setName("stark"); person.setAge(40); return person; }}Copy the code
To create a FactoryBeanTest, first get CustFactoryBean and then call the getObject method of the class to get the Person Bean
public class FactoryBeanTest {
@Test
public void getBeanByImport(a){
ApplicationContext context = new AnnotationConfigApplicationContext(ImportBeanConfig.class);
System.out.println("IoC container initialization completed");
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
CustFactoryBean custFactoryBean = (CustFactoryBean) context.getBean(CustFactoryBean.class);
Person person = null;
try {
person = (Person) custFactoryBean.getObject();
} catch(Exception e){ e.printStackTrace(); } System.out.println(person); }}Copy the code
The console successfully prints CustFactoryBean and the Person instantiation object Peter
Modify the isSingleton method of CustFactoryBean from singleton to multi-instance
@Override
public boolean isSingleton(a) {
return false;
}
Copy the code
Add a test method to FactoryBeanTest that takes two Person instantiations to see if they are the same object
@Test public void getMultiInstanceByCustFactoryBean(){ ApplicationContext context = new AnnotationConfigApplicationContext(ImportBeanConfig.class); System.out.println("IoC container initialization complete "); String[] beanDefinitionNames = context.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } CustFactoryBean custFactoryBean = (CustFactoryBean) context.getBean(CustFactoryBean.class); Person person = null; Person person1 = null; try { person = (Person) custFactoryBean.getObject(); person1 = (Person) custFactoryBean.getObject(); } catch (Exception e){ e.printStackTrace(); } System.out.println(person); System.out.println(person == person1); }Copy the code
The console prints false, two Person objects do not want to wait, indicating multiple cases