目录
Spring AOP
AOP原理
Spring IOC
IOC工作流程
IOC原理
Spring Bean作用域
Spring Bean作用域并发安全
Spring循环依赖
Spring容器获取bean实例
依赖注入 DI
基于field注入
基于Setter注入
基于构造器注入
Spring自带工具类
Spring@Async注解
同步调用和异步调用
Spring实现的线程池
@Async自定义线程池
Spring ORM
Spring Batch
Spring Message
Spring是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与Swing等桌面应用程序AP组合。Spring不仅仅能应用于J2EE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。Spring框架主要由七部分组成,分别是Spring Core、Spring AOP、Spring ORM、 Spring DAO、Spring Context、Spring Web和Spring Web MVC。
Spring | Home
AOP(Aspect Oriented Programming)面向切面思想。Java是一个面向对象(OOP)的编程语言,当需要为多个不具有继承关系的对象引入一个公共行为时,例如日志记录、权限校验、事务管理、统计等功能,只能在每个对象里都引用公共行为,这样做不便于维护,而且有大量重复代码,AOP的出现弥补了OOP的这点不足。
给对象提供代理以控制对这个对象的访问,分为静态代理和动态代理,静态代理就是在程序运行前就已经存在代理类的字节码文件,代理类和原始类的关系在运行前就已经确定。动态代理类的源码是在程序运行期间通过JVM反射等机制动态生成,代理类和委托类的关系是运行时才确定的。JDK代理和Cglib代理两种动态代理,Spring框架在底层都集成了进去,无需担心实现动态生成代理。
Spring是如何生成代理对象的
创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。如果目标对象有实现接口,使用jdk代理。如果目标对象没有实现接口,则使用Cglib代理。然后从容器获取代理后的对象,在运行期植入"切面"类的方法。如果目标类没有实现接口,且class为final修饰的,则不能进行Spring AOP编程。
控制反转(Inversion Of Control),将对象的创建和依赖关系(对象之间的依赖关系可以通过配置文件或者注解来建立)交给第三方容器处理,用的时候告诉容器需要什么然后直接去拿就行了。
Person类作为bean
public class Person {
public void work(){
System.out.println("I am working...");
}
}
applicationContext.xml配置bean
测试类
public class Client {
public static void main(String[] args) {
ClassPathResource classPathResource = new ClassPathResource("applicationContext.xml");//资源定位
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();//创建一个默认的bean工厂
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);//创建Reader用来读取xml文件,将创建的defaultListableBeanFactory作为参数传递给Reader,表示为此工厂创建Reader
beanDefinitionReader.loadBeanDefinitions(classPathResource);//Reader读取配置信息,并将解析的bean定义注册到defaultListableBeanFactory中。
System.out.println(defaultListableBeanFactory.getBeanDefinitionCount());
Person person = (Person)defaultListableBeanFactory.getBean("person");
person.work();
}
}
先获取资源定位,然后创建了一个默认的bean工厂,然后创建了一个Reader用来读取xml文件,将创建的defaultListableBeanFactory作为参数传递给Reader,表示为此工厂创建Reader,用Reader读取配置信息,并将解析的bean定义注册到defaultListableBeanFactory 中。这样bean工厂就正确初始化了,可以调用工厂的getBean()方法获取实例了。Spring用ApplicationContext包含了上面所有步骤,如下:
IOC(Inversion of Control)即控制反转,将对象的创建和依赖关系交给第三方容器处理,当需要某个对象时,容器会把这个对象返回给你,而不需要自己去new出一个对象,对象的创建和管理会由容器自动进行,直接从容器中拿来用就可以了。源码如下:
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
T getBean(String name, @Nullable Class requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
T getBean(Class requiredType) throws BeansException;
T getBean(Class requiredType, Object... args) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, @Nullable Class> typeToMatch) throws NoSuchBeanDefinitionException;
@Nullable
Class> getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
这个接口便是spring核心的bean工厂定义,它是IOC容器的顶层接口,spring中所有bean工厂都直接或间接的实现了这个接口。ApplicationContext接口也继承了BeanFactory接口,因此它具有BeanFactory接口的所有功能。从BeanFactory获取bean时,实例化BeanFactory容器并不会实例化所配置的bean,只有当使用某个bean(getBean)时,才会实例化该bean。ApplicationContext获取bean时,实例化ApplicationContext容器时会一并实例化容器中的所有的bean。BeanFactory的源码可以看出,它实现的核心功能就是根据名称或类型来返回一个bean实例。一个工厂如果要具备这种功能,结合工厂模式的思想,设想它应该需要具备以下几个条件:
1、持有各种bean的定义,只有拿到了bean的定义信息,才能根据这些信息进行实例化。
2、持有各种bean之间的依赖关系,如果一个类中持有对另一个类的引用,那么在对该类进行实例化时,必须根据类之间的依赖关系对相关类也进行实例化,所以工厂必须获得类之间的依赖关系,否则无法正确实例化。
3、以上两种信息都依赖于配置信息定义,比如xml配置文件,工厂需要一个工具来读取配置文件的信息。
以上是我们设想IOC的实现思路,只要满足以上三个条件,就能构造一个工厂,生产各种bean。首先如何持有bean的定义呢?看下面接口:
package org.springframework.beans.factory.config;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.core.AttributeAccessor;
import org.springframework.lang.Nullable;
/**
* 一个BeanDefinition描述一个bean实例具有的属性值,构造函数参数值,以及具体实现的进一步信息。
*
* A BeanDefinition describes a bean instance, which has property values,
* constructor argument values, and further information supplied by
* concrete implementations.
*
*/
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;
void setParentName(@Nullable String parentName);
@Nullable
String getParentName();
void setBeanClassName(@Nullable String beanClassName);
@Nullable
String getBeanClassName();
void setScope(@Nullable String scope);
@Nullable
String getScope();
void setLazyInit(boolean lazyInit);
boolean isLazyInit();
/**
* Set the names of the beans that this bean depends on being initialized.
* The bean factory will guarantee that these beans get initialized first.
*/
void setDependsOn(String... dependsOn);
/**
* Return the bean names that this bean depends on.
*/
@Nullable
String[] getDependsOn();
void setAutowireCandidate(boolean autowireCandidate);
boolean isAutowireCandidate();
void setPrimary(boolean primary);
boolean isPrimary();
void setFactoryBeanName(@Nullable String factoryBeanName);
@Nullable
String getFactoryBeanName();
void setFactoryMethodName(@Nullable String factoryMethodName);
@Nullable
String getFactoryMethodName();
ConstructorArgumentValues getConstructorArgumentValues();
MutablePropertyValues getPropertyValues();
boolean isSingleton();
boolean isPrototype();
boolean isAbstract();
int getRole();
@Nullable
String getDescription();
@Nullable
String getResourceDescription();
@Nullable
BeanDefinition getOriginatingBeanDefinition();
}
BeanDefinition是spring中的bean定义接口,spring工厂里持有的就是此接口定义的内容。这个接口继承了另外两个接口,一个是AttributeAccessor接口,继承这个接口就意味着BeanDefinition接口拥有了处理属性的能力,另外一个接口是BeanMetedataElement,它可以获得bean的配置定义的元素,对于xml文件来说就是会持有bean的标签。BeanDefinition接口定义了两个方法分别是void setDependsOn(String... dependsOn)和String[] getDependsOn(),这两个方法是设置依赖的bean的名称和获取依赖bean的名称,只要有一个BeanDefinition,就能得到得到bean的定义信息和bean之间的依赖关系,从而可以生产一个完整的bean实例。让bean工厂持有一个Map
package org.springframework.beans.factory.support;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Provider;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.beans.factory.CannotLoadBeanClassException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartFactoryBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.NamedBeanHolder;
import org.springframework.core.OrderComparator;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CompositeIterator;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* 基于bean definition对象的完整bean工厂
*
* Default implementation of the
* {@link org.springframework.beans.factory.ListableBeanFactory} and
* {@link BeanDefinitionRegistry} interfaces: a full-fledged bean factory
* based on bean definition objects.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sam Brannen
* @author Costin Leau
* @author Chris Beams
* @author Phillip Webb
* @author Stephane Nicoll
* @since 16 April 2001
* @see StaticListableBeanFactory
* @see PropertiesBeanDefinitionReader
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
*/
@SuppressWarnings("serial")
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
@Nullable
private static Class> javaxInjectProviderClass;
static {
try {
javaxInjectProviderClass =
ClassUtils.forName("javax.inject.Provider", DefaultListableBeanFactory.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - Provider interface simply not supported then.
javaxInjectProviderClass = null;
}
}
/** Map from serialized id to factory instance */
private static final Map> serializableFactories =
new ConcurrentHashMap<>(8);
/** Optional id for this factory, for serialization purposes */
@Nullable
private String serializationId;
/** Whether to allow re-registration of a different definition with the same name */
private boolean allowBeanDefinitionOverriding = true;
/** Whether to allow eager class loading even for lazy-init beans */
private boolean allowEagerClassLoading = true;
/** Optional OrderComparator for dependency Lists and arrays */
@Nullable
private Comparator
DefaultListableBeanFactory类是默认的bean工厂实现类,这里只贴出了部分源码,完整的代码太长。来看其中的一行代码:
/** Map of bean definition objects, keyed by bean name */
//beanFactory持有此map,这样就可以在任何时候获取bean的BeanDefinition来创建一个bean实例
private final Map beanDefinitionMap = new ConcurrentHashMap<>(256);
这行代码证明了我们的猜测,从方法说明就可以看出这是bean定义的map对象,以bean的名称作为key。bean工厂的初始化就是往这个map对象里加东西,把xml文件里定义的bean填充到这个对象里,bean工厂就可以工作了。那么怎样将xml文件配置的bean注册到这个map对象里呢?设想:需要一个工具来找到xml配置文件,可以称之为资源定位,需要一个Reader来读取xml配置信息,即DOM解析,将读取出来的信息注册到map对象里。以代码来验证一下,写一个Person类作为bean:
public class Person {
public void work(){
System.out.println("I am working...");
}
}
applicationContext.xml配置bean:
测试类:
public class Client {
public static void main(String[] args) {
ClassPathResource classPathResource = new ClassPathResource("applicationContext.xml");
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
beanDefinitionReader.loadBeanDefinitions(classPathResource);
System.out.println(defaultListableBeanFactory.getBeanDefinitionCount());
Person person = (Person)defaultListableBeanFactory.getBean("person");
person.work();
}
}
执行结果:
org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
1
I am working...
从结果可以看出,成功解析了xml文件,并注册了一个bean定义,通过getBean()方法成功返回了一个实例。上面的测试类用4行代码实现了bean工厂的初始化:第一行,完成了资源定位。第二行,创建了一个默认的bean工厂。第三行,创建了一个Reader,这个Reader用来读取xml文件,将创建的defaultListableBeanFactory 作为参数传递给Reader,表示为此工厂创建Reader。第四行,用Reader读取配置信息,并将解析的bean定义注册到defaultListableBeanFactory 中。执行完以上四个步骤,bean工厂正确初始化了,接下来可以调用工厂的方法,以及获得bean实例。在实际开发中不会这么复杂,spring可以更简单,它是这么做的
public class TestSpringBeanFactory {
public static void main(String[] args) {
ApplicationContext ctx = new FileSystemXmlApplicationContext("src/applicationContext.xml");
System.out.println(ctx.getBeanDefinitionCount());
Person person = (Person) ctx.getBean("person");
person.work();
}
}
spring用一行代码就完成了上面四个步骤,仔细看日志信息就可以发现,spring也是用XmlBeanDefinitionReader来读取、解析并注册,同时在日志信息里还多了两行,这说明在这一行代码里,spring还做了更多的事情。在new一个FileSystemXmlApplicationContext对象的时候,spring到底做了那些事情?FileSystemXmlApplicationContext类的内容主要是定义了若干重载的构造方法,核心构造方法如下:
/**
* Create a new FileSystemXmlApplicationContext with the given parent,
* loading the definitions from the given XML files.
*
* loading all bean definitions and creating all singletons.
* Alternatively, call refresh manually after further configuring the context.
*
*/
public FileSystemXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
从方法说明可以看出,在这个构造方法里加载所有bean定义并创建bean单例实例。其中的refresh()方法就是IOC容器初始化的入口,refresh()方法位AbstractApplicationContext类中,这是一个抽象类,它实现了ApplicationContext的基础功能,这里使用了模版方法模式,给实现它的子类提供了统一的模板:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.告诉子类刷新内部bean工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
refresh()方法里列出了IOC容器初始化的步骤,第一个方法是初始化准备,这里只是设置启动日期和活动标识以及执行属性源的初始化。重点看第二个方法obtainFreshBeanFactory(),它告诉子类刷新内部bean工厂,返回了一个ConfigurableListableBeanFactory,跟踪这个方法:
/**
* Tell the subclass to refresh the internal bean factory.
* @return the fresh BeanFactory instance
* @see #refreshBeanFactory()
* @see #getBeanFactory()
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
/**
* Return the internal bean factory of this application context.
* Can be used to access specific functionality of the underlying factory.
*
*/
ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
obtainFreshBeanFactory()方法的第一行调用了refreshBeanFactory()方法,这是一个抽象方法,由它的子类来实现,方法的第二行调用了getBeanFactory(),这是在其父接口中定义的一个空方法。抽象方法refreshBeanFactory()在其子类子类AbstractRefreshableApplicationContext中实现:
/**
* This implementation performs an actual refresh of this context's underlying
* bean factory, shutting down the previous bean factory (if any) and
* initializing a fresh bean factory for the next phase of the context's lifecycle.
*
* 此实现执行该上下文的底层bean工厂的实际刷新,关闭以前的bean工厂(如果有的话),
* 并为上下文生命周期的下一阶段初始化一个新的bean工厂
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
这个方法被final关键字修饰,也就是说不可以被重写,IOC容器的初始化就是在这个方法中完成的。第一步先判断有没有现有的工厂,有的话就销毁掉,然后创建一个默认的工厂,也就是DefaultListableBeanFactory ,接下来两行代码是设置bean工厂的一些属性,注意看loadBeanDefinitions(beanFactory)这行,当创建了一个默认的bean工厂后,加载bean定义,接下来跟踪一下loadBeanDefinitions(beanFactory)的实现,这个方法是由AbstractXmlApplicationContext抽象类实现的:
/**
* Loads the bean definitions via an XmlBeanDefinitionReader.装载bean定义通过XmlBeanDefinitionReader
*
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
方法的第一行首先定义了一个Reader,这个Reader就是用来读取xml配置文件的,最后一行就是真正载入bean定义的实现过程,代码如下:
/**
* Load the bean definitions with the given XmlBeanDefinitionReader.
*
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
上面的方法调用了XmlBeanDefinitionReader类的loadBeanDefinitions(EncodedResource encodedResource)方法:
/**
* Load bean definitions from the specified XML file.
* rows BeanDefinitionStoreException in case of loading or parsing errors
*/
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
从方法说明可以看出,这个方法是从指定的xml文件中加载bean定义,try块中的代码才是载入bean定义的过程。spring将资源返回的输入流包装以后传给了doLoadBeanDefinitions()方法,进入这个方法,代码如下:
/**
* Actually load bean definitions from the specified XML file.
*
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
/**
* Actually load the specified document using the configured DocumentLoader.
*
*/
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
spring使用documentLoader将资源转换成了Document资源,spring使用的documentLoader为DefaultDocumentLoader,loadDocument方法定义在此类中:
/**
* Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
* XML parser.
*/
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
这就是DOM解析xml了,可以想象spring是根据XSD文件规定的格式解析了xml文件的各节点及属性。再回头看看registerBeanDefinitions(doc, resource)方法,
/**
* Register the bean definitions contained in the given DOM document.
* Called by {@code loadBeanDefinitions}.
*
*/
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
方法说明很明确的告诉我们,这个方法是注册给定的DOM文档中包含的bean定义。到这里思路就很明确了,spring将包装的输入流解析成DOM文档,然后将DOM中包含的bean定义信息注册到IOC容器持有的Map
Sping中bean的scope的值可以是singleton、prototype、request、session、global session。默认情况下是singleton。只有在web容器中才能使用request、session、global session。
singleton:单例模式,spring容器中有且仅有一个对象,init方法在创建容器时仅执行一次。关闭容器会导致bean实例的销毁,调用destroy方法一次。当spring创建applicationContext容器的时候,spring会初始化所有的该作用域实例,加上lazy-init就可以避免预处理。
prototype:原型模式,spring容器要创建同一类型的多个对象,对象的销毁由垃圾回收机制gc()控制,destroy方法将不会被执行,init方法在每个对象创建时均执行一次。每次通过getBean获取该bean就会新产生一个实例,创建后spring将不再对其管理。
request:每次请求都新产生一个实例,和prototype不同的是创建后接下来的管理spring依然在监听。
session:每次会话,同上。
global session:全局的web域,类似于servlet中的application。
创建两个方法
import com.javaxxf.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public void saveUser() {
System.out.println("Hello Spring...");
}
//初始化方法
public void init(){
System.out.println("bean创建了,我要干嘛干嘛....");
}
//销毁f方法
public void destroy(){
System.out.println("bean销毁了,我要干嘛干嘛....");
}
}
修改配置文件
测试
public class UserController {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
UserService userService1= (UserService) ac.getBean("userService");
UserService userService2= (UserService) ac.getBean("userService");
UserService userService3= (UserService) ac.getBean("userService");
}
}
结果:
当scope=“singleton”时,spring容器中有且仅有一个对象,init方法在创建容器时仅执行一次
把scope=设置为prototype
结果
当scope=“prototype”时,对象的销毁由垃圾回收机制gc()控制,destroy方法将不会被执行。
Spring的bean默认是单例,在Controller中定义成员变量,多个请求进来,进入的都是同一个单例的Controller对象,并对此成员变量的值进行修改操作,多个请求互相影响,导致并发安全问题。
@Controller
public class HomeController {
private int i;
@GetMapping("testsingleton1")
@ResponseBody
public int test1() {
return ++i;
}
}
多次访问此url,可以看到每次的结果都是自增的,所以这样的代码显然是并发不安全的。
解决方案
让无状态的海量Http请求之间不受影响,可以采取以下几种措施:
1.单例变原型:对web项目可以Controller类上加注解@Scope("prototype")
或@Scope("request")
,对非web项目,在Component类上添加注解@Scope("prototype")
。
优点:实现简单。
缺点:很大程度上增大了bean创建实例化销毁的服务器资源开销。线程隔离类ThreadLocal
2.线程隔离类ThreadLocal:将成员变量包装为ThreadLocal,以试图达到并发安全,同时打印出Http请求的线程名,修改代码如下:
@Controller
public class HomeController {
private ThreadLocal i = new ThreadLocal<>();
@GetMapping("testsingleton1")
@ResponseBody
public int test1() {
if (i.get() == null) {
i.set(0);
}
i.set(i.get().intValue() + 1);
log.info("{} -> {}", Thread.currentThread().getName(), i.get());
return i.get().intValue();
}
}
多次访问此url测试一把,打印日志如下
[INFO ] 2019-12-03 11:49:08,226 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-1 -> 1
[INFO ] 2019-12-03 11:49:16,457 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-2 -> 1
[INFO ] 2019-12-03 11:49:17,858 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-3 -> 1
[INFO ] 2019-12-03 11:49:18,461 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-4 -> 1
[INFO ] 2019-12-03 11:49:18,974 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-5 -> 1
[INFO ] 2019-12-03 11:49:19,696 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-6 -> 1
[INFO ] 2019-12-03 11:49:22,138 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-7 -> 1
[INFO ] 2019-12-03 11:49:22,869 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-9 -> 1
[INFO ] 2019-12-03 11:49:23,617 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-8 -> 1
[INFO ] 2019-12-03 11:49:24,569 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-10 -> 1
[INFO ] 2019-12-03 11:49:25,218 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-1 -> 2
[INFO ] 2019-12-03 11:49:25,740 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-2 -> 2
[INFO ] 2019-12-03 11:49:43,308 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-3 -> 2
[INFO ] 2019-12-03 11:49:44,420 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-4 -> 2
[INFO ] 2019-12-03 11:49:45,271 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-5 -> 2
[INFO ] 2019-12-03 11:49:45,808 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-6 -> 2
[INFO ] 2019-12-03 11:49:46,272 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-7 -> 2
[INFO ] 2019-12-03 11:49:46,489 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-9 -> 2
[INFO ] 2019-12-03 11:49:46,660 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-8 -> 2
[INFO ] 2019-12-03 11:49:46,820 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-10 -> 2
[INFO ] 2019-12-03 11:49:46,990 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-1 -> 3
[INFO ] 2019-12-03 11:49:47,163 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
http-nio-8080-exec-2 -> 3
......
从日志分析,二十多次的连续请求得到的结果有1有2有3,而我们期望不管我并发请求有多少,每次的结果都是1;web服务器默认的请求线程池大小为10,这10个核心线程可以被之后不同的Http请求复用,所以这也是为什么相同线程名的结果不会重复的原因。总结:ThreadLocal的方式可以达到线程隔离,但还是无法达到并发安全。
3.尽量避免使用成员变量
将成员变量替换为RequestMapping方法中的局部变量,代码修改如下:
@Controller
public class HomeController {
@GetMapping("testsingleton1")
@ResponseBody
public int test1() {
int i = 0;
// TODO biz code
return ++i;
}
}
但当很少的某种情况下,必须使用成员变量呢,该怎么处理?
4.使用并发安全的类
可以考虑使用并发安全的容器,如ConcurrentHashMap、ConcurrentHashSet等,将成员变量放到这些并发安全的容器中进行管理即可。
5.分布式或微服务的并发安全
如果是微服务或分布式服务的影响,方式4处理不了,可以借助于某些分布式缓存中间件如Redis等来保证同一种服务的不同服务实例都拥有同一份共享信息。
什么是循环依赖
一个或多个对象之间存在直接或间接的依赖关系,这种依赖关系构成一个环形调用,有下面 3 种方式。
情况2的例子
@Service
public class Louzai1 {
@Autowired
private Louzai2 louzai2;
public void test1() {
}
}
@Service
public class Louzai2 {
@Autowired
private Louzai1 louzai1;
public void test2() {
}
}
会有什么问题呢
对象的创建过程会产生死循环。
Spring如何解决的
spring内部的三级缓存来解决的,
第一级缓存:singletonObjects,用于保存实例化、注入、初始化完成的 bean 实例。
第二级缓存:earlySingletonObjects,用于保存实例化完成的 bean 实例。
第三级缓存:singletonFactories,用于保存 bean 创建工厂,以便后面有机会创建代理对象。
执行逻辑:先从“第一级缓存”找对象,有就返回,没有就找“二级缓存”;找“二级缓存”,有就返回,没有就找“三级缓存”;找“三级缓存”,找到了,就获取对象,放到“二级缓存”,从“三级缓存”移除。
执行流程
在第一层中,先去获取 A 的 Bean,发现没有就准备去创建一个,然后将 A 的代理工厂放入“三级缓存”(这个 A 其实是一个半成品,还没有对里面的属性进行注入),但是 A 依赖 B 的创建,就必须先去创建 B;
在第二层中,准备创建 B,发现 B 又依赖 A,需要先去创建 A;
在第三层中,去创建 A,因为第一层已经创建了 A 的代理工厂,直接从“三级缓存”中拿到 A 的代理工厂,获取 A 的代理对象,放入“二级缓存”,并清除“三级缓存”;
回到第二层,现在有了 A 的代理对象,对 A 的依赖完美解决(这里的 A 仍然是个半成品),B 初始化成功;
回到第一层,现在 B 初始化成功,完成 A 对象的属性注入,然后再填充 A 的其它属性,以及 A 的其它步骤(包括 AOP),完成对 A 完整的初始化功能(这里的 A 才是完整的 Bean)。
将 A 放入“一级缓存”。
为什么要用3级缓存?先看源码如下(Spring 的版本是 5.2.15.RELEASE)
这里需要多跑几次,把前面的beanName跳过去,只看 louzai1。
第一层
进入 doGetBean(),从 getSingleton() 没有找到对象,进入创建 Bean 的逻辑。
进入 doCreateBean() 后,调用 addSingletonFactory()。
往三级缓存 singletonFactories 塞入 louzai1 的工厂对象。
进入到 populateBean(),执行 postProcessProperties(),这里是一个策略模式,找到下图的策略对象。
正式进入该策略对应的方法。
下面都是为了获取 louzai1 的成员对象,然后进行注入。
进入 doResolveDependency(),找到 louzai1 依赖的对象名 louzai2
需要获取 louzai2 的 bean,是 AbstractBeanFactory 的方法。
正式获取 louzai2 的 bean。
到这里,第一层套娃基本结束,因为 louzai1 依赖 louzai2,下面我们进入第二层套娃。
第二层
获取 louzai2 的 bean,从 doGetBean(),到 doResolveDependency(),和第一层的逻辑完全一样,找到 louzai2 依赖的对象名 louzai1。
前面的流程全部省略,直接到 doResolveDependency()。
正式获取 louzai1 的 bean。
到这里,第二层套娃结束,因为 louzai2 依赖 louzai1,所以我们进入第三层套娃。
第三层
获取 louzai1 的 bean,在第一层和第二层中,我们每次都会从 getSingleton() 获取对象,但是由于之前没有初始化 louzai1 和 louzai2 的三级缓存,所以获取对象为空。
到了第三层,由于第三级缓存有 louzai1 数据,这里使用三级缓存中的工厂,为 louzai1 创建一个代理对象,塞入二级缓存。
这里就拿到了 louzai1 的代理对象,解决了 louzai2 的依赖关系,返回到第二层。
返回第二层后,louzai2 初始化结束,二级缓存的数据,什么时候会给到一级呢?会通过 createBean() 创建一个 louzai2 的 bean,当 louzai2 的 bean 创建成功后,执行getSingleton(),它会对 louzai2 的结果进行处理。
进入 getSingleton(),会看到下面这个方法。
这里就是处理 louzai2 的 一、二级缓存的逻辑,将二级缓存清除,放入一级缓存。
返回第一层
louzai1初始化完毕后,会把 louzai1 的二级缓存清除,将对象放入一级缓存。
到这里,所有的流程结束,返回 louzai1对象。为什么要有三级缓存呢?
先说“一级缓存”的作用,变量命名为 singletonObjects,结构是 Map
直接找到创建 A 对象时,把实例化的 A 对象存入“三级缓存”的代码。
下面我们主要看这个对象工厂是如何得到的,进入 getEarlyBeanReference() 方法。
这个对象工厂的作用是:如果A有 AOP,就创建一个代理对象,如果A没有AOP,就返回原对象。二级缓存的作用是用来存放对象工厂生成的对象,这个对象可能是原对象,也可能是个代理对象。
什么要这样设计呢?把二级缓存干掉不行么
@Service
public class A {
@Autowired
private B b;
@Autowired
private C c;
public void test1() {
}
}
@Service
public class B {
@Autowired
private A a;
public void test2() {
}
}
@Service
public class C {
@Autowired
private A a;
public void test3() {
}
}
A需要找B和C,但是B需要找A,C也需要找A 。假如A需要进行AOP,因为代理对象每次都是生成不同的对象,如果干掉第二级缓存,只有第一、三级缓存:
B 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A1。
C 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A2。
通过 A 的工厂的代理对象,生成了两个不同的对象 A1 和 A2,所以为了避免这种问题的出现,搞个二级缓存,把 A1 存下来,下次再获取时,直接从二级缓存获取,无需再生成新的代理对象。所以“二级缓存”的目的是为了避免因为 AOP 创建多个对象,其中存储的是半成品的 AOP 的单例 bean。所以啊三级缓存的作用就是
一级缓存:为“Spring 的单例属性”而生,就是个单例池,用来存放已经初始化完成的单例 Bean;
二级缓存:为“解决 AOP”而生,存放的是半成品的 AOP 的单例 Bean;
三级缓存:为“打破循环”而生,存放的是生成半成品单例 Bean 的工厂方法
使用spring框架的过程中获取bean是非常常见的操作,此处列举了七种获取方式:
1.使用BeanFactory
从工厂中直接获取Bean实例,但是XmlBeanFactory
类已经废弃,不建议使用
@Test
public void getBeanTest1() {
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
UserInfo userInfo = (UserInfo) beanFactory.getBean("userInfo");
System.out.println(userInfo);
}
2.通过ApplicationContext
对象获取Bean
@Test
public void getBeanTest2() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInfo userInfo = (UserInfo) applicationContext.getBean("userInfo");
System.out.println(userInfo);
}
3.继承抽象类ApplicationObjectSupport
并将自己继承的类注入到Spring容器中,继承ApplicationObjectSupport来获取ApplicationContext
@Test
public void getBeanTest3() {
ApplicationContextUtil2 applicationContextUtil2 = (ApplicationContextUtil2) ApplicationContextUtil.getBean("applicationContextUtil2");
UserInfo userInfo = (UserInfo) applicationContextUtil2.getBean("userInfo");
System.out.println(userInfo);
}
//ApplicationContextUtil2
public class ApplicationContextUtil2 extends ApplicationObjectSupport {
/**
* 通过bean的id获取bean对象
* @param beanName
* @return
*/
public Object getBean(String beanName){
return super.getApplicationContext().getBean(beanName);
}
}
将Bean注入到Spring容器中,通过注解,或者配置均可
4.继承抽象类WebApplicationObjectSupport
并将自己继承的类注入到Spring容器中
@Test
public void getBeanTest4() {
ApplicationContextUtil3 applicationContextUtil3 = (ApplicationContextUtil3) ApplicationContextUtil.getBean("applicationContextUtil3");
UserInfo userInfo = (UserInfo) applicationContextUtil3.getBean("userInfo");
System.out.println(userInfo);
}
public class ApplicationContextUtil3 extends WebApplicationObjectSupport{
/**
* 通过bean的id获取bean对象
* @param beanName
* @return
*/
public Object getBean(String beanName){
return super.getWebApplicationContext().getBean(beanName);
}
}
5.使用Spring提供的工具类WebApplicationContextUtils
来获取WebApplicationContext
对象,这个方法很常见于SpringMVC构建的web项目中
@Test
public void getBeanTest5(){
//模拟ServletContext上下文,不然会出现空指针异常
MockServletContext sc = new MockServletContext("");
sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, "/applicationContext.xml");
ServletContextListener listener = new ContextLoaderListener();
ServletContextEvent event = new ServletContextEvent(sc);
listener.contextInitialized(event);
//使用WebApplicationContextUtils的getRequiredWebApplicationContext方法
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
UserInfo userInfo = (UserInfo) webApplicationContext.getBean("userInfo");
System.out.println(userInfo);
//使用WebApplicationContextUtils的getWebApplicationContext方法
WebApplicationContext webApplicationContext2 = WebApplicationContextUtils.getWebApplicationContext(sc);
UserInfo userInfo2 = (UserInfo) webApplicationContext2.getBean("userInfo");
System.out.println(userInfo2);
}
6.通过实现ApplicationContextAware
接口,在Spring容器启动的时候将ApplicationContext
注入进去,从而获取ApplicationContext
对象
@Test
public void getBeanTest6(){
UserInfo userInfo2 = (UserInfo) ApplicationContextUtil.getBean("userInfo");
System.out.println(userInfo2);
}
public class ApplicationContextUtil implements ApplicationContextAware{
private static ApplicationContext applicationContext;
/**
* 通过bean的id获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return applicationContext.getBean(beanName);
}
/**
* 根据bean的id和类型获取bean对象
* @param beanName
* @param clazz
* @param
* @return
*/
public static T getBean(String beanName,Class clazz){
return clazz.cast(getBean(beanName));
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
7.使用ContextLoader
提供的getCurrentWebApplicationContext
方法提供的方法也是常用的获取WebApplicationContext
的一种方法,这个方法常见于SpringMVC实现的web项目中
@Test
public void getBeanTest7() {
MockServletContext sc = new MockServletContext("");
sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, "/applicationContext.xml");
ServletContextListener listener = new ContextLoaderListener();
ServletContextEvent event = new ServletContextEvent(sc);
listener.contextInitialized(event);
//如果不加上面的模拟创建ServletContext对象,会报空指针异常
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
UserInfo userInfo = (UserInfo) wac.getBean("userInfo");
System.out.println(userInfo);
}
Spring支持使用@Autowired、
@Resource
、@Inject
三个注解进行依赖注入。
@Component(“id”) id可选,告诉spring这是一个组件,交由spring管理, 相当于xml当中的
@Autowired 默认按类型进行装配(该注解由spring提供,org.springframework.beans.factory.annotation.Autowired
),默认情况下要求依赖对象必须存在,如果不存在会抛出异常,要想允许依赖对象为null值,可以设置required属性为false,@Autowired(required=false),如果使用名称装配,可以结合使用@Qualifier,@Autowired@Qualifier(“name”)。
@Resource(该注解由J2EE提供),默认按照名称进行装配,名称可以通过name属性进行指定,如果未指定name属性,当注解写在字段上时,默认取字段名查找,当找不到名称匹配的bean时,才按照类型进行装配。但是如果name属性一旦指定,将只会按照名称进行装配。
public interface Svc {
void sayHello();
}
@Service
public class SvcA implements Svc {
@Override
public void sayHello() {
System.out.println("hello, this is service A");
}
}
@Service
public class SvcB implements Svc {
@Override
public void sayHello() {
System.out.println("hello, this is service B");
}
}
@Service
public class SvcC implements Svc {
@Override
public void sayHello() {
System.out.println("hello, this is service C");
}
}
@SpringBootTest
public class SimpleTest {
@Autowired
// @Qualifier("svcA")
Svc svc;
@Test
void rc() {
Assertions.assertNotNull(svc);
svc.sayHello();
}
}
装配顺序:@Autowired默认按照type
在上下文中查找匹配的bean,查找type为Svc的bean
,如果有多个bean则按照name
进行匹配,如果有@Qualifier
注解,则按照@Qualifier
指定的name
进行匹配,查找name为svcA的bean
,如果没有,则按照变量名进行匹配,查找name为svcA的bean。
匹配不到,则报错。@Autowired(required=false)
如果设置required
为false
(默认为true
),则注入失败时不会抛出异常。
@Inject
和@Autowired
是相同的,因为它们的依赖注入都是使用AutowiredAnnotationBeanPostProcessor
来处理的。@Inject
是JSR-330定义的规范,@Inject
是Java EE包里的。@Autowired
可以设置required=false
而@Inject
没有这个属性。
@Resource
是JSR-250定义的注解。Spring 在 CommonAnnotationBeanPostProcessor
实现了对JSR-250
的注解的处理,其中就包括@Resource
。
装配顺序:@Resource如果没有指定name或者type
默认先按照name
从Spring上下文中查找如果没找到就按照type查找
匹配的bean进行装配,找不到则抛出异常。如果指定了name
,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。如果指定了type
,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
使用IDEA进行Spring开发,当在字段上面使用@Autowired
注解时,IDEA会有警告提示:
Field injection is not recommended
Inspection info: Spring Team Recommends: "Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies".
译为:
不建议使用基于field的注入方式。
Spring开发团队建议:在你的Spring Bean永远使用基于constructor的方式进行依赖注入。对于必须的依赖,永远使用断言来确认。
@Service
public class HelpService {
@Autowired
@Qualifier("svcB")
private Svc svc;
public void sayHello() {
svc.sayHello();
}
}
public interface Svc {
void sayHello();
}
@Service
public class SvcB implements Svc {
@Override
public void sayHello() {
System.out.println("hello, this is service B");
}
}
将光标放到@Autowired
处,使用Alt + Enter
快捷进行修改之后,代码就会变成基于Constructor的注入方式。
@Service
public class HelpService {
private final Svc svc;
@Autowired
public HelpService(@Qualifier("svcB") Svc svc) {
// Assert.notNull(svc, "svc must not be null");
this.svc = svc;
}
public void sayHello() {
svc.sayHello();
}
}
所谓基于field注入,就是在bean的变量上使用注解进行依赖注入。本质上是通过反射的方式直接注入到field。是平常开发中经常看到的一种方式,也是Spring团队所不推荐的方式,注解方式:
@Autowired
private Svc svc;
配置文件方式
//xml方式配置DI(基本类型)
field注入只需要把@Autowired
扔到变量之上就好了,不需要特殊的构造器或者set方法,依赖注入容器会提供你所需的依赖。使用这种基于 field 注入的方式,容易违背单一职责原则(SRP:Single responsibility principle),拥有太多的依赖通常意味着类要承担更多的责任。依赖注入框架的核心思想之一就是受容器管理的类不应该去依赖容器所使用的依赖。换句话说这个类应该是一个简单的POJO(Plain Ordinary Java Object)能够被单独实例化并且能为它提供它所需的依赖。具体体现在:类和依赖容器强耦合,不能在容器外使用;类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化。
不能使用属性注入的方式构建不可变对象(final
修饰的变量)。
Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.
简单来说就是,强制依赖就用构造器方式,可选、可变的依赖就用setter 注入。可以在同一个类中使用这两种方法。构造器注入更适合强制性的注入旨在不变性,Setter注入更适合可变性的注入。让我们看看Spring 这样推荐的理由,首先是基于构造方法注入:
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.
Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (final
修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (比如各个service
),在被调用时可以保证它们都完全准备好了。从代码质量的角度来看,一个巨大的构造方法通常代表着这个类可能承担了过多的责任。而对于基于setter的注入:
Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later.
基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。如果使用setter注入必需的依赖,那么将会有过多的null检查充斥在代码中。使用 setter 注入的一个优点是,这个依赖可以很方便的被改变或者重新注入。
通过对应变量的setXXX()
方法以及在方法上面使用注解,来完成依赖注入,注解方式:
private Helper helper;
@Autowired
public void setHelper(Helper helper) {
this.helper = helper;
}
配置文件方式
import java.util.Map;
import java.util.Properties;
public class Dependency {
private int[] array;
private List list;
private Map map;
private Properties properties;
public int[] getArray() {
return array;
}
public void setArray(int[] array) {
this.array = array;
}
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
public Map getMap() {
return map;
}
public void setMap(Map map) {
this.map = map;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
Xml配置文件:
1
2
3
list1
list2
list3
v1
v2
v3
在 Spring 4.3
及以后的版本中,setter上面的@Autowired
注解是可以不写的。
将各个必需的依赖全部放在带有注解构造方法的参数中,并在构造方法中完成对应变量的初始化,这种方式就是基于构造方法的注入。注解方式:
private final Svc svc;
@Autowired
public HelpService(@Qualifier("svcB") Svc svc) {
this.svc = svc;
}
配置文件方式
在 Spring 4.3
及以后的版本中,如果这个类只有一个构造方法,那么这个构造方法上面也可以不写 @Autowired
注解 。
断言是一个逻辑判断,用于检查不应该发生的情况,Assert关键字在JDK1.4中引入,可通过JVM参数-enableassertions开启,SpringBoot中提供了Assert断言工具类,通常用于数据合法性检查。
// 要求参数 object 必须为非空(Not Null),否则抛出异常,不予放行
// 参数 message 参数用于定制异常信息。
void notNull(Object object, String message)
// 要求参数必须空(Null),否则抛出异常,不予『放行』。
// 和 notNull() 方法断言规则相反
void isNull(Object object, String message)
// 要求参数必须为真(True),否则抛出异常,不予『放行』。
void isTrue(boolean expression, String message)
// 要求参数(List/Set)必须非空(Not Empty),否则抛出异常,不予放行
void notEmpty(Collection collection, String message)
// 要求参数(String)必须有长度(即,Not Empty),否则抛出异常,不予放行
void hasLength(String text, String message)
// 要求参数(String)必须有内容(即,Not Blank),否则抛出异常,不予放行
void hasText(String text, String message)
// 要求参数是指定类型的实例,否则抛出异常,不予放行
void isInstanceOf(Class type, Object obj, String message)
// 要求参数 `subType` 必须是参数 superType 的子类或实现类,否则抛出异常,不予放行
void isAssignable(Class superType, Class subType, String message)
ObjectUtils获取对象的基本信息
// 获取对象的类名。参数为 null 时,返回字符串:"null"
String nullSafeClassName(Object obj)
// 参数为 null 时,返回 0
int nullSafeHashCode(Object object)
// 参数为 null 时,返回字符串:"null"
String nullSafeToString(boolean[] array)
// 获取对象 HashCode(十六进制形式字符串)。参数为 null 时,返回 0
String getIdentityHexString(Object obj)
// 获取对象的类名和 HashCode。 参数为 null 时,返回字符串:""
String identityToString(Object obj)
// 相当于 toString()方法,但参数为 null 时,返回字符串:""
String getDisplayString(Object obj)
判断工具
// 判断数组是否为空
boolean isEmpty(Object[] array)
// 判断参数对象是否是数组
boolean isArray(Object obj)
// 判断数组中是否包含指定元素
boolean containsElement(Object[] array, Object element)
// 相等,或同为 null时,返回 true
boolean nullSafeEquals(Object o1, Object o2)
/*
判断参数对象是否为空,判断标准为:
Optional: Optional.empty()
Array: length == 0
CharSequence: length == 0
Collection: Collection.isEmpty()
Map: Map.isEmpty()
*/
boolean isEmpty(Object obj)
其他工具方法
// 向参数数组的末尾追加新元素,并返回一个新数组
A[] addObjectToArray(A[] array, O obj)
// 原生基础类型数组 --> 包装类数组
Object[] toObjectArray(Object source)
StringUtils字符串判断工具
// 判断字符串是否为 null,或 ""。注意,包含空白符的字符串为非空
boolean isEmpty(Object str)
// 判断字符串是否是以指定内容结束。忽略大小写
boolean endsWithIgnoreCase(String str, String suffix)
// 判断字符串是否已指定内容开头。忽略大小写
boolean startsWithIgnoreCase(String str, String prefix)
// 是否包含空白符
boolean containsWhitespace(String str)
// 判断字符串非空且长度不为 0,即,Not Empty
boolean hasLength(CharSequence str)
// 判断字符串是否包含实际内容,即非仅包含空白符,也就是 Not Blank
boolean hasText(CharSequence str)
// 判断字符串指定索引处是否包含一个子串。
boolean substringMatch(CharSequence str, int index, CharSequence substring)
// 计算一个字符串中指定子串的出现次数
int countOccurrencesOf(String str, String sub)
字符串操作工具
// 查找并替换指定子串
String replace(String inString, String oldPattern, String newPattern)
// 去除尾部的特定字符
String trimTrailingCharacter(String str, char trailingCharacter)
// 去除头部的特定字符
String trimLeadingCharacter(String str, char leadingCharacter)
// 去除头部的空白符
String trimLeadingWhitespace(String str)
// 去除头部的空白符
String trimTrailingWhitespace(String str)
// 去除头部和尾部的空白符
String trimWhitespace(String str)
// 删除开头、结尾和中间的空白符
String trimAllWhitespace(String str)
// 删除指定子串
String delete(String inString, String pattern)
// 删除指定字符(可以是多个)
String deleteAny(String inString, String charsToDelete)
// 对数组的每一项执行 trim() 方法
String[] trimArrayElements(String[] array)
// 将 URL 字符串进行解码
String uriDecode(String source, Charset charset)
路径相关工具方法
// 解析路径字符串,优化其中的 “..”
String cleanPath(String path)
// 解析路径字符串,解析出文件名部分
String getFilename(String path)
// 解析路径字符串,解析出文件后缀名
String getFilenameExtension(String path)
// 比较两个两个字符串,判断是否是同一个路径。会自动处理路径中的 “..”
boolean pathEquals(String path1, String path2)
// 删除文件路径名中的后缀部分
String stripFilenameExtension(String path)
// 以 “. 作为分隔符,获取其最后一部分
String unqualify(String qualifiedName)
// 以指定字符作为分隔符,获取其最后一部分
String unqualify(String qualifiedName, char separator)
CollectionUtils集合判断工具
// 判断 List/Set 是否为空
boolean isEmpty(Collection> collection)
// 判断 Map 是否为空
boolean isEmpty(Map,?> map)
// 判断 List/Set 中是否包含某个对象
boolean containsInstance(Collection> collection, Object element)
// 以迭代器的方式,判断 List/Set 中是否包含某个对象
boolean contains(Iterator> iterator, Object element)
// 判断 List/Set 是否包含某些对象中的任意一个
boolean containsAny(Collection> source, Collection> candidates)
// 判断 List/Set 中的每个元素是否唯一。即 List/Set 中不存在重复元素
boolean hasUniqueObject(Collection> collection)
集合操作工具
// 将 Array 中的元素都添加到 List/Set 中
void mergeArrayIntoCollection(Object array, Collection collection)
// 将 Properties 中的键值对都添加到 Map 中
void mergePropertiesIntoMap(Properties props, Map map)
// 返回 List 中最后一个元素
T lastElement(List list)
// 返回 Set 中最后一个元素
T lastElement(Set set)
// 返回参数 candidates 中第一个存在于参数 source 中的元素
E findFirstMatch(Collection> source, Collection candidates)
// 返回 List/Set 中指定类型的元素。
T findValueOfType(Collection> collection, Class type)
// 返回 List/Set 中指定类型的元素。如果第一种类型未找到,则查找第二种类型,以此类推
Object findValueOfType(Collection> collection, Class>[] types)
// 返回 List/Set 中元素的类型
Class> findCommonElementType(Collection> collection)
文件资源IO流FileCopyUtils
输入
// 从文件中读入到字节数组中
byte[] copyToByteArray(File in)
// 从输入流中读入到字节数组中
byte[] copyToByteArray(InputStream in)
// 从输入流中读入到字符串中
String copyToString(Reader in)
输出
// 从字节数组到文件
void copy(byte[] in, File out)
// 从文件到文件
int copy(File in, File out)
// 从字节数组到输出流
void copy(byte[] in, OutputStream out)
// 从输入流到输出流
int copy(InputStream in, OutputStream out)
// 从输入流到输出流
int copy(Reader in, Writer out)
// 从字符串到输出流
void copy(String in, Writer out)
ResourceUtils从资源路径获取文件
// 判断字符串是否是一个合法的 URL 字符串。
static boolean isUrl(String resourceLocation)
// 获取 URL
static URL getURL(String resourceLocation)
// 获取文件(在 JAR 包内无法正常使用,需要是一个独立的文件)
static File getFile(String resourceLocation)
Resource
// 文件系统资源 D:\...
FileSystemResource
// URL 资源,如 file://... http://...
UrlResource
// 类路径下的资源,classpth:...
ClassPathResource
// Web 容器上下文中的资源(jar 包、war 包)
ServletContextResource
// 判断资源是否存在
boolean exists()
// 从资源中获得 File 对象
File getFile()
// 从资源中获得 URI 对象
URI getURI()
// 从资源中获得 URI 对象
URL getURL()
// 获得资源的 InputStream
InputStream getInputStream()
// 获得资源的描述信息
String getDescription()
StreamUtils
输入
void copy(byte[] in, OutputStream out)
int copy(InputStream in, OutputStream out)
void copy(String in, Charset charset, OutputStream out)
long copyRange(InputStream in, OutputStream out, long start, long end)
输出
byte[] copyToByteArray(InputStream in)
String copyToString(InputStream in, Charset charset)
// 舍弃输入流中的内容
int drain(InputStream in)
反射、AOP ReflectionUtils
获取方法
// 在类中查找指定方法
Method findMethod(Class> clazz, String name)
// 同上,额外提供方法参数类型作查找条件
Method findMethod(Class> clazz, String name, Class>... paramTypes)
// 获得类中所有方法,包括继承而来的
Method[] getAllDeclaredMethods(Class> leafClass)
// 在类中查找指定构造方法
Constructor accessibleConstructor(Class clazz, Class>... parameterTypes)
// 是否是 equals() 方法
boolean isEqualsMethod(Method method)
// 是否是 hashCode() 方法
boolean isHashCodeMethod(Method method)
// 是否是 toString() 方法
boolean isToStringMethod(Method method)
// 是否是从 Object 类继承而来的方法
boolean isObjectMethod(Method method)
// 检查一个方法是否声明抛出指定异常
boolean declaresException(Method method, Class> exceptionType)
执行方法
// 执行方法
Object invokeMethod(Method method, Object target)
// 同上,提供方法参数
Object invokeMethod(Method method, Object target, Object... args)
// 取消 Java 权限检查。以便后续执行该私有方法
void makeAccessible(Method method)
// 取消 Java 权限检查。以便后续执行私有构造方法
void makeAccessible(Constructor> ctor)
获取字段
// 在类中查找指定属性
Field findField(Class> clazz, String name)
// 同上,多提供了属性的类型
Field findField(Class> clazz, String name, Class> type)
// 是否为一个 "public static final" 属性
boolean isPublicStaticFinal(Field field)
设置字段
// 获取 target 对象的 field 属性值
Object getField(Field field, Object target)
// 设置 target 对象的 field 属性值,值为 value
void setField(Field field, Object target, Object value)
// 同类对象属性对等赋值
void shallowCopyFieldState(Object src, Object dest)
// 取消 Java 的权限控制检查。以便后续读写该私有属性
void makeAccessible(Field field)
// 对类的每个属性执行 callback
void doWithFields(Class> clazz, ReflectionUtils.FieldCallback fc)
// 同上,多了个属性过滤功能。
void doWithFields(Class> clazz, ReflectionUtils.FieldCallback fc,
ReflectionUtils.FieldFilter ff)
// 同上,但不包括继承而来的属性
void doWithLocalFields(Class> clazz, ReflectionUtils.FieldCallback fc)
AopUtils判断代理类型
// 判断是不是 Spring 代理对象
boolean isAopProxy()
// 判断是不是 jdk 动态代理对象
isJdkDynamicProxy()
// 判断是不是 CGLIB 代理对象
boolean isCglibProxy()
获取被代理对象的 class
// 获取被代理的目标 class
Class> getTargetClass()
AopContext获取当前对象的代理对象
Object currentProxy()
Spring3开始提供了@Async
注解,该注解可以被标注在方法上,以便异步地调用该方法。调用者将在调用时立即返回,方法的实际执行将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执行。在项目应用中,@Async
调用线程池,推荐使用自定义线程池的模式。@Async
注解使用系统默认或者自定义的线程池(代替默认线程池),可在项目中设置多个线程池,在异步调用时,指明需要调用的线程池名称,如@Async("new_task")。
自定义线程池常用方案:重新实现接口AsyncConfigurer。
同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行。异步调用指程序在顺序执行时,调用者无需等待被调用的方法完全执行完毕就执行后面的程序。
同步调用
定义Task类,创建三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10秒内)
@Component
public class Task {
public static Random random =new Random();
public void doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
}
public void doTaskTwo() throws Exception {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
}
public void doTaskThree() throws Exception {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
}
}
在单元测试用例中,注入Task对象,并在测试用例中执行doTaskOne、doTaskTwo、doTaskThree三个函数。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {
@Autowired
private Task task;
@Test
public void test() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
}
}
输出:
开始做任务一
完成任务一,耗时:4256毫秒
开始做任务二
完成任务二,耗时:4957毫秒
开始做任务三
完成任务三,耗时:7173毫秒
任务一、任务二、任务三顺序的执行完了。
异步调用
同步调用按照顺序执行完了三个任务,但是执行时间比较长,若这三个任务之间不存在依赖关系,可以并发执行的话,可以通过异步调用的方式来并发执行,以此来节省时间的开销。在Spring Boot中,只需要通过使用@Async注解就能简单的将原来的同步函数变为异步函数,如下:
@Component
public class Task {
@Async
public void doTaskOne() throws Exception {
// 同上内容,省略
}
@Async
public void doTaskTwo() throws Exception {
// 同上内容,省略
}
@Async
public void doTaskThree() throws Exception {
// 同上内容,省略
}
}
@Async注解能够生效,需要在Spring Boot的主程序中配置@EnableAsync
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Async所修饰的函数不要定义为static类型,这样异步调用不会生效。
异步回调
为了让doTaskOne、doTaskTwo、doTaskThree能正常结束,假设我们需要统计一下三个任务并发执行共耗时多少,这就需要等到上述三个函数都完成调动之后记录时间,并计算结果。如何判断上述三个异步调用是否已经执行完成呢?就需要使用Future来返回异步调用的结果
@Async
public Future doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
return new AsyncResult<>("任务一完成");
}
@Test
public void test() throws Exception {
long start = System.currentTimeMillis();
Future task1 = task.doTaskOne();
Future task2 = task.doTaskTwo();
Future task3 = task.doTaskThree();
while(true) {
if(task1.isDone() && task2.isDone() && task3.isDone()) {
// 三个任务都调用完成,退出循环等待
break;
}
Thread.sleep(1000);
}
long end = System.currentTimeMillis();
System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
}
测试用例一开始记录开始时间,调用三个异步函数的时候,返回Future类型的结果对象,调用完三个异步函数之后,开启一个循环,根据返回的Future对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。跳出循环之后,根据结束时间 - 开始时间,计算出三个任务并发执行的总耗时。 执行单元测试,结果如下
开始做任务一
开始做任务二
开始做任务三
完成任务三,耗时:37毫秒
完成任务二,耗时:3661毫秒
完成任务一,耗时:7149毫秒
任务全部完成,总耗时:8025毫秒
可以看到,通过异步调用,让任务一、二、三并发执行,有效的减少了程序的总运行时间。
SimpleAsyncTaskExecutor
:不重用线程,默认每次调用都会创建一个新的线程。
SyncTaskExecutor
:没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。
ConcurrentTaskExecutor
:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
SimpleThreadPoolTaskExecutor
:Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。
ThreadPoolTaskExecutor
:经常用到的线程池,其实质是对java.util.concurrent.ThreadPoolExecutor
的包装。
Spring中启用@Async
// 基于Java配置的启用方式:
@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }
// Spring boot启用:
@EnableAsync
@EnableTransactionManagement
public class SettlementApplication {
public static void main(String[] args) {
SpringApplication.run(SettlementApplication.class, args);
}
}
@Async应用默认线程池
@Async
注解在使用时,不指定线程池的名称,默认线程池为SimpleAsyncTaskExecutor。基于@Async无返回值调用,直接在使用类,使用方法(建议在使用方法)上,加上注解,若需要抛出异常,需手动new一个异常抛出。
/** * 带参数的异步调用 异步方法可以传入参数 * 对于返回值是void,异常会被AsyncUncaughtExceptionHandler处理掉 * @param s */
@Async
public void asyncInvokeWithException(String s) {
log.info("asyncInvokeWithParameter, parementer={}", s);
throw new IllegalArgumentException(s);
}
有返回值Future调用
/** * 异常调用返回Future * 对于返回值是Future,不会被AsyncUncaughtExceptionHandler处理,需要我们在方法中捕获异常并处理 * 或者在调用方在调用Futrue.get时捕获异常进行处理 * * @param i * @return */
@Async
public Future asyncInvokeReturnFuture(int i) {
log.info("asyncInvokeReturnFuture, parementer={}", i);
Future future; try {
Thread.sleep(1000 * 1);
future = new AsyncResult("success:" + i); throw new IllegalArgumentException("a"); }
catch (InterruptedException e) {
future = new AsyncResult("error"); }
catch(IllegalArgumentException e){
future = new AsyncResult("error-IllegalArgumentException");
}
return future; }
CompletableFuture调用CompletableFuture不使用@Async注解。JDK5新增了Future接口,用于描述一个异步计算的结果。虽然Future以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式跟异步的初衷相违背,轮询的方式会耗费大量的CPU资源,而且也不能及时地得到计算结果。
@Async默认异步配置使用的是SimpleAsyncTaskExecutor,该线程池默认来一个任务创建一个线程,若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误。针对线程创建问题,SimpleAsyncTaskExecutor提供了限流机制,通过concurrencyLimit属性来控制开关,当concurrencyLimit>=0
时开启限流机制,默认关闭限流机制即concurrencyLimit=-1
,当关闭情况下,会不断创建新的线程来处理任务。基于默认配置,SimpleAsyncTaskExecutor并不是严格意义的线程池,达不到线程复用的功能。
自定义线程池,可对系统中线程池更加细粒度的控制,方便调整线程池大小配置,线程执行异常控制和处理。在设置系统自定义线程池代替默认线程池时,虽可通过多种模式设置,但替换默认线程池最终产生的线程池有且只能设置一个(不能设置多个类继承AsyncConfigurer)。自定义线程池有如下模式:
重新实现接口AsyncConfigurer
继承AsyncConfigurerSupport
配置由自定义的TaskExecutor替代内置的任务执行器
实现接口AsyncConfigurer
@Configuration public class AsyncConfiguration implements AsyncConfigurer { @Bean("kingAsyncExecutor") public ThreadPoolTaskExecutor executor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); int corePoolSize = 10; executor.setCorePoolSize(corePoolSize); int maxPoolSize = 50; executor.setMaxPoolSize(maxPoolSize); int queueCapacity = 10; executor.setQueueCapacity(queueCapacity); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); String threadNamePrefix = "kingDeeAsyncExecutor-"; executor.setThreadNamePrefix(threadNamePrefix); executor.setWaitForTasksToCompleteOnShutdown(true); // 使用自定义的跨线程的请求级别线程工厂类19 int awaitTerminationSeconds = 5; executor.setAwaitTerminationSeconds(awaitTerminationSeconds); executor.initialize(); return executor; } @Override public Executor getAsyncExecutor() { return executor(); } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (ex, method, params) -> ErrorLogger.getInstance().log(String.format("执行异步任务'%s'", method), ex); } }
继承AsyncConfigurerSupport
@Configuration @EnableAsync class SpringAsyncConfigurer extends AsyncConfigurerSupport { @Bean public ThreadPoolTaskExecutor asyncExecutor() { ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor(); threadPool.setCorePoolSize(3); threadPool.setMaxPoolSize(3); threadPool.setWaitForTasksToCompleteOnShutdown(true); threadPool.setAwaitTerminationSeconds(60 * 15); return threadPool; } @Override public Executor getAsyncExecutor() { return asyncExecutor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (ex, method, params) -> ErrorLogger.getInstance().log(String.format("执行异步任务'%s'", method), ex); } }
配置自定义的TaskExecutor
由于AsyncConfigurer的默认线程池在源码中为空,Spring通过beanFactory.getBean(TaskExecutor.class)
先查看是否有线程池,未配置时,通过beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class)
,又查询是否存在默认名称为TaskExecutor的线程池。在项目中,定义名称为TaskExecutor的bean生成一个默认线程池。也可不指定线程池的名称,申明一个线程池,本身底层是基于TaskExecutor.class
便可。
比如:
Executor.class:ThreadPoolExecutorAdapter->ThreadPoolExecutor->AbstractExecutorService->ExecutorService->Executor
在替换默认的线程池时,需设置默认的线程池名称为TaskExecutor
TaskExecutor.class:ThreadPoolTaskExecutor->SchedulingTaskExecutor->AsyncTaskExecutor->TaskExecutor
@EnableAsync @Configuration public class TaskPoolConfig { @Bean(name = AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME) public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//核心线程池大小 executor.setCorePoolSize(10);//最大线程数
executor.setMaxPoolSize(20); //队列容量
executor.setQueueCapacity(200); //活跃时间
executor.setKeepAliveSeconds(60); //线程名字前缀 executor.setThreadNamePrefix("taskExecutor-"); executor.setRejectedExecutionHandler(
new ThreadPoolExecutor.CallerRunsPolicy());
return executor; }
@Bean(name = "new_task")
public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //核心线程池大小 executor.setCorePoolSize(10); //最大线程数
executor.setMaxPoolSize(20); //队列容量
executor.setQueueCapacity(200);//活跃时间
executor.setKeepAliveSeconds(60); //线程名字前缀 executor.setThreadNamePrefix("taskExecutor-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; } }
Spring Data JPA 作为Spring Data 中对于关系型数据库支持的一种框架技术,属于 ORM 的一种,通过得当的使用,可以大大简化开发过程中对于数据操作的复杂度。
Java里面写的一段DB操作逻辑,是如何一步步被传递到 DB 中执行了的呢?为什么 Java 里面可以去对接不同产商的 DB 产品?为什么有 JDBC、还会有各种 MyBatis 或者诸如 Hibernate 等 ORM框架呢?这些 JDBC、JPA、ORM、Hibernate 等等相互之间啥关系?除了 MyBatis、Hibernate 等习以为常的内容,是否还有其他操作DB的方案呢?带着这些问题往下看
JDBC
JDBC(Java DataBase Connectivity),是 Java 连接数据库操作的原生接口。JDBC为数据库访问提供标准的接口。由各个数据库厂商及第三方中间件厂商依照JDBC规范为数据库的连接提供的标准方法。
package com.txw.jdbc;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.ResultSet;@SuppressWarnings("all") // 注解警告信息public class JdbcTest01 {
public static void main(String[] args) throws Exception {
// 1.加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2 创建和数据库之间的连接
String username = "testdb";
String password = "123456";
String url = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai";
Connection conn = DriverManager.getConnection(url,username,password);
// 3.准备发送SQL
String sql = "select * from t_person";
PreparedStatement pstm = conn.prepareStatement(sql);
// 4.执行SQL,接收结果集
ResultSet rs = pstm.executeQuery();
// 5 处理结果集
while(rs.next()){
int personId1 = rs.getInt("person_id");
String personName1 = rs.getString("person_name");
int age1 = rs.getInt("age");
String sex1 = rs.getString("sex");
String mobile1 = rs.getString("mobile");
String address1 = rs.getString("address");
System.out.println("personId="+personId1+",personName="+personName1 +",age="+age1+",sex="+sex1+",mobile="+mobile1+",address="+address1);
}
// 6.释放资源
rs.close();
pstm.close();
conn.close();
}}
代码示例中JDBC的操作环节:根据使用的DB类型不同加载对应的JdbcDriver,连接DB,编写 SQL语句,发送到DB中执行并接收结果返回,对结果进行处理解析,释放过程中的连接资源。其弊端在于业务代码里面耦合了字符串格式SQL语句,复杂场景维护起来比较麻烦;非结构化的 key-value 映射方式处理结果,操作过于复杂,且不符合 Java 面向对象的思想;需要关注过程资源的释放、操作不当容易造成泄露。
ORM 框架
对象-关系映射(Object-Relational Mapping,简称 ORM)。ORM 就是将代码里面的 Java 类与 DB 中的 table 表进行映射,代码中对相关 Java 类的操作,即体现为 DB 中对相关 Table 的操作。
JPA 介绍
JPA,即 Java Persistence API 的缩写,也即 Java 持久化层 API。
一套标准 API:在 javax.persistence 的包下面提供,用来操作实体对象,执行 CRUD 操作,将开发者从烦琐的 JDBC 和 SQL 代码中解脱出来,按照 Java 思路去编写代码操作 DB。
面向对象操作语言:通过面向对象的思路,避免代码与 SQL 的深度耦合。
ORM 元数据映射:ORM,即 Object Relation Mapping,对象关系映射
Java 应用程序,可以通过 JPA 规范,利用一些常见的基于 JPA 规范的框架来实现对 DB 的操作。而常见的一些 ORM 框架,比如 Hibernate、EclipseLink、OpenJPA 等等,其实都是提供了对 JPA 规范的支持,是 JPA 规范的具体实现提供者,用于辅助 Java 程序对数据库数据的操作。
Spring Data JPA
JPA 其实是一个基于 ORM 的 Java API 规范定义。Spirng Data JPA 是 Spring 提供的一套简化 JPA 开发的框架,按照约定好的【方法命名规则】写 DAO 层接口,就可以在不写接口实现的情况下,实现对数据库的访问和操作,同时提供了很多除了 CRUD 之外的功能,如分页、排序、复杂查询等等。Spring Data JPA 不是一个完整 JPA 规范的实现,它只是一个代码抽象层,主要用于减少为各种持久层存储实现数据访问层所需的代码量。其底层依旧是 Hibernate。Hibernate 是一个标准的 ORM 框架,实现 JPA 接口。
①JDBC 是 Java 操作最终数据库的底层接口,JDBC 是与各个 DB 产商之间约定的协议规范,基于这些规范,可在 Java 代码中往 DB 执行 SQL 操作。
②因为 JDBC 负责将 SQL 语句执行到 DB 中,属于相对原始的接口,业务代码里面需要构建拼接出 SQL 语句,然后基于 JDBC 去 DB 中执行对应 SQL 语句。这样存在的问题会比较明显,Java 代码中需要耦合大量的 SQL 语句、且因为缺少封装,实际业务编码使用时会比较繁琐、维护复杂。
③为了能够将代码与 SQL 语句分离开,以一种更符合 Java 面向对象编程思维的方式来操作 DB,诞生了 ORM(Object Relation Mapping,对象关系映射)概念。ORM 将 Java 的 Object 与 DB 中的 Table 进行映射起来,管理 Object 也等同于对 Table 的管理与操作,这样就可以实现没有 SQL 的情况下实现对 DB 的操作。常见的 ORM 框架有 Hibernate、EclipseLink、OpenJPA 等等。
④为了规范 ORM 的具体使用,Java 5.x 开始制定了基于 ORM 思想的 Java 持久化层操作 API 规范,也即 JPA(注意,JPA 只是一个基于 ORM 的 Java API 规范,供各个 ORM 框架提供 API 时遵循),当前主流 ORM 框架都是支持 JPA 规范的。
⑤Spring 框架盛行的时代,为了能够更好适配,Spring Data JPA 诞生, 这个可以理解为对 JPA 规范的二次封装。
Spring Batch 是 Spring 提供的一个数据处理框架。企业域中的许多应用程序需要批量处理才能在关键任务环境中执行业务操作。Spring Batch 构建了人们期望的 Spring Framework 特性(生产力,基于 POJO 的开发方法和一般易用性),同时使开发人员可以在必要时轻松访问和利用更高级的企业服务。Spring Batch 提供了可重用的功能,这些功能对于处理大量的数据至关重要,包括记录/跟踪,事务管理,作业处理统计,作业重启,跳过和资源管理。
从数据库,文件或队列中读取大量记录。
以某种方式处理数据。
以修改之后的形式写回数据
Spring Batch总体架构
在 Spring Batch 中一个 job 可以定义很多的步骤 step,在每一个 step 里面可以定义其专属的 ItemReader 用于读取数据。ItemProcesseor 用于处理数据,ItemWriter 用于写数据,而每一个定义的 job 则都在 JobRepository 里面,我们可以通过 JobLauncher 来启动某一个 job。
EnableJms注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(JmsBootstrapConfiguration.class)
public @interface EnableJms {
}
@Import(JmsBootstrapConfiguration.class)
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class JmsBootstrapConfiguration {
/**
* jms 监听注解后处理, 将{@link JmsListener} 注册到{@link JmsListenerContainerFactory}
* @return
*/
@Bean(name = JmsListenerConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public JmsListenerAnnotationBeanPostProcessor jmsListenerAnnotationProcessor() {
return new JmsListenerAnnotationBeanPostProcessor();
}
/**
* JMS 监听注册
* @return
*/
@Bean(name = JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME)
public JmsListenerEndpointRegistry defaultJmsListenerEndpointRegistry() {
return new JmsListenerEndpointRegistry();
}
}
JmsListenerAnnotationBeanPostProcessor.afterSingletonsInstantiated()
@Override
public void afterSingletonsInstantiated() {
// Remove resolved singleton classes from cache
this.nonAnnotatedClasses.clear();
if (this.beanFactory instanceof ListableBeanFactory) {
// Apply JmsListenerConfigurer beans from the BeanFactory, if any
// 根据类型获取bean
Map beans =
((ListableBeanFactory) this.beanFactory).getBeansOfType(JmsListenerConfigurer.class);
List configurers = new ArrayList<>(beans.values());
// 排序 Order
AnnotationAwareOrderComparator.sort(configurers);
for (JmsListenerConfigurer configurer : configurers) {
// 放入jms监听配置,开发者自定义
configurer.configureJmsListeners(this.registrar);
}
}
if (this.containerFactoryBeanName != null) {
this.registrar.setContainerFactoryBeanName(this.containerFactoryBeanName);
}
if (this.registrar.getEndpointRegistry() == null) {
// Determine JmsListenerEndpointRegistry bean from the BeanFactory
if (this.endpointRegistry == null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to find endpoint registry by bean name");
this.endpointRegistry = this.beanFactory.getBean(
JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME, JmsListenerEndpointRegistry.class);
}
this.registrar.setEndpointRegistry(this.endpointRegistry);
}
// Set the custom handler method factory once resolved by the configurer
MessageHandlerMethodFactory handlerMethodFactory = this.registrar.getMessageHandlerMethodFactory();
if (handlerMethodFactory != null) {
this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(handlerMethodFactory);
}
// Actually register all listeners
this.registrar.afterPropertiesSet();
}
this.registrar.afterPropertiesSet()
@Override
public void afterPropertiesSet() {
registerAllEndpoints();
}
protected void registerAllEndpoints() {
Assert.state(this.endpointRegistry != null, "No JmsListenerEndpointRegistry set");
synchronized (this.mutex) {
for (JmsListenerEndpointDescriptor descriptor : this.endpointDescriptors) {
// 注册监听
this.endpointRegistry.registerListenerContainer(
descriptor.endpoint, resolveContainerFactory(descriptor));
}
this.startImmediately = true; // trigger immediate startup
}
}
注册监听 postProcessAfterInitialization
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AopInfrastructureBean || bean instanceof JmsListenerContainerFactory ||
bean instanceof JmsListenerEndpointRegistry) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
// 获取 bean 的代理对象.class
Class> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass)) {
Map> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup>) method -> {
Set listenerMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, JmsListener.class, JmsListeners.class);
return (!listenerMethods.isEmpty() ? listenerMethods : null);
});
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(targetClass);
if (logger.isTraceEnabled()) {
logger.trace("No @JmsListener annotations found on bean type: " + targetClass);
}
} else {
// Non-empty set of methods
annotatedMethods.forEach((method, listeners) ->
listeners.forEach(listener -> processJmsListener(listener, method, bean)));
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @JmsListener methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}
protected void processJmsListener(JmsListener jmsListener, Method mostSpecificMethod, Object bean) {
Method invocableMethod = AopUtils.selectInvocableMethod(mostSpecificMethod, bean.getClass());
// 设置 监听方法信息
MethodJmsListenerEndpoint endpoint = createMethodJmsListenerEndpoint();
endpoint.setBean(bean);
endpoint.setMethod(invocableMethod);
endpoint.setMostSpecificMethod(mostSpecificMethod);
endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
endpoint.setEmbeddedValueResolver(this.embeddedValueResolver);
endpoint.setBeanFactory(this.beanFactory);
endpoint.setId(getEndpointId(jmsListener));
endpoint.setDestination(resolve(jmsListener.destination()));
if (StringUtils.hasText(jmsListener.selector())) {
endpoint.setSelector(resolve(jmsListener.selector()));
}
if (StringUtils.hasText(jmsListener.subscription())) {
endpoint.setSubscription(resolve(jmsListener.subscription()));
}
if (StringUtils.hasText(jmsListener.concurrency())) {
endpoint.setConcurrency(resolve(jmsListener.concurrency()));
}
JmsListenerContainerFactory> factory = null;
String containerFactoryBeanName = resolve(jmsListener.containerFactory());
if (StringUtils.hasText(containerFactoryBeanName)) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to obtain container factory by bean name");
try {
factory = this.beanFactory.getBean(containerFactoryBeanName, JmsListenerContainerFactory.class);
} catch (NoSuchBeanDefinitionException ex) {
throw new BeanInitializationException("Could not register JMS listener endpoint on [" +
mostSpecificMethod + "], no " + JmsListenerContainerFactory.class.getSimpleName() +
" with id '" + containerFactoryBeanName + "' was found in the application context", ex);
}
}
// 注册监听点 到 JmsListenerContainerFactory
this.registrar.registerEndpoint(endpoint, factory);
}
监听注册的主要方法org.springframework.jms.config.JmsListenerEndpointRegistrar#registerEndpoint(org.springframework.jms.config.JmsListenerEndpoint, org.springframework.jms.config.JmsListenerContainerFactory>)
public void registerEndpoint(JmsListenerEndpoint endpoint, @Nullable JmsListenerContainerFactory> factory) {
Assert.notNull(endpoint, "Endpoint must not be null");
Assert.hasText(endpoint.getId(), "Endpoint id must be set");
// Factory may be null, we defer the resolution right before actually creating the container
// jms 监听点描述
JmsListenerEndpointDescriptor descriptor = new JmsListenerEndpointDescriptor(endpoint, factory);
synchronized (this.mutex) {
if (this.startImmediately) { // register and start immediately
Assert.state(this.endpointRegistry != null, "No JmsListenerEndpointRegistry set");
// 注册
this.endpointRegistry.registerListenerContainer(descriptor.endpoint,
resolveContainerFactory(descriptor), true);
}
else {
this.endpointDescriptors.add(descriptor);
}
}
}
org.springframework.jms.config.JmsListenerEndpointRegistry#registerListenerContainer(org.springframework.jms.config.JmsListenerEndpoint, org.springframework.jms.config.JmsListenerContainerFactory>, boolean)
public void registerListenerContainer(JmsListenerEndpoint endpoint, JmsListenerContainerFactory> factory,
boolean startImmediately) {
Assert.notNull(endpoint, "Endpoint must not be null");
Assert.notNull(factory, "Factory must not be null");
String id = endpoint.getId();
Assert.hasText(id, "Endpoint id must be set");
synchronized (this.listenerContainers) {
if (this.listenerContainers.containsKey(id)) {
throw new IllegalStateException("Another endpoint is already registered with id '" + id + "'");
}
// 创建消息监听容器
MessageListenerContainer container = createListenerContainer(endpoint, factory);
this.listenerContainers.put(id, container);
if (startImmediately) {
// 启动消息监听容器
startIfNecessary(container);
}
}
}
org.springframework.jms.config.JmsListenerEndpointRegistry#createListenerContainer
/**
* Create and start a new container using the specified factory.
* 创建监听容器
*/
protected MessageListenerContainer createListenerContainer(JmsListenerEndpoint endpoint,
JmsListenerContainerFactory> factory) {
// 创建监听 容器
MessageListenerContainer listenerContainer = factory.createListenerContainer(endpoint);
if (listenerContainer instanceof InitializingBean) {
try {
// 后置方法
((InitializingBean) listenerContainer).afterPropertiesSet();
} catch (Exception ex) {
throw new BeanInitializationException("Failed to initialize message listener container", ex);
}
}
int containerPhase = listenerContainer.getPhase();
if (containerPhase < Integer.MAX_VALUE) { // a custom phase value
if (this.phase < Integer.MAX_VALUE && this.phase != containerPhase) {
throw new IllegalStateException("Encountered phase mismatch between container factory definitions: " +
this.phase + " vs " + containerPhase);
}
this.phase = listenerContainer.getPhase();
}
return listenerContainer;
}
关键接口JmsListenerContainerFactory
public interface JmsListenerContainerFactory {
/**
* Create a {@link MessageListenerContainer} for the given {@link JmsListenerEndpoint}.
* 创建肩痛容器
* @param endpoint the endpoint to configure
* @return the created container
*/
C createListenerContainer(JmsListenerEndpoint endpoint);
}
注册完成后是否立即启动
this.listenerContainers.put(id, container);
if (startImmediately) {
// 启动消息监听容器
startIfNecessary(container);
}
private void startIfNecessary(MessageListenerContainer listenerContainer) {
if (this.contextRefreshed || listenerContainer.isAutoStartup()) {
listenerContainer.start();
}
}
执行完start
方法就结束了processJmsListener
的调用链路, postProcessAfterInitialization
也结束了
JmsListenerEndpointRegistry
辅助JmsListenerAnnotationBeanPostProcessor处理 registerListenerContainer
/**
* Create a message listener container for the given {@link JmsListenerEndpoint}.
* This create the necessary infrastructure to honor that endpoint
* with regards to its configuration.
*
The {@code startImmediately} flag determines if the container should be
* started immediately.
*
* 注册监听容器
*
* @param endpoint the endpoint to add
* 监听点
* @param factory the listener factory to use
* 监听容器工厂
* @param startImmediately start the container immediately if necessary
* 是否立即启动容器
* @see #getListenerContainers()
* @see #getListenerContainer(String)
*/
public void registerListenerContainer(JmsListenerEndpoint endpoint, JmsListenerContainerFactory> factory,
boolean startImmediately) {
Assert.notNull(endpoint, "Endpoint must not be null");
Assert.notNull(factory, "Factory must not be null");
String id = endpoint.getId();
Assert.hasText(id, "Endpoint id must be set");
synchronized (this.listenerContainers) {
if (this.listenerContainers.containsKey(id)) {
throw new IllegalStateException("Another endpoint is already registered with id '" + id + "'");
}
// 创建消息监听容器
MessageListenerContainer container = createListenerContainer(endpoint, factory);
this.listenerContainers.put(id, container);
if (startImmediately) {
// 启动消息监听容器
startIfNecessary(container);
}
}
}
Spring JmsTemplate源码分析
send 发送消息
@Override
public void send(final String destinationName, final MessageCreator messageCreator) throws JmsException {
// 执行.
execute(session -> {
Destination destination = resolveDestinationName(session, destinationName);
doSend(session, destination, messageCreator);
return null;
}, false);
}
@Nullable
public T execute(SessionCallback action, boolean startConnection) throws JmsException {
Assert.notNull(action, "Callback object must not be null");
Connection conToClose = null;
Session sessionToClose = null;
try {
Session sessionToUse = ConnectionFactoryUtils.doGetTransactionalSession(
obtainConnectionFactory(), this.transactionalResourceFactory, startConnection);
if (sessionToUse == null) {
// 创建链接
conToClose = createConnection();
// 创建session
sessionToClose = createSession(conToClose);
if (startConnection) {
conToClose.start();
}
sessionToUse = sessionToClose;
}
if (logger.isDebugEnabled()) {
logger.debug("Executing callback on JMS Session: " + sessionToUse);
}
/**
* sessionCallback 执行
* {@link JmsTemplate#doSend(Session, javax.jms.Destination, org.springframework.jms.core.MessageCreator)}
*/
return action.doInJms(sessionToUse);
} catch (JMSException ex) {
throw convertJmsAccessException(ex);
} finally {
// 资源释放
JmsUtils.closeSession(sessionToClose);
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), startConnection);
}
}
action.doInJms(sessionToUse)
的操作
Destination destination = resolveDestinationName(session, destinationName);
doSend(session, destination, messageCreator);
return null;
doSend
发送方法
protected void doSend(Session session, Destination destination, MessageCreator messageCreator)
throws JMSException {
Assert.notNull(messageCreator, "MessageCreator must not be null");
// 创建消息生产者
MessageProducer producer = createProducer(session, destination);
try {
// 创建消息
Message message = messageCreator.createMessage(session);
if (logger.isDebugEnabled()) {
logger.debug("Sending created message: " + message);
}
// 发送
doSend(producer, message);
// Check commit - avoid commit call within a JTA transaction.
if (session.getTransacted() && isSessionLocallyTransacted(session)) {
// Transacted session created by this template -> commit.
JmsUtils.commitIfNecessary(session);
}
} finally {
// 关闭消息生产者
JmsUtils.closeMessageProducer(producer);
}
}
createProducer
中通过javax.jms.Session.createProducer
创建MessageProducer
,第三方消息中间件独立实现
@Override
public javax.jms.Message createMessage(Session session) throws JMSException {
try {
// 消息转换
return this.messageConverter.toMessage(this.message, session);
} catch (Exception ex) {
throw new MessageConversionException("Could not convert '" + this.message + "'", ex);
}
}
doSend这里也是第三方消息中间件实现
protected void doSend(MessageProducer producer, Message message) throws JMSException {
if (this.deliveryDelay >= 0) {
producer.setDeliveryDelay(this.deliveryDelay);
}
if (isExplicitQosEnabled()) {
// 发送消息,第三方消息中间件实现
producer.send(message, getDeliveryMode(), getPriority(), getTimeToLive());
} else {
producer.send(message);
}
}
closeMessageProducer
public static void closeMessageProducer(@Nullable MessageProducer producer) {
if (producer != null) {
try {
producer.close();
} catch (JMSException ex) {
logger.trace("Could not close JMS MessageProducer", ex);
} catch (Throwable ex) {
// We don't trust the JMS provider: It might throw RuntimeException or Error.
logger.trace("Unexpected exception on closing JMS MessageProducer", ex);
}
}
}
receive 接收消息
@Override
@Nullable
public Message receive(String destinationName) throws JmsException {
return receiveSelected(destinationName, null);
}
@Override
@Nullable
public Message receiveSelected(final String destinationName, @Nullable final String messageSelector) throws JmsException {
return execute(session -> {
Destination destination = resolveDestinationName(session, destinationName);
return doReceive(session, destination, messageSelector);
}, true);
}
@Nullable
protected Message doReceive(Session session, Destination destination, @Nullable String messageSelector)
throws JMSException {
return doReceive(session, createConsumer(session, destination, messageSelector));
}
@Nullable
protected Message doReceive(Session session, MessageConsumer consumer) throws JMSException {
try {
// Use transaction timeout (if available).
long timeout = getReceiveTimeout();
// 链接工厂
ConnectionFactory connectionFactory = getConnectionFactory();
// JMS 资源信息
JmsResourceHolder resourceHolder = null;
if (connectionFactory != null) {
// 从连接对象中获取JMS 资源信息
resourceHolder = (JmsResourceHolder) TransactionSynchronizationManager.getResource(connectionFactory);
}
if (resourceHolder != null && resourceHolder.hasTimeout()) {
// 超时时间
timeout = Math.min(timeout, resourceHolder.getTimeToLiveInMillis());
}
// 具体的消息
Message message = receiveFromConsumer(consumer, timeout);
if (session.getTransacted()) {
// 事务性操作
// Commit necessary - but avoid commit call within a JTA transaction.
if (isSessionLocallyTransacted(session)) {
// Transacted session created by this template -> commit.
JmsUtils.commitIfNecessary(session);
}
} else if (isClientAcknowledge(session)) {
// Manually acknowledge message, if any.
if (message != null) {
message.acknowledge();
}
}
return message;
} finally {
JmsUtils.closeMessageConsumer(consumer);
}
}
Spring MessageConverter
消息转换接口,主要有两个接口:从消息转换到Object(fromMessage),从Object转换到消息(toMessage)
Object fromMessage(Message> message, Class> targetClass);
Message> toMessage(Object payload, @Nullable MessageHeaders headers);
序号 | class | 作用 |
---|---|---|
1 | ByteArrayMessageConverter | byte 数组消息转换器 |
2 | MappingJackson2MessageConverter | jackson2 的消息转换器 |
3 | MarshallingMessageConverter | xml 的消息转换器 |
4 | StringMessageConverter | 字符串消息转换器 |
AbstractMessageConverter
fromMessage
@Override
@Nullable
public final Object fromMessage(Message> message, Class> targetClass) {
return fromMessage(message, targetClass, null);
}
@Override
@Nullable
public final Object fromMessage(Message> message, Class> targetClass, @Nullable Object conversionHint) {
if (!canConvertFrom(message, targetClass)) {
return null;
}
return convertFromInternal(message, targetClass, conversionHint);
}
// 子类实现
@Nullable
protected Object convertFromInternal(
Message> message, Class> targetClass, @Nullable Object conversionHint) {
return null;
}
@Override
protected Object convertFromInternal(Message> message, Class> targetClass, @Nullable Object conversionHint) {
Charset charset = getContentTypeCharset(getMimeType(message.getHeaders()));
Object payload = message.getPayload();
return (payload instanceof String ? payload : new String((byte[]) payload, charset));
}
toMessage
@Override
@Nullable
public final Message> toMessage(Object payload, @Nullable MessageHeaders headers) {
return toMessage(payload, headers, null);
}
@Override
@Nullable
public final Message> toMessage(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) {
if (!canConvertTo(payload, headers)) {
return null;
}
// 子类实现
Object payloadToUse = convertToInternal(payload, headers, conversionHint);
if (payloadToUse == null) {
return null;
}
MimeType mimeType = getDefaultContentType(payloadToUse);
if (headers != null) {
MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(headers, MessageHeaderAccessor.class);
if (accessor != null && accessor.isMutable()) {
if (mimeType != null) {
accessor.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, mimeType);
}
// 创建信息对象
return MessageBuilder.createMessage(payloadToUse, accessor.getMessageHeaders());
}
}
MessageBuilder> builder = MessageBuilder.withPayload(payloadToUse);
if (headers != null) {
builder.copyHeaders(headers);
}
if (mimeType != null) {
builder.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, mimeType);
}
return builder.build();
}
org.springframework.messaging.converter.StringMessageConverter#convertToInternal
@Override
@Nullable
protected Object convertToInternal(
Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) {
if (byte[].class == getSerializedPayloadClass()) {
// 获取编码
Charset charset = getContentTypeCharset(getMimeType(headers));
// 获取byte数组
payload = ((String) payload).getBytes(charset);
}
return payload;
}
创建Message对象
@SuppressWarnings("unchecked")
public static Message createMessage(@Nullable T payload, MessageHeaders messageHeaders) {
Assert.notNull(payload, "Payload must not be null");
Assert.notNull(messageHeaders, "MessageHeaders must not be null");
if (payload instanceof Throwable) {
return (Message) new ErrorMessage((Throwable) payload, messageHeaders);
}
else {
return new GenericMessage<>(payload, messageHeaders);
}
}
@SuppressWarnings("unchecked")
public Message build() {
if (this.originalMessage != null && !this.headerAccessor.isModified()) {
return this.originalMessage;
}
MessageHeaders headersToUse = this.headerAccessor.toMessageHeaders();
if (this.payload instanceof Throwable) {
return (Message) new ErrorMessage((Throwable) this.payload, headersToUse);
}
else {
return new GenericMessage<>(this.payload, headersToUse);
}
}
两种创建方式基本相同,如果出现异常,组装异常消息对象ErrorMessage
,成功创建GenericMessage。