上一篇說到了手寫Spring AOP,來進行功能的增強,下面本篇內容主要是手寫Spring Config。通過配置的方式來使用Spring
前面內容鏈接:
我自橫刀向天笑,手寫Spring IOC容器,快來Look Look!
配置分析
為什么要提供配置的方式呢,之前的內容中我們測試的時候都是通過代碼來進行的:
GeneralBeanDefinition bd = new GeneralBeanDefinition();
bd.setBeanClass(Lad.class);
List<Object> args = new ArrayList<>();
args.add("sunwukong");
args.add(new BeanReference("magicGril"));
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("swk", bd);
bd = new GeneralBeanDefinition();
bd.setBeanClass(MagicGril.class);
args = new ArrayList<>();
args.add("baigujing");
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("magicGril", bd);
下面看下平時使用的時候,通過配置是什么樣的:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="girl" class="di.MagicGirl"
init-method="start" destroy-method="end">
<constructor-arg type="java.lang.String" value="girl"></constructor-arg>
<property name="friend" ref="boy"></property>
</bean>
<bean id="boy" class="di.Lad">
<constructor-arg type="java.lang.String" value="boy"></constructor-arg>
<constructor-arg type="di.MagicGirl" value="girl"></constructor-arg>
</bean>
</beans>
可以看出,提供配置的方式的優點:
- 實用簡單,改動起來比較靈活
- 而且不需要改動代碼
常用的配置方式,就是XML和注解的形式,它們的工作過程如下:
配置的工作過程
定義XML標記和注解
需要定義什么樣的XML標記和注解呢?通過之前的內容知道,配置的內容就是Bean定義信息,那么Bean定義的內容就是需要配置的內容
首先來看下Bean定義接口中有哪些信息:
XML配置的方式,首先需要定義一個DTD或者XSD文檔,來定義一套標記信息,去指定Bean定義
<bean id="girl" class="di.MagicGirl"
init-method="start" destroy-method="end">
<constructor-arg type="java.lang.String" value="girl"></constructor-arg>
<property name="friend" ref="boy"></property>
</bean>
可以看出,bean的配置指定的內容就是Bean定義接口中的信息
注解的方式,需要定義一套注解,那么需要哪些注解呢,也是Bean定義接口中的內容:
- 指定類、指定BeanName、指定scope、指定工廠方法、指定工廠Bean、指定init method、指定destroy method,這些在我們使用Spring的時候是通過@Component來實現的
- 指定構造參數的依賴:@Autowired、@Qualifier
- 指定屬性依賴:@Value
Bean配置的解析
Bean配置的解析過程,需要單獨的接口來實現,而不是在BeanFactory中來做,要做到單一職責原則,所以需要定義單獨的接口來解析Bean配置,然后再向BeanFactory注冊Bean定義
ApplicationContext接口
ApplicationContext這個接口就是用來完成Bean配置解析的,上面說到實現配置的方式有XML和注解,所以會有兩個實現類來實現ApplicationContext接口
- XML方式的實現:
- XML文件可能存在多個,所以這里使用了list
- 需要完成:加載xml、解析xml、創建Bean定義、注冊Bean定義的任務
- 注解方式的實現
- 掃描的包也會存在多個,這里也使用list
- 需要完成:掃描包、獲取注解、創建Bean定義、注冊Bean定義的任務
因為需要創建和注冊Bean定義,所以會使用到BeanFactory和BeanDefinitionRegistry接口,那么這部分代碼在子類中分別實現的話就會重復,所以抽象出來放在父類中:
用戶在使用的使用需要知道哪些接口和類呢?
- 指定配置相關:xml、注解
- 獲取bean相關:BeanFactory
那么可以使用外觀模式,讓用戶只需要知道ApplicationContext和其子類就行了,ApplicationContext可以繼承BeanFactory,繼而把兩個接口合在一起:
ApplicationContext接口:
/**
* @className: ApplicationContext
* 用來構建整個應用環境的接口,用來完成Bean的配置和解析
* 1:為了減少用戶對框架類接口的依賴,擴展了BeanFactory接口,
* Bean的配置和Bean的獲取都可以通過ApplicationContext接口來完成
* 2:配置資源的方式有xml和注解,所以存在xml和注解兩種子類的實現
* 3. Bean配置解析首先需要加載,所以實現了配置資源Resource的加載接口ResourceLoader
* @author: TR
*/
public interface ApplicationContext extends ResourceLoader,BeanFactory {
}
ApplicationContext的抽象類實現
/**
* @className: AbstractApplicationContext
* @description: ApplicationContext的抽象類實現
* @author: TR
*/
public abstract class AbstractApplicationContext implements ApplicationContext {
/** 用組合的方式來持有BeanFactory,完成BeanFactory接口的方法 */
protected BeanFactory beanFactory;
public AbstractApplicationContext() {
super();
this.beanFactory = new PreBuildBeanFactory();
}
public AbstractApplicationContext(BeanFactory beanFactory) {
super();
this.beanFactory = beanFactory;
}
@Override
public Object getBean(String beanName) throws Exception {
return this.beanFactory.getBean(beanName);
}
@Override
public void registerBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
this.beanFactory.registerBeanPostProcessor(beanPostProcessor);
}
}
xml配置方式的ApplicationContext實現類
/**
* @className: XmlApplicationContext
* @description: xml配置方式的ApplicationContext實現類
* @author: TR
*/
public class XmlApplicationContext extends AbstractApplicationContext {
}
注解配置方式的ApplicationContext實現類
/**
* @className: AnnotationApplicationContext
* @description: 注解配置方式的ApplicationContext實現類
* @author: TR
*/
public class AnnotationApplicationContext extends AbstractApplicationContext {
}
配置的實現
XML方式
XML文件來源的處理
xml配置文件的來源會有多種,比如:
不同來源的XML文件,它的加載方式是不一樣的,但是在解析的過程中,最后都希望獲取到InputStream
這里也需要設計一套接口,對於不同來源的XML文件分別進行處理
InputStreamSource接口
/**
* @className: InputStreamSource
* @description: 配置方式的最終統一接口
* @author: TR
*/
public interface InputStreamSource {
/**
* 最終要獲取的就是輸入流
* @return: java.io.InputStream
**/
InputStream getInputStream() throws IOException;
}
Resource接口
/**
* @className: Resource
* @description: 輸入流的資源擴展接口
* @author: TR
*/
public interface Resource extends InputStreamSource {
//classpath形式的xml配置文件
String CLASS_PATH_PREFIX = "classpath:";
//系統文件形式的xml配置文件
String File_SYSTEM_PREFIX = "file:";
/**
* 判斷資源是否存在
* @return: boolean
**/
boolean exists();
/**
* 是否可讀
* @return: boolean
**/
boolean isReadable();
/**
* 是否打開
* @return: boolean
**/
boolean isOpen();
/**
* 獲取資源文件
* @return: java.io.File
**/
File getFile();
}
InputStreamSource接口的實現類
FileSystemResource實現類:
/**
* @className: FileSystemResource
* @description: 系統文件類型的資源實現類
* @author: TR
*/
public class FileSystemResource implements Resource {
/** 文件資源對象 */
private File file;
public FileSystemResource(String fileName) {
super();
this.file = new File(fileName);
}
public FileSystemResource(File file) {
super();
this.file = file;
}
@Override
public boolean exists() {
return this.file == null ? false : this.file.exists();
}
@Override
public boolean isReadable() {
return this.file == null ? false : this.file.canRead();
}
@Override
public boolean isOpen() {
return false;
}
@Override
public File getFile() {
return file;
}
@Override
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
}
}
ClassPathResource實現類:
/**
* @className: ClassPathResource
* @description: classpath形式的資源實現類
* @author: TR
*/
public class ClassPathResource implements Resource {
//classpath所需要的信息
private String path;
private Class<?> clazz;
private ClassLoader classLoader;
public ClassPathResource(String path) {
this(path, null );
}
public ClassPathResource(String path, Class<?> clazz) {
this(path, clazz, null);
}
public ClassPathResource(String path, Class<?> clazz, ClassLoader classLoader) {
super();
this.path = path;
this.clazz = clazz;
this.classLoader = classLoader;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Class<?> getClazz() {
return clazz;
}
public void setClazz(Class<?> clazz) {
this.clazz = clazz;
}
public ClassLoader getClassLoader() {
return classLoader;
}
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public boolean exists() {
if (StringUtils.isNotBlank(path)) {
if (this.clazz != null) {
return this.clazz.getResource(path) != null;
}
if (this.classLoader != null) {
return this.classLoader.getResource(path.startsWith("/") ? path.substring(1) : path) != null;
}
return this.getClass().getResource(path) != null;
}
return false;
}
@Override
public boolean isReadable() {
return exists();
}
@Override
public boolean isOpen() {
return false;
}
@Override
public File getFile() {
return null;
}
@Override
public InputStream getInputStream() throws IOException {
if (StringUtils.isNotBlank(path)) {
if (this.clazz != null) {
return this.clazz.getResourceAsStream(path);
}
if (this.classLoader != null) {
return this.classLoader.getResourceAsStream(path.startsWith("/") ? path.substring(1) : path);
}
return this.getClass().getResourceAsStream(path);
}
return null;
}
}
UrlResource實現類:
/**
* @className: UrlResource
* @description: URL形式的資源實現類
* @author: TR
*/
public class UrlResource implements Resource {
/** url的資源對象 */
private URL url;
public UrlResource(String url) throws IOException {
this.url = new URL(url);
}
public UrlResource(URL url) {
super();
this.url = url;
}
public URL getUrl() {
return url;
}
public void setUrl(URL url) {
this.url = url;
}
@Override
public boolean exists() {
return this.url != null;
}
@Override
public boolean isReadable() {
return exists();
}
@Override
public boolean isOpen() {
return false;
}
@Override
public File getFile() {
return null;
}
@Override
public InputStream getInputStream() throws IOException {
return null;
}
}
XML資源加載器
用戶給定資源時是一個字符串,上面有三種資源,那么誰去負責創建這些資源呢
這里需要定義一個資源加載器,去分辨不同的資源,然后進行加載,這部分工作是由ApplicationContext來完成的,所以ApplicationContext需要繼承ResourceLoader接口
ResourceLoader接口:
/**
* @className: ResourceLoader
* 配置資源加載接口
* 不同的配置方式,加載過程不一樣,所以需要抽象出來一個接口應對變化的部分
* 雖然加載的方式不一樣,但是返回的資源結果是一樣的,都是Resource
* @author: TR
*/
public interface ResourceLoader {
/**
* 加載資源
* @param location:
* @return: demo.context.Resource
**/
Resource getResource(String location) throws IOException;
}
在這里,還需要區分用戶給的字符串代表的是哪種資源,所以需要定義字符串的規則:
注解方式
如何掃描的
掃描的包有哪些呢?
需要到指定的包目錄下找出所有的類文件,而且要包含子孫包下的
需要定義一個資源路徑的匹配行為
掃描的結果
掃描到了包下的class文件后,需要的是類名,而且掃描的是class文件,直接使用上面的FileResource即可
掃描的類ClassPathBeanDefinitionScanner
/**
* @className: ClassPathBeanDefinitionScanner
* @description: 掃描class文件
* @author: TR
*/
public class ClassPathBeanDefinitionScanner {
private static Log logger = LogFactory.getLog(ClassPathBeanDefinitionScanner.class);
private BeanDefinitionRegistry registry;
private BeanDefinitionReader reader;
private PathMatcher pathMatcher = new AntPathMatcher();
private String resourcePatter = "**/*.class";
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
super();
this.registry = registry;
this.reader = new AnnotationBeanDefinitionReader(registry);
}
/**
* 掃描包的方法
* @param basePackages:
* @return: void
**/
public void scan(String... basePackages) throws Throwable {
if (basePackages != null && basePackages.length > 0) {
for (String b : basePackages) {
this.reader.loadBeanDefintions(doScan(b));
}
}
}
/**
* 將掃描的class轉為Resource
* @param basePackage:
* @return: demo.context.Resource[]
**/
private Resource[] doScan(String basePackage) throws IOException {
// 掃描包下的類
// 構造初步匹配模式串,= 給入的包串 + / + **/*.class,替換里面的.為/
String pathPattern = StringUtils.replace(basePackage, ".", "/") + "/" + this.resourcePatter;
if (pathPattern.charAt(0) != '/') {
pathPattern = "/" + pathPattern;
}
// 找出模式的根包路徑
String rootPath = this.determineRootDir(pathPattern);
// 得到文件名匹配的絕對路徑模式
String fullPattern = this.getClass().getResource("/").toString() + pathPattern;
// 根據根包理解得到根包對應的目錄
File rootDir = new File(this.getClass().getResource(rootPath).toString());
// 存放找到的類文件的resource集合
Set<Resource> scanedClassFileResources = new HashSet<>();
// 調用doRetrieveMatchingFiles來掃描class文件
this.doRetrieveMatchingFiles(fullPattern, rootDir, scanedClassFileResources);
return (Resource[]) scanedClassFileResources.toArray();
}
private String determineRootDir(String location) {
int rootDirEnd = location.length();
rootDirEnd = location.indexOf('*');
int zi = location.indexOf('?');
if (zi != -1 && zi < rootDirEnd) {
rootDirEnd = location.lastIndexOf('/', zi);
}
if (rootDirEnd != -1) {
return location.substring(0, rootDirEnd);
} else {
return location;
}
}
/**
* 遞歸找指定目錄下的所有類,匹配模式的加入到結果中。
*
* @param fullPattern
* @param dir
* @param result
* @throws IOException
*/
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<Resource> result) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("Searching directory [" + dir.getAbsolutePath() + "] for files matching pattern ["
+ fullPattern + "]");
}
for (File content : listDirectory(dir)) {
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
if (!content.canRead()) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping subdirectory [" + dir.getAbsolutePath()
+ "] because the application is not allowed to read the directory");
}
} else {
doRetrieveMatchingFiles(fullPattern, content, result);
}
}
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(new FileSystemResource(content));
}
}
}
protected File[] listDirectory(File dir) {
File[] files = dir.listFiles();
if (files == null) {
if (logger.isInfoEnabled()) {
logger.info("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
}
return new File[0];
}
Arrays.sort(files, Comparator.comparing(File::getName));
return files;
}
public BeanDefinitionRegistry getRegistry() {
return registry;
}
public void setRegistry(BeanDefinitionRegistry registry) {
this.registry = registry;
}
public BeanDefinitionReader getReader() {
return reader;
}
public void setReader(BeanDefinitionReader reader) {
this.reader = reader;
}
public PathMatcher getPathMatcher() {
return pathMatcher;
}
public void setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
}
public String getResourcePatter() {
return resourcePatter;
}
public void setResourcePatter(String resourcePatter) {
this.resourcePatter = resourcePatter;
}
}
解析成Bean定義
XML和注解最終的輸出結果都是Resource,在這里還需要把Resource解析成Bean定義信息才行
需要定義接口來進行解析:
BeanDefinitionReader接口:
/**
* @className: BeanDefinitionReader
* @description: 將Resource資源解析成Bean定義的接口
* @author: TR
*/
public interface BeanDefinitionReader {
/**
* 解析單個資源
* @param resource:
* @return: void
**/
void loadBeanDefintions(Resource resource) throws Throwable;
/**
* 解析多個資源
* @param resource:
* @return: void
**/
void loadBeanDefintions(Resource... resource) throws Throwable;
}
AbstractBeanDefinitionReader抽象類:
/**
* @className: AbstractBeanDefinitionReader
* @description: TODO
* @date: 2021/6/10 15:58
* @author: jinpeng.sun
*/
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
/** 持有BeanDefinitionRegistry接口,以便完成注冊到BeanFactory中 */
protected BeanDefinitionRegistry beanDefinitionRegistry;
public AbstractBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
super();
this.beanDefinitionRegistry = beanDefinitionRegistry;
}
}
xml配置方式的bean定義解析器:
/**
* @className: XmlBeanDefinitionReader
* @description: xml配置方式的bean定義解析器
* @author: TR
*/
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
super(beanDefinitionRegistry);
}
@Override
public void loadBeanDefintions(Resource resource) throws Throwable {
this.loadBeanDefintions(new Resource[] {resource});
}
@Override
public void loadBeanDefintions(Resource... resource) throws Throwable {
if (resource != null && resource.length > 0) {
for (Resource r : resource) {
this.parseXml(r);
}
}
}
private void parseXml(Resource r) {
//TODO 解析xml文檔,獲取bean定義,創建bean定義對象,注冊到BeanDefinitionRegistry中
}
}
注解配置方式的bean定義解析器:
* @className: AnnotationBeanDefinitionReader
* @description: 注解配置方式的bean定義解析器:
* @author: TR
*/
public class AnnotationBeanDefinitionReader extends AbstractBeanDefinitionReader {
public AnnotationBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
super(beanDefinitionRegistry);
}
@Override
public void loadBeanDefintions(Resource resource) throws Throwable {
this.loadBeanDefintions(new Resource[] {resource});
}
@Override
public void loadBeanDefintions(Resource... resource) throws Throwable {
if (resource != null && resource.length > 0) {
for (Resource r : resource) {
this.retriveAndRegistBeanDefinition(r);
}
}
}
private void retriveAndRegistBeanDefinition(Resource resource) {
if(resource != null && resource.getFile() != null) {
String className = getClassNameFormFile(resource.getFile());
try {
Class<?> clazz = Class.forName(className);
Component component = clazz.getAnnotation(Component.class);
if (component != null) {
GeneralBeanDefinition beanDefinition = new GeneralBeanDefinition();
beanDefinition.setBeanClass(clazz);
beanDefinition.setScope(component.scope());
beanDefinition.setFactoryMethodName(component.factoryMethodName());
beanDefinition.setFactoryBeanName(component.factoryBeanName());
beanDefinition.setInitMethodName(component.initMethodName());
beanDefinition.setDestroyMethodName(component.destroyMethodName());
//獲取所有的構造方法,在構造方法上找Autowired注解,如果有的話,將這個構造方法set到bd
this.handleConstructor(clazz, beanDefinition);
//處理工廠方法參數依賴
if(StringUtils.isNotBlank(beanDefinition.getFactoryMethodName())) {
this.handleFactoryMethodArgs(clazz, beanDefinition);
}
//處理屬性依賴
this.handlePropertyDi(clazz, beanDefinition);
String beanName = "".equals(component.value()) ? component.name() : null;
if (StringUtils.isBlank(beanName)) {
// TODO 應用名稱生成規則生成beanName;
// 默認駝峰命名法
beanName = CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, clazz.getSimpleName());
}
// 注冊bean定義
this.beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
}
} catch (ClassNotFoundException | BeanDefinitionException e) {
e.printStackTrace();
}
}
}
private void handlePropertyDi(Class<?> clazz, GeneralBeanDefinition bd) {
// TODO Auto-generated method stub
}
private void handleFactoryMethodArgs(Class<?> clazz, GeneralBeanDefinition bd) {
// TODO Auto-generated method stub
}
private void handleConstructor(Class<?> clazz, GeneralBeanDefinition bd) {
//獲取所有的構造方法,在構造方法上找Autowired注解,如果有的話,將這個構造方法set到bd
Constructor<?>[] constructors = clazz.getConstructors();
if (constructors != null && constructors.length > 0) {
for (Constructor c : constructors) {
if (c.getAnnotation(Autowired.class) != null) {
bd.setConstructor(c);
Parameter[] ps = c.getParameters();
//遍歷獲取參數上的注解,及創建參數依賴
break;
}
}
}
}
private int classPathAbsLength = AnnotationBeanDefinitionReader.class.getResource("/").toString().length();
private String getClassNameFormFile(File file) {
//返回絕對路徑名字符串
String absPath = file.getAbsolutePath();
String name = absPath.substring(classPathAbsLength+1, absPath.indexOf("."));
return StringUtils.replace(name, File.separator, ".");
}
}
完善XmlApplicationContext和AnnotationApplicationContext:
public class XmlApplicationContext extends AbstractApplicationContext {
private List<Resource> resources;
private BeanDefinitionReader definitionReader;
public XmlApplicationContext(String... locations) throws Throwable {
super();
load(locations);
//資源解析成BeanDefinition,外派給BeanDefinitionReader接口來實現
this.definitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) this.beanFactory);
Resource[] resourceArray = new Resource[resources.size()];
resources.toArray(resourceArray);
//將解析后的BeanDefinition裝載到BeanFactory中
definitionReader.loadBeanDefintions(resourceArray);
}
/**
* 根據用戶指定的配置文件位置,加載資源信息
* @param locations:
* @return: void
**/
private void load(String[] locations) throws IOException {
if (resources == null) {
resources = new ArrayList<Resource>();
}
//完成加載,創建好Resource
if (locations != null && locations.length > 0) {
for (String lo : locations) {
Resource resource = getResource(lo);
if (resource != null) {
this.resources.add(resource);
}
}
}
}
@Override
public Resource getResource(String location) throws IOException {
if (StringUtils.isNotBlank(location)) {
//根據字符串的前綴判斷區分,class、系統文件、url三種資源的加載
if (location.startsWith(Resource.CLASS_PATH_PREFIX)) {
return new ClassPathResource(location.substring(Resource.CLASS_PATH_PREFIX.length()));
} else if (location.startsWith(Resource.File_SYSTEM_PREFIX)) {
return new FileSystemResource(location.substring(Resource.File_SYSTEM_PREFIX.length()));
} else {
return new UrlResource(location);
}
}
return null;
}
}
public class AnnotationApplicationContext extends AbstractApplicationContext {
private ClassPathBeanDefinitionScanner scanner;
public AnnotationApplicationContext(String... locations) throws Throwable {
scanner = new ClassPathBeanDefinitionScanner((BeanDefinitionRegistry) this.beanFactory);
scanner.scan(locations);
}
@Override
public Resource getResource(String location) throws IOException {
return null;
}
}