Spring core concepts and tips

Posted by jaybones on Mon, 27 Dec 2021 19:47:11 +0100

BeanDefinition

BeanDefinition represents the definition of a Bean. There are many attributes in BeanDefinition to describe the characteristics of a Bean. For example:

  • class, indicating the type of bean
  • Scope, indicating the scope of the bean, singleton or prototype
  • lazyInit, indicating whether the bean is lazy to load
  • initMethodName, indicating the method to be executed for bean initialization
  • Destrorymethodname, which indicates the method to execute the bean when the bean is destroyed
  • and more ...

In Spring, we can define bean 1 and xml in the following ways, define a bean 2 through tags, and define bean 2 and annotation in @ Bean, @ Component (@ Service, @ Controller, @ Repository). We call it * * declarative definition Bean * *

We can also define beans programmatically, that is, directly through BeanDefinition, such as:

// Define BeanDefinition
AbstractBeanDefinition beanDefinition =
    BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
// Set scope
beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
// Set bean type
beanDefinition.setBeanClass(BeanTest.class);
// Set lazy load
beanDefinition.setLazyInit(true);

// Register bean
applicationContext.registerBeanDefinition("test", beanDefinition);
Copy code

Similar to declarative transactions and programming transactions, beans defined through, @ Bean, @ Component and other declarative methods will eventually be resolved into the corresponding BeanDefinition object by Spring and put into the Spring container.

BeanDefinitionReader

Beandefinitionreaders are BeanDefinition readers provided in the Spring container. These beandefinitionreaders are rarely used when we use Spring, but they are used more in the Spring source code, which is equivalent to the internal infrastructure of Spring. ​

AnnotationBeanDefinitionReader

The class can be directly converted to BeanDefinition, and the annotations on the class will be parsed, for example:

AnnotationConfigApplicationContext applicationContext =
    new AnnotationConfigApplicationContext(AnnotatedBeanDefinitionReaderTest.class);
AnnotatedBeanDefinitionReader annotationBeanDefinitionReader =
    new AnnotatedBeanDefinitionReader(applicationContext);

annotationBeanDefinitionReader.register(UserService.class);
System.out.println(applicationContext.getBean(UserService.class));
Copy code

AnnotationBeanDefinitionReader

AnnotationConfigApplicationContext applicationContext =
    new AnnotationConfigApplicationContext(XmlBeanDefinitionReaderTest.class);
XmlBeanDefinitionReader xmlBeanDefinitionReader =
    new XmlBeanDefinitionReader(applicationContext);
xmlBeanDefinitionReader.loadBeanDefinitions("classpath:spring-context.xml");

System.out.println(applicationContext.getBean(UserService.class));
Copy code

ClassPathBeanDefinitionScanner

ClassPathBeanDefinitionScanner is a scanner, but its function is similar to BeanDefinitionReader. It can scan, scan a package path, and parse the scanned class. For example, if there is @ Component annotation on the scanned class, the class will be parsed into a BeanDefinition, such as:

AnnotationConfigApplicationContext applicationContext =
    new AnnotationConfigApplicationContext(ClassPathBeanDefinitionScannerTest.class);
ClassPathBeanDefinitionScanner classPathBeanDefinitionScanner =
    new ClassPathBeanDefinitionScanner(applicationContext);
classPathBeanDefinitionScanner.scan("com.summer.test.service");

System.out.println(applicationContext.getBean(UserService.class));
Copy code

BeanFactory

BeanFactory represents a Bean factory, so it is obvious that BeanFactory is responsible for creating beans and providing API s for obtaining beans. SpringApplicationContext is a subclass of BeanFactory, which is defined in the Spring source code as follows:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
		MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

    // ...
}            
Copy code

First of all, in Java, interfaces can be * * multi inherited, * * ApplicationContext inherits ListableBeanFactory and hierarchalbeanfactory, while ListableBeanFactory and hierarchalbeanfactory both inherit from BeanFactory, so we can think that ApplicationContext inherits BeanFactory, which is equivalent to Apple inheriting fruit, BMW inherits from the automobile. ApplicationContext is also a kind of BeanFactory. UFIDA has all the functions supported by BeanFactory. However, ApplicationContext is more powerful than BeanFactory, and ApplicationContext also implements other basic interfaces. For example, MessageSource means internationalization, ApplicationEventPublisher means event publishing, EnvironmentCapable means obtaining environment variables, etc. we will discuss in detail about ApplicationContext later. ​

In the implementation of Spring source code, when we create a new ApplicationContext, the bottom layer will create a new BeanFactory, which is equivalent to using some methods of ApplicationContext, such as getBean(), the getBean of BeanFactory called by the bottom layer. ​

In the Spring source code, there is a very important implementation class for the BeanFactory interface: DefaultLIsttableBeanFactory, which is also very core. ​

Therefore, we can directly use DefaultLIsttableBeanFactory instead of an implementation class of ApplicationContext:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(UserService.class);

beanFactory.registerBeanDefinition("userService", beanDefinition);
System.out.println(beanFactory.getBean("userService"));
Copy code

DefaultLIsttableBeanFactory is very powerful. We can monitor its inheritance relationship:

From the perspective of inheritance relationship, it implements many interfaces and has the following functions: 1. Alias registry: supports alias function. A name can correspond to multiple aliases. 2. BeanDefinitionRegistry: you can register, save, remove, and obtain a BeanDefinition. 3. SingletonBeanFactory: you can register directly to obtain a singleton bean. 4. Simpleliasregistry: it is a mine that implements all the definitions in the alias registry interface and supports the alias function. 5. ListableBeanFactory: Based on BeanFactory, Other functions are added. You can get the definition information of all beandefinitions.

ApplicationContext

AnnotationConfigApplicationContext

ClassPathXmlApplicationContext

internationalization

Define a MessageSource first:

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasename("messages");
    return messageSource;
}
Copy code

After creating this Bean, we can use the Bean object of the MessageSource wherever internationalization is needed. At the same time, because ApplicationContext also has internationalization function, it can be used directly as follows:

context.getMessage("test", null, new Locale("en"));
Copy code

Resource loading

ApplicationContext also has the function of loading resources. For example, you can directly use applicationcoext to obtain the contents of a file:

AnnotationConfigApplicationContext context = new AnnotaionConfigApplicaitonContext(AppConfig.class);
Resource resource = context.getResource("file://C:\\a.txt")
System.out.println(resource.contextLength())
Copy code

Implementing this function through ApplicationConext can improve our development efficiency. For example, we can also use:

AnnotationConfigApplicationContext context = new AnnotaionConfigApplicaitonContext(AppConfig.class);
Resource resource = context.getResource("classpath:spring-context.xml")
System.out.println(resource.contextLength())
    
    
Resource resource2 = context.getResource("https://baidu.com");
System.out.println(resource2.getURL());
Copy code

We can also access multiple resources

AnnotationConfigApplicationContext context = new AnnotaionConfigApplicaitonContext(AppConfig.class);
Resource resource = context.getResource("classpath:spring-context.xml")
System.out.println(resource.contextLength())
    
Copy code

Get runtime environment variables

Map<String, Object> systemEnvironment = context.getEnvironment().getSystemEnvironment();
System.out.println(systemEnvironment);

Map<String, Object> systemProperties = context.getEnvironment().getSystemProperties();
System.out.println(systemProperties);

MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
System.out.println(propertySources);
Copy code

You can also parse files

@PropertySource("classpath:spring.properties")
Copy code

You can add a property file to our environment variable, which can be obtained through the following code:

String abc = context.getEnvironment().getProperty("abc", "1");
System.out.println(abc);
Copy code

Event release

Define events

public class TestEvent extends ApplicationEvent {

	public TestEvent(Object source) {
		super(source);
	}
}
Copy code

Define event listeners

public class TestListener implements ApplicationListener<TestEvent> {

	@Override
	public void onApplicationEvent(TestEvent event) {
		System.out.println("Received an event,,,,,");
	}
}
Copy code

Calling program

@Configuration
public class ApplicationEventTest {

    @Bean
    public ApplicationListener applicationListener() {
        return new TestListener();
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationEventTest.class);
        context.publishEvent(new TestEvent(new Object()));
    }
}

Copy code

Type conversion

In Spring, there are many places where you may need to convert String to other types. Today, let's learn about the use of three types of conversion: PropertyEditor, ConversionService and TypeConverter.

PropertyEditor

PropertyEditor is a type converter provided by JDK. First, create bean s:

@Service
public class OrderService {

	@Value("orderVal")
	private Order order;

	public void test() {
		System.out.println("test order :  " + order);
	}
}

Copy code

Create a type converter to convert a string to an Order instance object.

public class String2ObjectPropertyEditor extends PropertyEditorSupport implements PropertyEditor {

	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		Order order = new Order();
		order.setName("haha");
		order.setAge(12);
		this.setValue(order);
	}
}
Copy code

Register converter and test code:

@Import({OrderService.class})
@Configuration
public class PropertyEditorTest {


	@Bean
	public CustomEditorConfigurer customEditorConfigurer() {
		Map<Class<?>, Class<? extends PropertyEditor>> customEditors = new HashMap<>();
		customEditors.put(Order.class, String2ObjectPropertyEditor.class);

		CustomEditorConfigurer customEditorConfigurer = new CustomEditorConfigurer();
		customEditorConfigurer.setCustomEditors(customEditors);

		return customEditorConfigurer;
	}

	public static void main(String[] args) {

		// Usage 1
		String2ObjectPropertyEditor  propertyEditor = new String2ObjectPropertyEditor();
		propertyEditor.setAsText("1");
		Object value = propertyEditor.getValue();
		System.out.println(value);

		// Usage 2
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(PropertyEditorTest.class);
		OrderService orderItemService = applicationContext.getBean(OrderService.class);
		orderItemService.test();
	}

}
Copy code

ConversionService

ConversionService is a type converter provided in Sprign, which is more powerful than PrppertyEditor. Define converter:

public class String2ObjectConversionService implements ConditionalGenericConverter {

	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
		return
				Objects.equals(sourceType.getType(), String.class)
						&&
						Objects.equals(targetType.getType(), Order.class);
	}

	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
		return Collections.singleton(new ConvertiblePair(String.class, Order.class));
	}


	@Override
	public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		return new Order("haha", 32);
	}
}

Copy code

Use alone

DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new String2ObjectConversionService());
Order order = conversionService.convert("1", Order.class);
System.out.println(order);
Copy code

Use in Spring:

@Bean
public ConversionServiceFactoryBean conversionService() {

    ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
    conversionServiceFactoryBean.setConverters(Collections.singleton(new String2ObjectConversionService()));

    return conversionServiceFactoryBean;
}
Copy code

Bean injection and calling code:

// Testing and injection
@Service
public class OrderService {

    //Inject via @ Value
	@Value("orderVal")
	private Order order;

	public void test() {
		System.out.println("test order :  " + order);
	}
}


// Calling code
ApplicationContext appliciton = new AnnotationConfigApplicationContext(ConvertTest.class);
OrderItemService orderItemService = appliciton.getBean(OrderItemService.class);
orderItemService.test();
Copy code

TypeConverter

TypeConverter integrates PropertyEditor and ConversionService and is used inside Spring:

SimpleTypeConverter typeConverter = new SimpleTypeConverter();
typeConverter.registerCustomEditor(Order.class, new String2ObjectPropertyEditor());
Order order = typeConverter.convertIfNecessary("orderVal", Order.class);
System.out.println(order);
Copy code

For example, in #abstractbeanfactory#adaptbeaninstance #

// AbstractBeanFacotry.java

<T> T adaptBeanInstance(String name, Object bean, @Nullable Class<?> requiredType) {
    // Check if required type matches the type of the actual bean instance.
    // If the conversion type is not empty and the bean type does not match the target type
    if (requiredType != null && !requiredType.isInstance(bean)) {
        try {
            // Try conversion
            Object convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
            if (convertedBean == null) {
                throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
            }
            return (T) convertedBean;
        }
        catch (TypeMismatchException ex) {
            if (logger.isTraceEnabled()) {
                logger.trace("Failed to convert bean '" + name + "' to required type '" +
                             ClassUtils.getQualifiedName(requiredType) + "'", ex);
            }
            throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
        }
    }
    return (T) bean;
}
Copy code

OrderComparator

OrderComparator is a comparator provided by Spring. It can compare according to the @ Order annotation or implement the Ordered interface to sort. For example:

public class A implements Ordered {


	@Override
	public int getOrder() {
		return 1;
	}
}
Copy code
public class B implements Ordered {


	@Override
	public int getOrder() {
		return 2;
	}
}

Copy code

Sort using:

public class OrderComparatorTest {

	public static void main(String[] args) {
		A a = new A();
		B b = new B();


		OrderComparator orderComparator = new OrderComparator();
		System.out.println(orderComparator.compare(a, b)); // -1

		List list = new ArrayList();
		list.add(a);
		list.add(b);

		list.sort(orderComparator);
		System.out.println(list); // a,b
	}
}
Copy code

In addition, Spring also provides a subclass of OrderComparator: * * annotationawarerodecomparator, * * which supports @ order to specify the value of order, such as:

@Order(1)
public class A1 {

}

Copy code
@Order(1)
public class B1 {

}

Copy code
public class OrderComparatorTest1 {

	public static void main(String[] args) {
		A1 a = new A1();
		B1 b = new B1();


		AnnotationAwareOrderComparator orderComparator = new AnnotationAwareOrderComparator();
		System.out.println(orderComparator.compare(a, b)); // -1

		List list = new ArrayList();
		list.add(a);
		list.add(b);

		list.sort(orderComparator);
		System.out.println(list); // a,b
	}
}
Copy code

BeanPostProessor

BeanPostProcessor represents the post processor of a bean. We can define one or more beanpostprocessors, for example, through the following code:

public class TestBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if ("userService".equals(beanName)) {
			System.out.println("postProcessBeforeInitialization userService");
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if ("userService".equals(beanName)) {
			System.out.println("postProcessAfterInitialization userService");
		}
		return bean;
	}
}
Copy code

A BeanPostProcessor can do some additional user-defined logic before and after the initialization of any Bean.

BeanFactoryPostProcessor

BeanFactoryProcessor refers to the post processor of a Bean factory. In fact, it is similar to BeanPostProcessor. BeanPostProcessor interferes with the creation process of a Bean and BeanFactoryProcessor interferes with the creation process of a BeanFactory. We can define a BeanFactoryProcessor as follows:

@Component
public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("bean factory post processor");
	}
}

Copy code

We can extend beanfactory in the postProcessBeanFactory() method.

FactoryBean

As mentioned above, we can interfere with the process of Spring creating beans through BeanPostProcessor, but if we want a Bean to be completely created by us, it is also possible, for example, through FactoryBean.

@Component
public class TestFactoryBean implements FactoryBean {


	@Override
	public Object getObject() throws Exception {
		UserService userService = new UserService();
		return userService;
	}

	@Override
	public Class<?> getObjectType() {
		return UserService.class;
	}
}

Copy code

Through the above code, we can create a UserService object and define it as a Bean, but the Bean defined in this way * * will only go through initialization * *, and other Spring life cycles will not go through, such as dependency injection. ​

An object can also be generated as a Bean through @ Bean. What is the difference between @ Bean and FactoryBean? In fact, in many scenarios, the two cities can be replaced, but in terms of principle, the difference is obvious. The Bean of @ Bean will go through the complete Bean life cycle.

ExcludeFilter and IncluderFilter

These two filters are used for filtering in Spring scanning. ExcludeFilter means exclude Filter and IncluderFilter means include Filter. For example, the following configuration indicates that the scan is on www.com.com All classes returned under this package, but excluding the UserService class, that is, all @ Component annotations above it will not become beans.

@ComponentScan(value = "com.summer.test.service",
excludeFilters = {
		@ComponentScan.Filter(
				type = FilterType.ASSIGNABLE_TYPE,
				classes = UserService.class)
})
public class AppConfig {
}
Copy code

For example, in the following configuration, even if there is no @ Component annotation on the UserService class, it will be scanned into a Bean

@ComponentScan(value = "com.summer.test.service",
includeFilters = {
		@ComponentScan.Filter(
				type = FilterType.ASSIGNABLE_TYPE,
				classes = UserService.class)
})
public class AppConfig {
}
Copy code

FilterType is divided into:

  • ANNOTATION,​
  • ASSIGNABLE_TYPE,​
  • ASPECTJ,
  • REGEX,
  • CUSTOM

MetadataReader/ClassMetaData/AnnotationMetadata

  • Metadata reader metadata reading
  • Metadata information of ClassMetaData class
  • Metadata information of annotation metadata

Test code

SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();

// Construct MetadataReader
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader("com.summer.test.service.UserService");

// Get a ClassMetadata and get the class name
ClassMetadata classMetadata = metadataReader.getClassMetadata();

System.out.println(classMetadata.getClassName());

// Get an AnnotationMetadata and get the annotation information on the class
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

System.out.println(annotationMetadata.hasMetaAnnotation(Component.class.getName()));  //true
System.out.println(annotationMetadata.hasAnnotation(Component.class.getName()));  //false

for (String annotationType : annotationMetadata.getAnnotationTypes()) {
    // org.springframework.stereotype.Service
    System.out.println(annotationType);
}

Topics: Java Spring Back-end