欢迎大家入坑,所谓师傅领进坑爬出去靠个人,首先我要说的是这个是上一篇《单刀直入@ComponentScan》的姊妹篇哈,接着把没聊透的事说明白,咱不是虎头蛇尾的人。
资源加载是啥意思
scan ,都认识吧,小学词汇连我都认识,扫到的是啥,扫到的是资源啊,如何让资源为我所用,就需要把资源搞进来,这就是资源加载。
spring如何加载资源的
首先不得不承认spring本身是很专一的,她把所有的资源都用一个统一的接口来表示,Resource,我们不妨看看她的真容。
```java
/**
* Interface for a resource descriptor that abstracts from the actual
* type of underlying resource, such as a file or class path resource.
*
* <p>An InputStream can be opened for every resource if it exists in
* physical form, but a URL or File handle can just be returned for
* certain resources. The actual behavior is implementation-specific.
*
* @author Juergen Hoeller
* @since 28.12.2003
* @see #getInputStream()
* @see #getURL()
* @see #getURI()
* @see #getFile()
* @see WritableResource
* @see ContextResource
* @see UrlResource
* @see FileUrlResource
* @see FileSystemResource
* @see ClassPathResource
* @see ByteArrayResource
* @see InputStreamResource
*/
public interface Resource extends InputStreamSource {
/**
* Determine whether this resource actually exists in physical form.
* <p>This method performs a definitive existence check, whereas the
* existence of a {@code Resource} handle only guarantees a valid
* descriptor handle.
*/
boolean exists();
/**
* Indicate whether non-empty contents of this resource can be read via
* {@link #getInputStream()}.
* <p>Will be {@code true} for typical resource descriptors that exist
* since it strictly implies {@link #exists()} semantics as of 5.1.
* Note that actual content reading may still fail when attempted.
* However, a value of {@code false} is a definitive indication
* that the resource content cannot be read.
* @see #getInputStream()
* @see #exists()
*/
default boolean isReadable() {
return exists();
}
/**
* Indicate whether this resource represents a handle with an open stream.
* If {@code true}, the InputStream cannot be read multiple times,
* and must be read and closed to avoid resource leaks.
* <p>Will be {@code false} for typical resource descriptors.
*/
default boolean isOpen() {
return false;
}
/**
* Determine whether this resource represents a file in a file system.
* A value of {@code true} strongly suggests (but does not guarantee)
* that a {@link #getFile()} call will succeed.
* <p>This is conservatively {@code false} by default.
* @since 5.0
* @see #getFile()
*/
default boolean isFile() {
return false;
}
/**
* Return a URL handle for this resource.
* @throws IOException if the resource cannot be resolved as URL,
* i.e. if the resource is not available as descriptor
*/
URL getURL() throws IOException;
/**
* Return a URI handle for this resource.
* @throws IOException if the resource cannot be resolved as URI,
* i.e. if the resource is not available as descriptor
* @since 2.5
*/
URI getURI() throws IOException;
/**
* Return a File handle for this resource.
* @throws java.io.FileNotFoundException if the resource cannot be resolved as
* absolute file path, i.e. if the resource is not available in a file system
* @throws IOException in case of general resolution/reading failures
* @see #getInputStream()
*/
File getFile() throws IOException;
/**
* Return a {@link ReadableByteChannel}.
* <p>It is expected that each call creates a <i>fresh</i> channel.
* <p>The default implementation returns {@link Channels#newChannel(InputStream)}
* with the result of {@link #getInputStream()}.
* @return the byte channel for the underlying resource (must not be {@code null})
* @throws java.io.FileNotFoundException if the underlying resource doesn't exist
* @throws IOException if the content channel could not be opened
* @since 5.0
* @see #getInputStream()
*/
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
/**
* Determine the content length for this resource.
* @throws IOException if the resource cannot be resolved
* (in the file system or as some other known physical resource type)
*/
long contentLength() throws IOException;
/**
* Determine the last-modified timestamp for this resource.
* @throws IOException if the resource cannot be resolved
* (in the file system or as some other known physical resource type)
*/
long lastModified() throws IOException;
/**
* Create a resource relative to this resource.
* @param relativePath the relative path (relative to this resource)
* @return the resource handle for the relative resource
* @throws IOException if the relative resource cannot be determined
*/
Resource createRelative(String relativePath) throws IOException;
/**
* Determine a filename for this resource, i.e. typically the last
* part of the path: for example, "myfile.txt".
* <p>Returns {@code null} if this type of resource does not
* have a filename.
*/
@Nullable
String getFilename();
/**
* Return a description for this resource,
* to be used for error output when working with the resource.
* <p>Implementations are also encouraged to return this value
* from their {@code toString} method.
* @see Object#toString()
*/
String getDescription();
}
请原谅我用代码占用了很大的篇幅,主要是为了让大家通过看文章不用去翻代码,能够连贯的读下来,同时没有把注释去掉的原因是,任何只看代码不看注释的行为都是耍流氓,多说一句,我觉得想要成为快乐的程序员,应该具备勇气和丰富的想象力。以上内容就需要你的想象力了,我啥都不说了。要用心,别走马观花,看热闹,觉得挺热闹然后给个赞,对自己啥收获也没有,那就是浪费时间,这是一个浪费了10余年老程序员的忠告,虽然现在他依然在浪费着时间,写到这里感觉自己有点飘了呢,开始好为人师了。
接下来着重说一下,Resource的神兵利器,ResourceLoader ,她算一个重头戏,资源加载器,拿来加载资源,为什么要有这么个东东呢,要根据她在spring中唯一的实现 DefaultResourceLoader 说起。
public class DefaultResourceLoader implements ResourceLoader {
@Nullable
private ClassLoader classLoader;
private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);
~~~~~~~~~~~~~~
/**
* Register the given resolver with this resource loader, allowing for
* additional protocols to be handled.
* <p>Any such resolver will be invoked ahead of this loader's standard
* resolution rules. It may therefore also override any default rules.
* @since 4.3
* @see #getProtocolResolvers()
*/
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.add(resolver);
}
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
不好意思,上面的类代码是不完整的,我删除了一些简单的构造函数,还有一些我没看懂的复杂函数,捡我认为重要的说,Resource getResource(String location),这核心的方法,也揭秘了ResourceLoader 存在的价值,首先我们知道我们在加载自愿的时候资源的形态是多样的,这个通过上面的注释也能看出来,有 UrlResource FileUrlResource FileSystemResource ClassPathResource,ResourceLoader 的作用就是可以让多样的资源来源加载用统一的方法来加载,主要是根据资源加载路径的组成方式,比如d:\abc.txt,www.baidu.com/abc.txt,classpath:abc.class, getResouce方法其实使用简单工厂模式的方式。public void addProtocolResolver(ProtocolResolver resolver) 这个方法其实是留给我们使用者去进行扩展的,当你的资源加载方式比较复杂,比如你的资源路径是通过读取另一些资源来拼装的,那么你可以自定义ProtocolResolver 来处理,因为getRource方法的第一句就是
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
如何去注册自己的ProtocolResolver 呢,applicationContext.addProtocolResolver();奥秘就是因为public abstract class AbstractApplicationContext extends DefaultResourceLoader。
如果你耐心的读到这里,我必须上点干货了,报答你的不走之恩。
ComponentScan 是如何应用这个ResourceLoader 的呢,ConfigurationClassPostProcessor 这个重头类就必须要说一下了,她是干嘛地…! 不得不说她是一个牛逼的类,干的事特别的多。
- 解析 @Configuration。
- 解析 @Bean 方法。
- 解析 @ComponentScan。
- 解析 @Import 注解。
- 解析 @ImportResource 注解。
- 解析 @PropertySource 注解。
可以这样说,所有bean的加载都是他来干,她实现了BeanDefinitionRegistryPostProcessor,懂得都懂,然后有一个关键台词,类里有一个成员变量,private ResourceLoader resourceLoader = new DefaultResourceLoader(); 然后把这个成员变量向下传递,一直到上文说到的private Set scanCandidateComponents(String basePackage)方法中哈,找到潜在的组件,什么组件无非就是上文说的加了@Component @Service @Controller等注解的类呗。
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
**Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);**
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
两句关键台词
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + ‘/’ + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
细心的读者会发现我们一直说的ResourceLoader 接口里面就没有getResources方法,这里面就提到一个衍生的接口,ResourcePatternResolver。
public interface ResourcePatternResolver extends ResourceLoader {
/**
* Pseudo URL prefix for all matching resources from the class path: "classpath*:"
* This differs from ResourceLoader's classpath URL prefix in that it
* retrieves all matching resources for a given name (e.g. "/beans.xml"),
* for example in the root of all deployed JAR files.
* @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
*/
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
/**
* Resolve the given location pattern into Resource objects.
* <p>Overlapping resource entries that point to the same physical
* resource should be avoided, as far as possible. The result should
* have set semantics.
* @param locationPattern the location pattern to resolve
* @return the corresponding Resource objects
* @throws IOException in case of I/O errors
*/
Resource[] getResources(String locationPattern) throws IOException;
}
getResourcePatternResolver 获取到的是PathMatchingResourcePatternResolver处理器,然后通过循环调用defaultResourceLoader的getResource方法来加载资源。今天先说这么多吧,对于加载还有不少内容,不放在一起了,以防都看蒙圈了。下一篇会着重讲下这里面的内容。