飞道的博客

05. 手写Spring核心框架

170人阅读  评论(0)

目录

05 手写Spring核心框架

Pt1 手写IoC/DI

Pt1.1 流程设计

Pt1.2 基础配置

application.properties

pom.xml

web.xml

Pt1.3 注解定义

@MyController

@MyService

@MyAutowired

@MyRequestMapping

@MyRequestParam

Pt1.4 核心代码

DispatcherServlet

ApplicationContext

BeanDefinition

BeanWrapper

Pt1.5 功能验证

Pt2 手写MVC

Pt2.1 流程设计

Pt2.2 MVC九大组件

Pt2.3 基础配置

Pt2.4 核心代码

DispatcherServlet

HandlerMapping

HandlerAdapter

ModelAndView

ViewResolver

View

Pt2.5 功能验证

Pt3 手写AOP

Pt3.1 流程设计

Pt3.2 基础配置

application.properties

Pt3.3 核心代码

AopProxy

JdkDynamicAopProxy

AdvisedSupport

Advice

AopConfig

ApplicationContext

Pt3.4 功能验证


05 手写Spring核心框架

在我们还没有开始Spring源码分析之前,先尝试模仿Spring手写一套类似Spring的核心骨架,这套代码能够帮助我们更好的理解Spring源码的实现架构,帮助我们在源码分析时构建更加清晰的思维脉络。所以这部分其实非常重要,当你在后续的源码分析中迷失时,只需要回到这里,看看我们在手写这段框架时关注了那些组件,就可以把我们的焦点拉回到主干这条线上,沿着这条线就不会迷失。

 

这部分代码,我已经上传到github上,可以参考。

https://github.com/ChenMingMing821/myspring5-action.git

 


Pt1 手写IoC/DI

Pt1.1 流程设计

IoC + DI是在启动的时候初始化的,负责管理Bean的生命周期和依赖关系。

IoC和DI主要涉及核心类:

  • DispatcherServlet:在web.xml定义的启动类Servlet。负责Web容器初始化,以及拦截客户端请求并完成调度和分发;

  • ApplicationContext:Spring运行上下文。负责读取Spring配置,扫描Bean,保存IoC容器。

  • BeanDefinition:保存Spring Bean的定义信息。

  • BeanWrapper:Spring对BeanDefinition的代理,包含了Bean定义和实例化对象信息。

  • BeanDefinitionReader:负责加载Spring配置,读取Bean定义。

 

 

IoC和DI是在Spring启动的过程中完成的,其中DispatcherServlet是入口,初始化整个流程是在ApplicationContext进行控制的。 过程大体分为以下几个步骤:

  1. 加载/解析Spring配置文件,扫描Bean;

  2. 将读取的Bean定义封装成BeanDefinition;

  3. 将Bean注册到IoC容器(未实例化);

  4. 完成依赖注入(自动);

 


Pt1.2 基础配置

application.properties

Spring IoC的自动扫描需要配置scanPackage,即扫描根路径,Spring会扫描根路径下所有Bean定义。当然在Spring中,该配置是在Spring的xml中配置的,这里我们直接以properties文件的形式进行配置,简化代码读取的逻辑,本质上是一样的。


  
  1.   # 配置类扫描包路径
  2.  scanPackage=com.demo.spring.simulation.v5.test

 

pom.xml

从依赖上可以看出,核心只有Servlet,引入日志、Lombok和Junit是用来简化开发和输出一些验证信息。没有任何的Spring组件依赖,我们要自己手写Spring的核心流程,实现上还是尽量干净一些。


  
  1.   <?xml version="1.0" encoding="UTF-8"?>
  2.  ​
  3.   <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.           xsi:schemaLocation= "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.       <modelVersion>4.0.0 </modelVersion>
  6.  ​
  7.       <groupId>org.spring5 </groupId>
  8.       <artifactId>myspring5-action </artifactId>
  9.       <version>1.0-SNAPSHOT </version>
  10.       <packaging>war </packaging>
  11.  ​
  12.       <name>myspring5-action Maven Webapp </name>
  13.       <!-- FIXME change it to the project's website -->
  14.       <url>http://www.example.com </url>
  15.  ​
  16.       <properties>
  17.           <project.build.sourceEncoding>UTF-8 </project.build.sourceEncoding>
  18.           <maven.compiler.source>1.8 </maven.compiler.source>
  19.           <maven.compiler.target>1.8 </maven.compiler.target>
  20.       </properties>
  21.  ​
  22.       <dependencies>
  23.           <dependency>
  24.               <groupId>javax.servlet </groupId>
  25.               <artifactId>javax.servlet-api </artifactId>
  26.               <version>3.1.0 </version>
  27.           </dependency>
  28.  ​
  29.           <dependency>
  30.               <groupId>org.projectlombok </groupId>
  31.               <artifactId>lombok </artifactId>
  32.               <version>1.18.12 </version>
  33.           </dependency>
  34.  ​
  35.           <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
  36.           <dependency>
  37.               <groupId>org.slf4j </groupId>
  38.               <artifactId>slf4j-api </artifactId>
  39.               <version>1.7.25 </version>
  40.           </dependency>
  41.  ​
  42.           <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
  43.           <dependency>
  44.               <groupId>ch.qos.logback </groupId>
  45.               <artifactId>logback-classic </artifactId>
  46.               <version>1.2.3 </version>
  47.           </dependency>
  48.  ​
  49.           <dependency>
  50.               <groupId>org.junit.jupiter </groupId>
  51.               <artifactId>junit-jupiter </artifactId>
  52.               <version>RELEASE </version>
  53.               <scope>test </scope>
  54.           </dependency>
  55.       </dependencies>
  56.  ​
  57.       <build>
  58.           <finalName>myspring5-action </finalName>
  59.           <pluginManagement> <!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
  60.               <plugins>
  61.                   <plugin>
  62.                       <artifactId>maven-clean-plugin </artifactId>
  63.                       <version>3.1.0 </version>
  64.                   </plugin>
  65.                   <plugin>
  66.                       <artifactId>maven-resources-plugin </artifactId>
  67.                       <version>3.0.2 </version>
  68.                   </plugin>
  69.                   <plugin>
  70.                       <artifactId>maven-compiler-plugin </artifactId>
  71.                       <version>3.8.0 </version>
  72.                   </plugin>
  73.                   <plugin>
  74.                       <artifactId>maven-surefire-plugin </artifactId>
  75.                       <version>2.22.1 </version>
  76.                   </plugin>
  77.                   <plugin>
  78.                       <artifactId>maven-war-plugin </artifactId>
  79.                       <version>3.2.2 </version>
  80.                   </plugin>
  81.                   <plugin>
  82.                       <artifactId>maven-install-plugin </artifactId>
  83.                       <version>2.5.2 </version>
  84.                   </plugin>
  85.                   <plugin>
  86.                       <artifactId>maven-deploy-plugin </artifactId>
  87.                       <version>2.8.2 </version>
  88.                   </plugin>
  89.               </plugins>
  90.           </pluginManagement>
  91.       </build>
  92.   </project>

 


web.xml

配置非常简单,定义了Web容器启动Servlet(MyDispatcherServlet)和配置路径。


  
  1.   <?xml version="1.0" encoding="UTF-8"?>
  2.   <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  3.           xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  4.           xsi:schemaLocation= "http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  5.           version= "3.1">
  6.  ​
  7.       <display-name>myspring5-action </display-name>
  8.       <welcome-file-list>
  9.           <welcome-file>index.jsp </welcome-file>
  10.       </welcome-file-list>
  11.      
  12.       <servlet>
  13.           <servlet-name>springMVC </servlet-name>
  14.           <servlet-class>com.demo.spring.simulation.v5.servlet.MyDispatcherServlet </servlet-class>
  15.           <init-param>
  16.               <param-name>contextConfigLocation </param-name>
  17.               <param-value>classpath:application.properties </param-value>
  18.           </init-param>
  19.           <load-on-startup>1 </load-on-startup>
  20.           <async-supported>true </async-supported>
  21.       </servlet>
  22.       <servlet-mapping>
  23.           <servlet-name>springMVC </servlet-name>
  24.           <url-pattern>/* </url-pattern>
  25.       </servlet-mapping>
  26.   </web-app>

 


Pt1.3 注解定义

Spring注解比较多,根据模拟过程中的需要选择性的实现部分。

 

@MyController


  
  1.   package com.demo.spring.simulation.v5.annotation;
  2.  ​
  3.   import java.lang.annotation.*;
  4.  ​
  5.   /**
  6.   * 自定义Controller注解
  7.   */
  8.   @Target({ElementType.TYPE})
  9.   @Retention(RetentionPolicy.RUNTIME)
  10.   @Documented
  11.   public @interface MyController {
  12.       String value() default "";
  13.  }

 

@MyService


  
  1.   package com.demo.spring.simulation.v5.annotation;
  2.  ​
  3.   import java.lang.annotation.*;
  4.  ​
  5.   /**
  6.   * 自定义Service注解
  7.   */
  8.   @Target({ElementType.TYPE})
  9.   @Retention(RetentionPolicy.RUNTIME)
  10.   @Documented
  11.   public @interface MyService {
  12.       String value() default "";
  13.  }

 

@MyAutowired


  
  1.   package com.demo.spring.simulation.v5.annotation;
  2.  ​
  3.   import java.lang.annotation.*;
  4.  ​
  5.   /**
  6.   * 自定义Autowired注解
  7.   */
  8.   @Target({ElementType.FIELD})
  9.   @Retention(RetentionPolicy.RUNTIME)
  10.   @Documented
  11.   public @interface MyAutowired {
  12.       String value() default "";
  13.  }

 

@MyRequestMapping


  
  1.   package com.demo.spring.simulation.v5.annotation;
  2.  ​
  3.   import java.lang.annotation.*;
  4.  ​
  5.   /**
  6.   * 自定义RequestMapping注解
  7.   */
  8.   @Target({ElementType.TYPE, ElementType.METHOD})
  9.   @Retention(RetentionPolicy.RUNTIME)
  10.   @Documented
  11.   public @interface MyRequestMapping {
  12.       String value() default "";
  13.  }

 

@MyRequestParam


  
  1.   package com.demo.spring.simulation.v5.annotation;
  2.  ​
  3.   import java.lang.annotation.*;
  4.  ​
  5.   /**
  6.   * 自定义RequestParam注解
  7.   */
  8.   @Target({ElementType.PARAMETER})
  9.   @Retention(RetentionPolicy.RUNTIME)
  10.   @Documented
  11.   public @interface MyRequestParam {
  12.       String value() default "";
  13.  }

 


Pt1.4 核心代码

DispatcherServlet

DispatcherServlet作为启动的整个入口,init()负责初始化IoC、DI、MVC和AOP的环境。这里先介绍IoC和DI的加载过程,从代码可以看出,初始化过程是在ApplicationContext中完成的。

DispatherServlet#init是入口,我们从这里开始看整个流程的处理。


  
  1. /**
  2. * DispatcherServlet负责请求调度和分发。
  3. */
  4. public class MyDispatcherServlet extends HttpServlet {
  5. // Spring配置文件路径
  6. private static final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";
  7. // Spring上下文,Spring IoC容器
  8. private MyApplicationContext applicationContext;
  9. @Override
  10. public void init(ServletConfig config) throws ServletException {
  11. log.info( "DispatcherServlet -> Create Web Server Starting.");
  12. // 1、初始化ApplicationContext。ApplicationContext包含了Spring核心IoC容器,完成Bean扫描、初始化和DI。
  13. log.info( "DispatcherServlet -> Init Spring IoC/DI Starting.");
  14. applicationContext = new MyApplicationContext(config.getInitParameter(CONTEXT_CONFIG_LOCATION));
  15. log.info( "DispatcherServlet -> Init Spring IoC/DI Finished.");
  16. // TODO
  17. log.info( "DispatcherServlet -> Create Web Server Finished.");
  18. }
  19. @Override
  20. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  21. this.doPost(req, resp);
  22. }
  23. @Override
  24. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  25. // TODO
  26. }
  27. }

 


ApplicationContext

DispatcherServlet#init直接调用ApplicationContext的构造器执行IoC初始化,根据ApplicationContext构造器的逻辑一步一步来看代码逻辑。


  
  1. package com.demo.spring.simulation.v5.context;
  2. import com.demo.spring.simulation.v5.annotation.MyAutowired;
  3. import com.demo.spring.simulation.v5.annotation.MyController;
  4. import com.demo.spring.simulation.v5.annotation.MyService;
  5. import com.demo.spring.simulation.v5.aop.MyJdkDynamicAopProxy;
  6. import com.demo.spring.simulation.v5.aop.config.MyAopConfig;
  7. import com.demo.spring.simulation.v5.aop.support.MyAdvisedSupport;
  8. import com.demo.spring.simulation.v5.beans.MyBeanWrapper;
  9. import com.demo.spring.simulation.v5.beans.config.MyBeanDefinition;
  10. import com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader;
  11. import lombok.extern.slf4j.Slf4j;
  12. import java.lang.reflect.Field;
  13. import java.util.HashMap;
  14. import java.util.List;
  15. import java.util.Map;
  16. import java.util.Properties;
  17. import java.util.concurrent.ConcurrentHashMap;
  18. /**
  19. * 完成Bean的扫描、创建和DI。
  20. */
  21. @Slf4j
  22. public class MyApplicationContext {
  23. // 负责读取Bean配置
  24. private MyBeanDefinitionReader reader;
  25. // 存储注册Bean定义的IoC容器
  26. private Map<String, MyBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, MyBeanDefinition>();
  27. // 存放单例的IoC容器
  28. private Map<String, Object> factoryBeanObjectCache = new HashMap<String, Object>();
  29. // 通用的IoC容器
  30. private Map<String, MyBeanWrapper> factoryBeanInstanceCache = new HashMap<String, MyBeanWrapper>();
  31. /**
  32. * Spring上下文环境初始化
  33. *
  34. * @param configLocations 配置文件路径
  35. */
  36. public MyApplicationContext(String... configLocations) {
  37. // 1、加载、解析配置文件,扫描相关的类。
  38. reader = new MyBeanDefinitionReader(configLocations);
  39. log.info( "ApplicationContext -> 1、加载、解析配置文件,扫描相关的类。");
  40. try {
  41. // 2、将扫描的Bean封装成BeanDefinition。
  42. List<MyBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
  43. log.info( "ApplicationContext -> 2、将扫描的Bean封装成BeanDefinition。");
  44. // 3、注册,把BeanDefintion缓存到容器。
  45. doRegistBeanDefinition(beanDefinitions);
  46. log.info( "ApplicationContext -> 3、注册,把BeanDefintion缓存到容器。");
  47. // 4、完成自动依赖注入。
  48. doAutowrited();
  49. log.info( "ApplicationContext -> 4、完成自动依赖注入。");
  50. } catch (Exception e) {
  51. e.printStackTrace();
  52. }
  53. }
  54. /**
  55. * 完成Bean的实例化和自动依赖注入(非延迟加载的场景)。
  56. */
  57. private void doAutowrited() {
  58. // 到这步,所有的Bean并没有真正的实例化,还只是配置阶段。
  59. for (Map.Entry<String, MyBeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
  60. String beanName = beanDefinitionEntry.getKey();
  61. // getBean才真正完成依赖注入
  62. getBean(beanName);
  63. }
  64. }
  65. /**
  66. * 把BeanDefintion缓存起来
  67. *
  68. * @param beanDefinitions 通过扫描配置文件获取的Bean定义
  69. * @throws Exception
  70. */
  71. private void doRegistBeanDefinition(List<MyBeanDefinition> beanDefinitions) throws Exception {
  72. log.info( "ApplicationContext -> 缓存BeanDefinition信息。");
  73. for (MyBeanDefinition beanDefinition : beanDefinitions) {
  74. // Bean在IoC容器中名称必须唯一
  75. if ( this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
  76. throw new Exception( "The " + beanDefinition.getFactoryBeanName() + "is exists");
  77. }
  78. // 分别用两种名称存储,便于查找
  79. beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
  80. beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
  81. }
  82. }
  83. /**
  84. * Bean的实例化和DI是从这个方法开始的。
  85. *
  86. * @param beanName
  87. * @return
  88. */
  89. public Object getBean(String beanName) {
  90. log.info( "ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。");
  91. // 1、先拿到BeanDefinition配置信息
  92. MyBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
  93. // 2、反射实例化newInstance();
  94. Object instance = instantiateBean(beanName, beanDefinition);
  95. // 3、封装成一个叫做BeanWrapper
  96. MyBeanWrapper beanWrapper = new MyBeanWrapper(instance);
  97. // 4、保存到IoC容器
  98. factoryBeanInstanceCache.put(beanName, beanWrapper);
  99. // 5、执行依赖注入
  100. populateBean(beanName, beanDefinition, beanWrapper);
  101. // 6、返回对象
  102. return beanWrapper.getWrapperInstance();
  103. }
  104. public Object getBean(Class<?> beanClass) {
  105. return getBean(beanClass.getName());
  106. }
  107. /**
  108. * DI核心逻辑。
  109. *
  110. * @param beanName
  111. * @param beanDefinition
  112. * @param beanWrapper
  113. */
  114. private void populateBean(String beanName, MyBeanDefinition beanDefinition, MyBeanWrapper beanWrapper) {
  115. log.info( "ApplicationContext -> 完成依赖注入核心逻辑。");
  116. // TODO 可能涉及到循环依赖待解决,如果依赖对象还未实例化,注入的示例为Null引发后续问题,这里需要考虑如何解决。
  117. // 1、拿到当前Bean实例化对象
  118. Object instance = beanWrapper.getWrapperInstance();
  119. // 2、拿到当前Bean的类信息
  120. Class<?> clazz = beanWrapper.getWrapperClass();
  121. // 3、只有注解的类,才执行依赖注入
  122. if (!(clazz.isAnnotationPresent(MyController.class) || clazz.isAnnotationPresent(MyService.class))) {
  123. return;
  124. }
  125. // 把所有的包括private/protected/default/public 修饰字段都取出来
  126. // TODO 这里只考虑接口注入的方式,实际还要考虑构造器注入和Setter注入。
  127. for (Field field : clazz.getDeclaredFields()) {
  128. // 是否被Autowired标记为自动注入
  129. if (!field.isAnnotationPresent(MyAutowired.class)) {
  130. continue;
  131. }
  132. MyAutowired autowired = field.getAnnotation(MyAutowired.class);
  133. // 如果用户没有自定义的beanName,就默认根据类型注入
  134. String autowiredBeanName = autowired.value().trim();
  135. if ("".equals(autowiredBeanName)) {
  136. // field.getType().getName() 获取字段的类型的全限定名
  137. autowiredBeanName = toLowerFirstCase(field.getType().getSimpleName());
  138. }
  139. // 暴力访问
  140. field.setAccessible( true);
  141. try {
  142. // 获取对应名称的bean实例对象 TODO
  143. // 此处没有考虑Bean的实例化顺序,可能需要注入的对象此时还没有完成实例化,在IoC容器中无法正确获取。不过除了在初始化时触发DI,
  144. // 在实际调用的时候,通过getBean()获取对象时,仍然会触发DI操作。
  145. if ( this.factoryBeanInstanceCache.get(autowiredBeanName) == null) {
  146. continue;
  147. }
  148. // ioc.get(beanName) 相当于通过接口的全名拿到接口的实现的实例
  149. field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
  150. } catch (IllegalAccessException e) {
  151. e.printStackTrace();
  152. continue;
  153. }
  154. }
  155. }
  156. /**
  157. * 创建真正的实例对象
  158. *
  159. * @param beanName
  160. * @param beanDefinition
  161. * @return
  162. */
  163. private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) {
  164. log.info( "ApplicationContext -> 通过反射创建Bean实例。");
  165. String className = beanDefinition.getBeanClassName();
  166. Object instance = null;
  167. try {
  168. Class<?> clazz = Class.forName(className);
  169. instance = clazz.newInstance();
  170. // 默认的类名首字母小写
  171. this.factoryBeanObjectCache.put(beanName, instance);
  172. } catch (Exception e) {
  173. e.printStackTrace();
  174. }
  175. return instance;
  176. }
  177. /**
  178. * 已注册所有Bean的名称
  179. *
  180. * @return
  181. */
  182. public String[] getBeanDefinitionNames() {
  183. return this.beanDefinitionMap.keySet().toArray( new String[ this.beanDefinitionMap.size()]);
  184. }
  185. /**
  186. * 已注册Bean的数量
  187. *
  188. * @return
  189. */
  190. public int getBeanDefinitionCount() {
  191. return this.beanDefinitionMap.size();
  192. }
  193. public Properties getConfig() {
  194. return this.reader.getConfig();
  195. }
  196. /**
  197. * 将大写字母转换为小写
  198. *
  199. * @param simpleName
  200. * @return
  201. */
  202. private String toLowerFirstCase(String simpleName) {
  203. char[] chars = simpleName.toCharArray();
  204. chars[ 0] += 32;
  205. return String.valueOf(chars);
  206. }
  207. }

 


BeanDefinition


  
  1. package com.demo.spring.simulation.v5.beans.config;
  2. import lombok.Data;
  3. /**
  4. * Spring Bean定义信息
  5. */
  6. @Data
  7. public class MyBeanDefinition {
  8. // Bean全路径类名
  9. private String beanClassName;
  10. // Bean在IoC容器中名称
  11. private String factoryBeanName;
  12. }
  13. BeanDefinitionReader
  14. package com.demo.spring.simulation.v5.beans.support;
  15. import com.demo.spring.simulation.v5.beans.config.MyBeanDefinition;
  16. import lombok.extern.slf4j.Slf4j;
  17. import java.io.File;
  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.net.URL;
  21. import java.util.ArrayList;
  22. import java.util.List;
  23. import java.util.Properties;
  24. /**
  25. * 扫描配置文件,读取Bean定义
  26. */
  27. @Slf4j
  28. public class MyBeanDefinitionReader {
  29. // 保存扫描的结果
  30. private List<String> regitryBeanClasses = new ArrayList<String>();
  31. // 保存配置信息
  32. private Properties contextConfig = new Properties();
  33. public MyBeanDefinitionReader(String... configLocations) {
  34. log.info( "BeanDefinitionReader -> 构造器执行开始。");
  35. // 1、读取配置信息。
  36. doLoadConfig(configLocations[ 0]);
  37. log.info( "BeanDefinitionReader -> 1、读取配置信息。");
  38. // 2、扫描配置文件中的配置的相关的类。
  39. doScanner(contextConfig.getProperty( "scanPackage"));
  40. log.info( "BeanDefinitionReader -> 2、扫描配置文件中的配置的相关的类。");
  41. log.info( "BeanDefinitionReader -> 构造器执行完成。");
  42. }
  43. /**
  44. * 将Bean封装为BeanDefinition
  45. *
  46. * @return
  47. */
  48. public List<MyBeanDefinition> loadBeanDefinitions() {
  49. log.info( "BeanDefinitionReader -> 将扫描的Bean信息封装成BeanDefinition。");
  50. List<MyBeanDefinition> result = new ArrayList<MyBeanDefinition>();
  51. try {
  52. for (String className : regitryBeanClasses) {
  53. Class<?> beanClass = Class.forName(className);
  54. // 接口不能实例化
  55. if (beanClass.isInterface()) {
  56. continue;
  57. }
  58. // BeanName有三种情况:
  59. // 1、默认是类名首字母小写
  60. // 2、自定义名称
  61. // 3、接口注入
  62. result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getSimpleName()), beanClass.getName()));
  63. // 如果是多个实现类,只能覆盖
  64. for (Class<?> i : beanClass.getInterfaces()) {
  65. result.add(doCreateBeanDefinition(i.getName(), beanClass.getName()));
  66. }
  67. }
  68. } catch (Exception e) {
  69. e.printStackTrace();
  70. }
  71. return result;
  72. }
  73. private MyBeanDefinition doCreateBeanDefinition(String beanName, String beanClassName) {
  74. MyBeanDefinition beanDefinition = new MyBeanDefinition();
  75. beanDefinition.setFactoryBeanName(beanName);
  76. beanDefinition.setBeanClassName(beanClassName);
  77. return beanDefinition;
  78. }
  79. /**
  80. * 从配置文件中加载Spring配置信息
  81. *
  82. * @param contextConfigLocation
  83. */
  84. private void doLoadConfig(String contextConfigLocation) {
  85. log.info( "BeanDefinitionReader -> 加载Spring配置文件。");
  86. InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation.replaceAll( "classpath:", ""));
  87. try {
  88. contextConfig.load(is);
  89. } catch (IOException e) {
  90. e.printStackTrace();
  91. } finally {
  92. if ( null != is) {
  93. try {
  94. is.close();
  95. } catch (IOException e) {
  96. e.printStackTrace();
  97. }
  98. }
  99. }
  100. }
  101. /**
  102. * 根据配置的basePackage扫描获取Bean定义
  103. *
  104. * @param scanPackage
  105. */
  106. private void doScanner(String scanPackage) {
  107. log.info( "BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。");
  108. //jar 、 war 、zip 、rar
  109. URL url = this.getClass().getClassLoader().getResource(scanPackage.replaceAll( "\\.", "/"));
  110. File classPath = new File(url.getFile());
  111. //当成是一个ClassPath文件夹
  112. for (File file : classPath.listFiles()) {
  113. if (file.isDirectory()) {
  114. doScanner(scanPackage + "." + file.getName());
  115. } else {
  116. if (!file.getName().endsWith( ".class")) {
  117. continue;
  118. }
  119. //全类名 = 包名.类名
  120. String className = (scanPackage + "." + file.getName().replace( ".class", ""));
  121. //Class.forName(className);
  122. regitryBeanClasses.add(className);
  123. }
  124. }
  125. }
  126. /**
  127. * 将大写字母转换为小写
  128. *
  129. * @param simpleName
  130. * @return
  131. */
  132. private String toLowerFirstCase(String simpleName) {
  133. char[] chars = simpleName.toCharArray();
  134. chars[ 0] += 32;
  135. return String.valueOf(chars);
  136. }
  137. /**
  138. * 获取配置信息
  139. *
  140. * @return
  141. */
  142. public Properties getConfig() {
  143. return this.contextConfig;
  144. }
  145. }

 


BeanWrapper


  
  1. package com.demo.spring.simulation.v5.beans;
  2. /**
  3. * Spring IoC容器对Bean生成的代理类
  4. */
  5. public class MyBeanWrapper {
  6. // Bean的实例化对象
  7. private Object wrappedInstance;
  8. // Bean的Class信息
  9. private Class<?> wrapperClass;
  10. public MyBeanWrapper(Object wrappedInstance) {
  11. this.wrappedInstance = wrappedInstance;
  12. this.wrapperClass = wrappedInstance.getClass();
  13. }
  14. public Object getWrapperInstance() {
  15. return this.wrappedInstance;
  16. }
  17. // 返回代理Class
  18. public Class<?> getWrapperClass() {
  19. return this.wrapperClass;
  20. }
  21. }

 


Pt1.5 功能验证

IoC测试我们直接以main启动ApplicationContext的模式来验证,推荐使用DEBUG模式看每一步数据的处理,这里为了简单描述,我直接输出测试代码和结果。

 

  • 测试启动类


  
  1. /**
  2. * Spring IoC和DI测试类。
  3. */
  4. public class MyApplicationContextTest {
  5. public static void main(String[] args) {
  6. MyApplicationContext applicationContext = new MyApplicationContext( "classpath:application.properties");
  7. // IoC容器初始化时,通过执行getBean完成DI。此处再次调用getBean防止有未被注入的属性。
  8. DemoController demoController = (DemoController) applicationContext.getBean(DemoController.class);
  9. demoController.say();
  10. }
  11. }

 

  • 业务处理入口


  
  1. @MyController
  2. public class DemoController {
  3. @MyAutowired()
  4. private DemoService demoService;
  5. public void say() {
  6. demoService.say();
  7. }
  8. }

 

  • 业务处理核心类


  
  1. @MyService
  2. public class DemoService {
  3. public void say() {
  4. System.out.println( "执行自定义Service方法。");
  5. }
  6. }

 

  • 测试结果输出

启动测试类,看输出结果。


  
  1. 17:02:30.638 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 构造器执行开始。
  2. 17:02:30.645 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 加载Spring配置文件。
  3. 17:02:30.648 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 1、读取配置信息。
  4. 17:02:30.648 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。
  5. 17:02:30.649 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。
  6. 17:02:30.652 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。
  7. 17:02:30.653 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。
  8. 17:02:30.653 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 2、扫描配置文件中的配置的相关的类。
  9. 17:02:30.653 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 构造器执行完成。
  10. 17:02:30.653 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 1、加载、解析配置文件,扫描相关的类。
  11. 17:02:30.653 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 将扫描的Bean信息封装成BeanDefinition。
  12. 17:02:30.658 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 2、将扫描的Bean封装成BeanDefinition。
  13. 17:02:30.658 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 缓存BeanDefinition信息。
  14. 17:02:30.658 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 3、注册,把BeanDefintion缓存到容器。
  15. 17:02:30.659 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  16. 17:02:30.659 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  17. 17:02:30.674 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  18. 17:02:30.689 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  19. 17:02:30.689 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  20. 17:02:30.690 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  21. 17:02:30.710 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  22. 17:02:30.712 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  23. 17:02:30.713 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  24. 17:02:30.713 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  25. 17:02:30.714 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  26. 17:02:30.715 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  27. 17:02:30.715 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  28. 17:02:30.715 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  29. 17:02:30.720 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  30. 17:02:30.725 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  31. 17:02:30.725 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  32. 17:02:30.726 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  33. 17:02:30.726 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  34. 17:02:30.726 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  35. 17:02:30.733 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  36. 17:02:30.734 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  37. 17:02:30.734 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  38. 17:02:30.735 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  39. 17:02:30.735 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  40. 17:02:30.735 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  41. 17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  42. 17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  43. 17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  44. 17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  45. 17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  46. 17:02:30.737 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  47. 17:02:30.738 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  48. 17:02:30.738 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  49. 17:02:30.738 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  50. 17:02:30.747 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  51. 17:02:30.753 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  52. 17:02:30.753 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  53. 17:02:30.755 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  54. 17:02:30.755 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  55. 17:02:30.755 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  56. 17:02:30.758 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  57. 17:02:30.760 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  58. 17:02:30.761 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  59. 17:02:30.764 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  60. 17:02:30.764 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  61. 17:02:30.764 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  62. 17:02:30.767 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  63. 17:02:30.767 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  64. 17:02:30.767 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  65. 17:02:30.770 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  66. 17:02:30.771 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  67. 17:02:30.771 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  68. 17:02:30.773 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  69. 17:02:30.776 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  70. 17:02:30.777 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  71. 17:02:30.779 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  72. 17:02:30.779 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  73. 17:02:30.780 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  74. 17:02:30.782 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  75. 17:02:30.784 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 4、完成自动依赖注入。
  76. 17:02:30.785 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
  77. 17:02:30.785 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
  78. 17:02:30.818 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
  79. 执行自定义Service方法。
  80. Process finished with exit code 0

 

 


Pt2 手写MVC

Pt2.1 流程设计

MVC概念中,M为Model,代表数据;V为View,代表展现层,比如JSP、HTML等;C是控制层,负责整体业务逻辑的处理和调度。

 

在SpringMVC中,Handler是核心逻辑的处理器,即MVC中的C也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法。在Controller层中@RequestMapping标注的所有方法都可以看成是一个Handler,只要可以实际处理请求就可以是Handler。

用户在客户端发起请求,请求URL对应C层的具体Handler(处理器),Handler完成逻辑处理后,输出结果数据(即M层),然后包装成View(V层)返回给客户端完成展现。Spring MVC核心类有以下:

  • DispatcherServlet 请求调度

  • HandlerMapping 请求映射

  • HandlerAdapter 请求方法适配器

  • ModelAndView 页面数据封装

  • ViewResolver 视图解析器

  • View 自定义模板引擎

 


Pt2.2 MVC九大组件

在实现Spring MVC手写源码之前,先来介绍下Spring九大组件,这也是我们在Spring MVC初始化过程中要完成的操作。

【1. HandlerMapping】

HandlerMapping是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler(即Controller)处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事。

 

【2. HandlerAdapter】

从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。

小结:Handler(即Controller)是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。

 

【3. HandlerExceptionResolver】

其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。

 

【4. ViewResolver】

ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。

 

【5. RequestToViewNameTranslator】

ViewName是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。

 

【6. LocaleResolver】

解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。

 

【7. ThemeResolver】

用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。

 

【8. MultipartResolver】

用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。

 

【9. FlashMapManager】

用来管理FlashMap的,FlashMap主要用在redirect中传递参数。

 


Pt2.3 基础配置

在配置文件中,我们配置静态资源的根路径。


  
  1. # 静态资源路径
  2. templateRoot=/webapp/WEB-INF/view

 


Pt2.4 核心代码

DispatcherServlet

在DispatcherServlet中完成了IoC、DI和MVC的初始化动作。


  
  1. package com.demo.spring.simulation.v5.servlet;
  2. import com.demo.spring.simulation.v5.annotation.MyController;
  3. import com.demo.spring.simulation.v5.annotation.MyRequestMapping;
  4. import com.demo.spring.simulation.v5.context.MyApplicationContext;
  5. import lombok.extern.slf4j.Slf4j;
  6. import javax.servlet.ServletConfig;
  7. import javax.servlet.ServletException;
  8. import javax.servlet.http.HttpServlet;
  9. import javax.servlet.http.HttpServletRequest;
  10. import javax.servlet.http.HttpServletResponse;
  11. import java.io.File;
  12. import java.io.IOException;
  13. import java.lang.reflect.Method;
  14. import java.util.*;
  15. import java.util.regex.Matcher;
  16. import java.util.regex.Pattern;
  17. /**
  18. * DispatcherServlet负责请求调度和分发。
  19. */
  20. @Slf4j
  21. public class MyDispatcherServlet extends HttpServlet {
  22. // Spring配置文件路径
  23. private static final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";
  24. // Spring上下文,Spring IoC容器
  25. private MyApplicationContext applicationContext;
  26. // 保存请求URL和处理方法的映射关系
  27. private List<MyHandlerMapping> handlerMappings = new ArrayList<MyHandlerMapping>();
  28. // 保存请求映射和处理Handler的关系
  29. private Map<MyHandlerMapping, MyHandlerAdapter> handlerAdapters = new HashMap<MyHandlerMapping, MyHandlerAdapter>();
  30. // 保存所有View解析器
  31. private List<MyViewResolver> viewResolvers = new ArrayList<MyViewResolver>();
  32. @Override
  33. public void init(ServletConfig config) throws ServletException {
  34. log.info( "DispatcherServlet -> Create Web Server Starting.");
  35. // 1、初始化ApplicationContext。ApplicationContext包含了Spring核心IoC容器,完成Bean扫描、初始化和DI。
  36. log.info( "DispatcherServlet -> Init Spring IoC/DI Starting.");
  37. applicationContext = new MyApplicationContext(config.getInitParameter(CONTEXT_CONFIG_LOCATION));
  38. log.info( "DispatcherServlet -> Init Spring IoC/DI Finished.");
  39. // 2、初始化Spring MVC九大组件
  40. log.info( "DispatcherServlet -> Init Spring MVC Starting.");
  41. initStrategies(applicationContext);
  42. log.info( "DispatcherServlet -> Init Spring MVC Finished.");
  43. log.info( "DispatcherServlet -> Create Web Server Finished.");
  44. }
  45. @Override
  46. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  47. this.doPost(req, resp);
  48. }
  49. @Override
  50. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  51. log.info( "DispatcherServlet -> Receive client request.");
  52. try {
  53. // 3、委派,根据URL去找到一个对应的Method并通过response返回
  54. doDispatch(req, resp);
  55. } catch (Exception e) {
  56. try {
  57. processDispatchResult(req, resp, new MyModelAndView( "500"));
  58. } catch (Exception e1) {
  59. e1.printStackTrace();
  60. resp.getWriter().write( "500 Exception,Detail : " + Arrays.toString(e.getStackTrace()));
  61. }
  62. }
  63. log.info( "DispatcherServlet -> Return client response.");
  64. }
  65. /**
  66. * 完成Spring MVC组件的初始化。
  67. *
  68. * @param context
  69. */
  70. private void initStrategies(MyApplicationContext context) {
  71. // 1、多文件上传的组件 TODO
  72. // initMultipartResolver(context);
  73. // log.info("DispatcherServlet -> 1、多文件上传的组件");
  74. // 2、初始化本地语言环境 TODO
  75. // initLocaleResolver(context);
  76. // log.info("DispatcherServlet -> 2、初始化本地语言环境");
  77. // 3、初始化模板处理器 TODO
  78. // initThemeResolver(context);
  79. // log.info("DispatcherServlet -> 3、初始化模板处理器");
  80. // 4、初始化HandlerMapping,必须实现。
  81. initHandlerMappings(context);
  82. log.info( "DispatcherServlet -> 4、初始化HandlerMapping,必须实现。");
  83. // 5、初始化参数适配器,必须实现。
  84. initHandlerAdapters(context);
  85. log.info( "DispatcherServlet -> 5、初始化参数适配器,必须实现。");
  86. // 6、初始化异常拦截器 TODO
  87. // initHandlerExceptionResolvers(context);
  88. // log.info("DispatcherServlet -> 6、初始化异常拦截器");
  89. // 7、初始化视图预处理器 TODO
  90. // initRequestToViewNameTranslator(context);
  91. // log.info("DispatcherServlet -> 7、初始化视图预处理器");
  92. // 8、初始化视图转换器,必须实现。
  93. initViewResolvers(context);
  94. log.info( "DispatcherServlet -> 8、初始化视图转换器,必须实现。");
  95. // 9、初始化FlashMap管理器 TODO
  96. // initFlashMapManager(context);
  97. // log.info("DispatcherServlet -> 9、初始化FlashMap管理器");
  98. }
  99. /**
  100. * HandlerMapping:保存请求URL和处理方法的映射关系。
  101. *
  102. * @param context
  103. */
  104. private void initHandlerMappings(MyApplicationContext context) {
  105. log.info( "DispatcherServlet -> 解析和缓存HandlerMapping");
  106. if ( this.applicationContext.getBeanDefinitionCount() == 0) {
  107. return;
  108. }
  109. for (String beanName : this.applicationContext.getBeanDefinitionNames()) {
  110. Object instance = applicationContext.getBean(beanName);
  111. Class<?> clazz = instance.getClass();
  112. // 1、Controller注解的类才具备URL配置
  113. if (!clazz.isAnnotationPresent(MyController.class)) {
  114. continue;
  115. }
  116. // 2、提取 class上配置的base_url
  117. String baseUrl = "";
  118. if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
  119. baseUrl = clazz.getAnnotation(MyRequestMapping.class).value();
  120. }
  121. // 3、获取 public的方法
  122. for (Method method : clazz.getMethods()) {
  123. if (!method.isAnnotationPresent(MyRequestMapping.class)) {
  124. continue;
  125. }
  126. // 4、提取每个方法上面配置的url
  127. MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
  128. // 5、拼接URL
  129. String regex = ( "/" + baseUrl + "/" + requestMapping.value().replaceAll( "\\*", ".*")).replaceAll( "/+", "/");
  130. Pattern pattern = Pattern.compile(regex);
  131. // 6、保存HandlerMapping映射关系
  132. handlerMappings.add( new MyHandlerMapping(pattern, instance, method));
  133. }
  134. }
  135. }
  136. /**
  137. * 初始化参数适配器。
  138. *
  139. * @param context
  140. */
  141. private void initHandlerAdapters(MyApplicationContext context) {
  142. log.info( "DispatcherServlet -> 创建HandlerAdapter处理类。");
  143. // HandlerAdapter调用具体的方法对用户发来的请求来进行处理,所以每个HandlerMapping都对应一个HandlerAdapter。
  144. for (MyHandlerMapping handlerMapping : handlerMappings) {
  145. this.handlerAdapters.put(handlerMapping, new MyHandlerAdapter());
  146. }
  147. }
  148. /**
  149. * 根据请求URL找对对应处理Handler完成请求,并返回Response。
  150. *
  151. * @param req
  152. * @param resp
  153. * @throws Exception
  154. */
  155. private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
  156. log.info( "DispatcherServlet -> 请求分发");
  157. // 1、通过从Request获得请求URL,去匹配一个HandlerMapping
  158. MyHandlerMapping handler = getHandler(req);
  159. if (handler == null) {
  160. processDispatchResult(req, resp, new MyModelAndView( "404"));
  161. return;
  162. }
  163. // 2、根据一个HandlerMaping获得一个HandlerAdapter
  164. MyHandlerAdapter ha = getHandlerAdapter(handler);
  165. // 3、解析某一个方法的形参和返回值之后,统一封装为ModelAndView对象
  166. MyModelAndView mv = ha.handler(req, resp, handler);
  167. // 4、把ModelAndView变成一个ViewResolver
  168. processDispatchResult(req, resp, mv);
  169. }
  170. /**
  171. * 匹配到一个Handler处理器。
  172. *
  173. * @param handlerMapping
  174. * @return
  175. */
  176. private MyHandlerAdapter getHandlerAdapter(MyHandlerMapping handlerMapping) {
  177. log.info( "DispatcherServlet -> 获取请求对应的处理类。");
  178. if ( this.handlerAdapters.isEmpty()) {
  179. return null;
  180. }
  181. return this.handlerAdapters.get(handlerMapping);
  182. }
  183. /**
  184. * 封装请求结果,输出到浏览器。
  185. *
  186. * @param req
  187. * @param resp
  188. * @param mv
  189. * @throws Exception
  190. */
  191. private void processDispatchResult(HttpServletRequest req, HttpServletResponse resp, MyModelAndView mv) throws Exception {
  192. log.info( "DispatcherServlet -> 封装请求结果并输出");
  193. if ( null == mv) {
  194. return;
  195. }
  196. if ( this.viewResolvers.isEmpty()) {
  197. return;
  198. }
  199. for (MyViewResolver viewResolver : this.viewResolvers) {
  200. MyView view = viewResolver.resolveViewName(mv.getViewName());
  201. //直接往浏览器输出
  202. view.render(mv.getModel(), req, resp);
  203. return;
  204. }
  205. }
  206. /**
  207. * 从Request中获取URL,然后匹配对应的HandlerMapping。
  208. *
  209. * @param req
  210. * @return
  211. */
  212. private MyHandlerMapping getHandler(HttpServletRequest req) {
  213. log.info( "DispatcherServlet -> 根据Request Url获取HandlerMapping");
  214. if ( this.handlerMappings.isEmpty()) {
  215. return null;
  216. }
  217. // 从Request中获取请求URL
  218. String url = req.getRequestURI();
  219. String contextPath = req.getContextPath();
  220. url = url.replaceAll(contextPath, "").replaceAll( "/+", "/");
  221. // 匹配HandlerMapping
  222. for (MyHandlerMapping mapping : handlerMappings) {
  223. Matcher matcher = mapping.getPattern().matcher(url);
  224. if (!matcher.matches()) {
  225. continue;
  226. }
  227. return mapping;
  228. }
  229. return null;
  230. }
  231. /**
  232. * 初始化视图解析器,根据配置的根路径,遍历解析所有的View。
  233. *
  234. * @param context
  235. */
  236. private void initViewResolvers(MyApplicationContext context) {
  237. log.info( "DispatcherServlet -> 初始化视图解析器");
  238. // 从配置中获取模板文件存放路径
  239. String templateRoot = context.getConfig().getProperty( "templateRoot");
  240. // String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();
  241. String templateRootPath = this.getClass().getClassLoader().getResource( "/").getPath().replaceAll( "/target/classes", "");
  242. templateRootPath = templateRootPath + "/src/main" + templateRoot;
  243. templateRootPath = templateRootPath.replaceAll( "/+", "/");
  244. File templateRootDir = new File(templateRootPath);
  245. for (File file : templateRootDir.listFiles()) {
  246. this.viewResolvers.add( new MyViewResolver(templateRootDir.getPath()));
  247. }
  248. }
  249. }

 


HandlerMapping

HandlerMapping保存了请求URL和处理器Handler之前的映射关系。


  
  1. package com.demo.spring.simulation.v5.servlet;
  2. import lombok.Data;
  3. import java.lang.reflect.Method;
  4. import java.util.regex.Pattern;
  5. /**
  6. * HandlerMapping映射注册、根据url获取对应的处理器、拦截器注册。
  7. */
  8. @Data
  9. public class MyHandlerMapping {
  10. // 请求URL的正则匹配
  11. private Pattern pattern;
  12. // URL对应的Method
  13. private Method method;
  14. // Method对应的实例对象
  15. private Object controller;
  16. public MyHandlerMapping(Pattern pattern, Object controller, Method method) {
  17. this.pattern = pattern;
  18. this.method = method;
  19. this.controller = controller;
  20. }
  21. }

 


HandlerAdapter

HandlerAdapter将Servlet的request-response模式进行封装,执行对应的处理器完成响应。


  
  1. package com.demo.spring.simulation.v5.servlet;
  2. import com.demo.spring.simulation.v5.annotation.MyRequestParam;
  3. import lombok.extern.slf4j.Slf4j;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.lang.annotation.Annotation;
  7. import java.util.Arrays;
  8. import java.util.HashMap;
  9. import java.util.Map;
  10. /**
  11. * HandlerAdapter调用具体的方法对用户发来的请求来进行处理。
  12. */
  13. @Slf4j
  14. public class MyHandlerAdapter {
  15. public boolean supports(Object handler) {
  16. return (handler instanceof MyHandlerMapping);
  17. }
  18. /**
  19. * @param req
  20. * @param resp
  21. * @param handlerMapping
  22. * @return
  23. * @throws Exception
  24. */
  25. public MyModelAndView handler(HttpServletRequest req, HttpServletResponse resp, MyHandlerMapping handlerMapping) throws Exception {
  26. log.info( "MyViewResolver -> Request请求处理核心逻辑");
  27. // 1、将方法的形参列表和Request参数列表进行匹配和对应。
  28. Map<String, Integer> paramIndexMapping = new HashMap<String, Integer>();
  29. // 2、获取请求处理方法的参数注解。
  30. // 提取方法中加入了注解的参数。一个参数可以有多个注解,而一个方法又有多个参数,所以是一个二维数组。
  31. Annotation[][] pa = handlerMapping.getMethod().getParameterAnnotations();
  32. for ( int i = 0; i < pa.length; i++) {
  33. for (Annotation a : pa[i]) {
  34. if (a instanceof MyRequestParam) {
  35. String paramName = ((MyRequestParam) a).value();
  36. if (! "".equals(paramName.trim())) {
  37. paramIndexMapping.put(paramName, i);
  38. }
  39. }
  40. }
  41. }
  42. // 3、提取Request和Response参数。
  43. Class<?>[] paramTypes = handlerMapping.getMethod().getParameterTypes();
  44. for ( int i = 0; i < paramTypes.length; i++) {
  45. Class<?> paramterType = paramTypes[i];
  46. if (paramterType == HttpServletRequest.class || paramterType == HttpServletResponse.class) {
  47. paramIndexMapping.put(paramterType.getName(), i);
  48. }
  49. }
  50. // 4、获取请求方法的形参列表。
  51. // Eg.http://localhost/web/query?name=Tom&Cat=1
  52. Map<String, String[]> params = req.getParameterMap();
  53. // 实参列表
  54. Object[] paramValues = new Object[paramTypes.length];
  55. for (Map.Entry<String, String[]> param : params.entrySet()) {
  56. String value = Arrays.toString(params.get(param.getKey()))
  57. .replaceAll( "\\[|\\]", "")
  58. .replaceAll( "\\s+", ",");
  59. if (!paramIndexMapping.containsKey(param.getKey())) {
  60. continue;
  61. }
  62. int index = paramIndexMapping.get(param.getKey());
  63. //允许自定义的类型转换器Converter
  64. paramValues[index] = castStringValue(value, paramTypes[index]);
  65. }
  66. // 处理Request
  67. if (paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
  68. int index = paramIndexMapping.get(HttpServletRequest.class.getName());
  69. paramValues[index] = req;
  70. }
  71. // 处理 Response
  72. if (paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
  73. int index = paramIndexMapping.get(HttpServletResponse.class.getName());
  74. paramValues[index] = resp;
  75. }
  76. // 5、通过反射执行方法体
  77. Object result = handlerMapping.getMethod().invoke(handlerMapping.getController(), paramValues);
  78. if (result == null || result instanceof Void) {
  79. return null;
  80. }
  81. boolean isModelAndView = handlerMapping.getMethod().getReturnType() == MyModelAndView.class;
  82. if (isModelAndView) {
  83. return (MyModelAndView) result;
  84. }
  85. return null;
  86. }
  87. private Object castStringValue(String value, Class<?> paramType) {
  88. if (String.class == paramType) {
  89. return value;
  90. } else if (Integer.class == paramType) {
  91. return Integer.valueOf(value);
  92. } else if (Double.class == paramType) {
  93. return Double.valueOf(value);
  94. } else {
  95. if (value != null) {
  96. return value;
  97. }
  98. return null;
  99. }
  100. }
  101. }

 


ModelAndView

保存请求响应的view和model信息,以便后续完成解析和渲染。


  
  1. package com.demo.spring.simulation.v5.servlet;
  2. import java.util.Map;
  3. /**
  4. * ModelAndView类用来存储处理完后的结果数据,以及显示该数据的视图。
  5. */
  6. public class MyModelAndView {
  7. // 该属性用来存储返回的视图信息
  8. private String viewName;
  9. // Model代表模型数据
  10. private Map<String,?> model;
  11. public MyModelAndView(String viewName, Map<String, ?> model) {
  12. this.viewName = viewName;
  13. this.model = model;
  14. }
  15. public MyModelAndView(String viewName) {
  16. this.viewName = viewName;
  17. }
  18. public String getViewName() {
  19. return viewName;
  20. }
  21. public Map<String, ?> getModel() {
  22. return model;
  23. }
  24. }

 


ViewResolver

ViewResolver用来将String类型的视图名解析为View类型的视图。ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。


  
  1. package com.demo.spring.simulation.v5.servlet;
  2. import lombok.extern.slf4j.Slf4j;
  3. import java.io.File;
  4. /**
  5. * 视图解析器的作用是将逻辑视图转为物理视图,所有的视图解析器都必须实现ViewResolver接口。
  6. */
  7. @Slf4j
  8. public class MyViewResolver {
  9. // 视图默认后缀名
  10. private final String DEFAULT_TEMPLATE_SUFFIX = ".html";
  11. // 视图文件根路径
  12. private File templateRootDir;
  13. public MyViewResolver(String templateRoot) {
  14. // String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();
  15. templateRootDir = new File(templateRoot);
  16. }
  17. /**
  18. * 根据视图名称获取视图定义信息
  19. *
  20. * @param viewName
  21. * @return
  22. */
  23. public MyView resolveViewName(String viewName) {
  24. log.info( "MyViewResolver -> 视图解析");
  25. if ( null == viewName || "".equals(viewName.trim())) {
  26. return null;
  27. }
  28. viewName = viewName.endsWith(DEFAULT_TEMPLATE_SUFFIX) ? viewName : (viewName + DEFAULT_TEMPLATE_SUFFIX);
  29. File templateFile = new File((templateRootDir.getPath() + "/" + viewName).replaceAll( "/+", "/"));
  30. return new MyView(templateFile);
  31. }
  32. }

 


View

View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。


  
  1. package com.demo.spring.simulation.v5.servlet;
  2. import lombok.extern.slf4j.Slf4j;
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletResponse;
  5. import java.io.File;
  6. import java.io.RandomAccessFile;
  7. import java.util.Map;
  8. import java.util.regex.Matcher;
  9. import java.util.regex.Pattern;
  10. /**
  11. * 存储视图对象数据
  12. */
  13. @Slf4j
  14. public class MyView {
  15. // 视图文件
  16. private File viewFile;
  17. public MyView(File templateFile) {
  18. this.viewFile = templateFile;
  19. }
  20. public void render(Map<String, ?> model, HttpServletRequest req, HttpServletResponse resp) throws Exception {
  21. log.info( "MyViewResolver -> 解析视图文件");
  22. StringBuffer sb = new StringBuffer();
  23. RandomAccessFile ra = new RandomAccessFile( this.viewFile, "r");
  24. String line = null;
  25. while ( null != (line = ra.readLine())) {
  26. line = new String(line.getBytes( "ISO-8859-1"), "utf-8");
  27. Pattern pattern = Pattern.compile( "¥\\{[^\\}]+\\}", Pattern.CASE_INSENSITIVE);
  28. Matcher matcher = pattern.matcher(line);
  29. while (matcher.find()) {
  30. String paramName = matcher.group();
  31. paramName = paramName.replaceAll( "¥\\{|\\}", "");
  32. Object paramValue = model.get(paramName);
  33. line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString()));
  34. matcher = pattern.matcher(line);
  35. }
  36. sb.append(line);
  37. }
  38. resp.setCharacterEncoding( "utf-8");
  39. resp.getWriter().write(sb.toString());
  40. }
  41. // 处理特殊字符
  42. public static String makeStringForRegExp(String str) {
  43. return str.replace( "\\", "\\\\").replace( "*", "\\*")
  44. .replace( "+", "\\+").replace( "|", "\\|")
  45. .replace( "{", "\\{").replace( "}", "\\}")
  46. .replace( "(", "\\(").replace( ")", "\\)")
  47. .replace( "^", "\\^").replace( "$", "\\$")
  48. .replace( "[", "\\[").replace( "]", "\\]")
  49. .replace( "?", "\\?").replace( ",", "\\,")
  50. .replace( ".", "\\.").replace( "&", "\\&");
  51. }
  52. }

 


Pt2.5 功能验证

  • 先编写测试代码,定义请求处理的Controller。


  
  1. package com.demo.spring.simulation.v5.test.mvc;
  2. import com.demo.spring.simulation.v5.annotation.MyAutowired;
  3. import com.demo.spring.simulation.v5.annotation.MyController;
  4. import com.demo.spring.simulation.v5.annotation.MyRequestMapping;
  5. import com.demo.spring.simulation.v5.annotation.MyRequestParam;
  6. import com.demo.spring.simulation.v5.servlet.MyModelAndView;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. import java.util.HashMap;
  10. import java.util.Map;
  11. @MyController
  12. @MyRequestMapping("/mvc/")
  13. public class MvcController {
  14. @MyRequestMapping("/hello")
  15. public MyModelAndView hello(HttpServletRequest request, HttpServletResponse response, @MyRequestParam(value = "id") String id, @MyRequestParam(value = "name") String name) {
  16. Map<String, Object> model = new HashMap<String, Object>();
  17. model.put( "message", "hello " + id + " " + name);
  18. return new MyModelAndView( "hello", model);
  19. }
  20. }

 

  • 定义View。


  
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title </title>
  6. </head>
  7. <body>
  8. <div>¥{message} </div>
  9. </body>
  10. </html>
  • 启动Web容器

我们需要已WEB服务器的模式启动MVC容器,在IDEA中配置Jetty启动。

 

启动容器,启动过程的日志比较多,就不展示了。我们请求刚才的Controller地址,看看能不能访问。

输出结果没有问题,说明整个MVC流程解析是正确的。还是推荐使用DEBUG模式研究整个MVC的初始化过程,加深印象。

 

 


Pt3 手写AOP

Pt3.1 流程设计

Spring AOP的原理是生成目标Bean的代理类,从而可以在目标方法前、后、异常时或者环绕执行AOP的切面逻辑。所以AOP的核心,一是生成目标类的代理类并注入切面逻辑,二是在IoC容器中用AOP代理类代替原始Bean,从而实现AOP的功能。

AOP主要涉及以下核心类:

  • AopProxy 代理顶层接口定义

  • JdkDynamicAopProxy 基于JDK动态代理实现

  • AdvisedSupport 配置解析

  • Advice 通知接口定义

  • AopConfig 封装配置

  • ApplicationContext 注册到IoC容器

 


Pt3.2 基础配置

application.properties

要使用AOP,通常要在Spring的XML配置AOP相关信息,比如切面、切点、通知逻辑等,为了简化实现逻辑我们将配置放到properties中便于读取方便。


  
  1. # AOP配置
  2. #切面表达式expression#
  3. pointCut=public .* com.demo.spring.simulation.v5.test.aop.AspectServiceImpl.print(.*)
  4. #切面类
  5. aspectClass=com.demo.spring.simulation.v5.test.aop.LogAspect
  6. #前置通知回调方法
  7. aspectBefore=before
  8. #后置通知回调方法
  9. aspectAfter=after
  10. #异常通知回调方法
  11. aspectAfterThrow=afterThrowing
  12. #异常类型捕获
  13. aspectAfterThrowingName=java.lang.Exception

 


Pt3.3 核心代码

AopProxy

定义用于实现动态代理的接口类。


  
  1. package com.demo.spring.simulation.v5.aop;
  2. public interface MyAopProxy {
  3. public void print();
  4. }

 

JdkDynamicAopProxy

基于JDK 动态代理实现的代理类,关于动态代理这里不深入,重点是两个方法:getProxy()用于生成代理类,invoke()用于完成代理逻辑的执行。


  
  1. package com.demo.spring.simulation.v5.aop;
  2. import com.demo.spring.simulation.v5.aop.aspect.MyAdvice;
  3. import com.demo.spring.simulation.v5.aop.support.MyAdvisedSupport;
  4. import java.lang.reflect.InvocationHandler;
  5. import java.lang.reflect.InvocationTargetException;
  6. import java.lang.reflect.Method;
  7. import java.lang.reflect.Proxy;
  8. import java.util.Map;
  9. /**
  10. * 基于JDK动态代理的AOP实现
  11. */
  12. public class MyJdkDynamicAopProxy implements InvocationHandler {
  13. // AOP核心支撑类,保存了被代理类Class和实例化对象、AOP定义等。
  14. private MyAdvisedSupport advisedSupport;
  15. public MyJdkDynamicAopProxy(MyAdvisedSupport advisedSupport) {
  16. this.advisedSupport = advisedSupport;
  17. }
  18. /**
  19. * 通过动态代理,生成目标类的代理类。
  20. *
  21. * @return
  22. */
  23. public Object getProxy() {
  24. return Proxy
  25. .newProxyInstance( this.getClass().getClassLoader(), this.advisedSupport.getTargetClass().getInterfaces(),
  26. this);
  27. }
  28. /**
  29. * 执行具体代理方法。
  30. *
  31. * @param proxy
  32. * @param method
  33. * @param args
  34. * @return
  35. * @throws Throwable
  36. */
  37. @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  38. // 1、获取方法对应的AOP定义。
  39. Map<String, MyAdvice> advices = this.advisedSupport
  40. .getInterceptorsAndDynamicInterceptionAdvice(method, this.advisedSupport.getTargetClass());
  41. // 2、执行AOP的before方法
  42. invokeAdvice(advices.get( "before"));
  43. // 3、执行被代理类切点方法
  44. Object returnValue = null;
  45. try {
  46. returnValue = method.invoke( this.advisedSupport.getTarget(), args);
  47. } catch (Exception ex) {
  48. // 4、发生异常时,执行AOP的Exception方法
  49. invokeAdvice(advices.get( "afterThrow"));
  50. throw ex;
  51. }
  52. // 5、调用AOP的after方法
  53. invokeAdvice(advices.get( "after"));
  54. return returnValue;
  55. }
  56. /**
  57. * 通过反射完成方法调用。
  58. *
  59. * @param advice
  60. */
  61. private void invokeAdvice(MyAdvice advice) {
  62. try {
  63. advice.getAdviceMethod().invoke(advice.getAspect());
  64. } catch (IllegalAccessException e) {
  65. e.printStackTrace();
  66. } catch (InvocationTargetException e) {
  67. e.printStackTrace();
  68. }
  69. }
  70. }

 


AdvisedSupport

AdvisedSupport是AOP核心类,负责解析AOP的配置(切点、切面、通知方法等),并完成对应关系的缓存。


  
  1. package com.demo.spring.simulation.v5.aop.support;
  2. import com.demo.spring.simulation.v5.aop.aspect.MyAdvice;
  3. import com.demo.spring.simulation.v5.aop.config.MyAopConfig;
  4. import java.lang.reflect.InvocationHandler;
  5. import java.lang.reflect.Method;
  6. import java.util.HashMap;
  7. import java.util.Map;
  8. import java.util.regex.Matcher;
  9. import java.util.regex.Pattern;
  10. /**
  11. * AOP配置解析。
  12. * 1、解析PointCut正则匹配;
  13. * 2、解析切面Aspect方法逻辑;
  14. * 3、解析被代理类切入方法;
  15. * 4、建立被代理类切入方法和切面方法的关系;
  16. */
  17. public class MyAdvisedSupport {
  18. // 代理的目标类
  19. private Class<?> targetClass;
  20. // 代理的目标类的实例对象
  21. private Object target;
  22. // AOP配置信息
  23. private MyAopConfig config;
  24. // AOP切点匹配规则
  25. private Pattern pointCutClassPattern;
  26. // 享元的共享池,用于保存被代理类方法和通知方法对应关系
  27. private transient Map<Method, Map<String, MyAdvice>> methodCache;
  28. public MyAdvisedSupport(MyAopConfig config) {
  29. this.config = config;
  30. }
  31. public Class<?> getTargetClass() {
  32. return this.targetClass;
  33. }
  34. public Object getTarget() {
  35. return this.target;
  36. }
  37. /**
  38. * 获取方法对应的AOP信息。
  39. *
  40. * @param method
  41. * @param targetClass
  42. * @return
  43. * @throws NoSuchMethodException
  44. */
  45. public Map<String, MyAdvice> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass)
  46. throws NoSuchMethodException {
  47. // 获取AOP方法
  48. Map<String, MyAdvice> cache = methodCache.get(method);
  49. if ( null == cache) {
  50. // 目标对象方法
  51. Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());
  52. cache = methodCache.get(m);
  53. // 对代理方法进行兼容处理 TODO 这里没太看懂
  54. this.methodCache.put(m, cache);
  55. }
  56. return cache;
  57. }
  58. /**
  59. * 设置被代理类的Class类型。
  60. *
  61. * @param targetClass
  62. */
  63. public void setTargetClass(Class<?> targetClass) {
  64. this.targetClass = targetClass;
  65. // 解析AOP配置,设置AOP匹配逻辑。
  66. this.parse();
  67. }
  68. //解析配置文件的方法
  69. private void parse() {
  70. // 1、把PointCut的Spring Excpress转换成Java正则表达式
  71. String pointCut =
  72. config.getPointCut().replaceAll( "\\.", "\\\\.").replaceAll( "\\\\.\\*", ".*").replaceAll( "\\(", "\\\\(")
  73. .replaceAll( "\\)", "\\\\)");
  74. // 保存专门匹配Class的正则
  75. String pointCutForClassRegex = pointCut.substring( 0, pointCut.lastIndexOf( "\\("));
  76. pointCutClassPattern =
  77. Pattern.compile( "class " + pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf( " ") + 1, pointCutForClassRegex.lastIndexOf( "\\.")));
  78. // 保存专门匹配方法的正则
  79. Pattern pointCutPattern = Pattern.compile(pointCut);
  80. // 2、享元的共享池,用于保存被代理类方法和通知方法对应关系。
  81. methodCache = new HashMap<Method, Map<String, MyAdvice>>();
  82. try {
  83. // 3、获取切面中定义的所有Method。
  84. Class aspectClass = Class.forName( this.config.getAspectClass());
  85. Map<String, Method> aspectMethods = new HashMap<String, Method>();
  86. for (Method method : aspectClass.getMethods()) {
  87. aspectMethods.put(method.getName(), method);
  88. }
  89. // 4、获取目标代理类的所有方法
  90. for (Method method : this.targetClass.getMethods()) {
  91. // 获取方法名称
  92. String methodString = method.toString();
  93. if (methodString.contains( "throws")) {
  94. methodString = methodString.substring( 0, methodString.lastIndexOf( "throws")).trim();
  95. }
  96. // 5、如果匹配切点规则,进行切面逻辑设定。
  97. Matcher matcher = pointCutPattern.matcher(methodString);
  98. if (matcher.matches()) {
  99. Map<String, MyAdvice> advices = new HashMap<String, MyAdvice>();
  100. if (!( null == config.getAspectBefore() || "".equals(config.getAspectBefore()))) {
  101. advices.put( "before",
  102. new MyAdvice(aspectClass.newInstance(), aspectMethods.get(config.getAspectBefore())));
  103. }
  104. if (!( null == config.getAspectAfter() || "".equals(config.getAspectAfter()))) {
  105. advices.put( "after",
  106. new MyAdvice(aspectClass.newInstance(), aspectMethods.get(config.getAspectAfter())));
  107. }
  108. if (!( null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow()))) {
  109. MyAdvice advice =
  110. new MyAdvice(aspectClass.newInstance(), aspectMethods.get(config.getAspectAfterThrow()));
  111. advice.setThrowName(config.getAspectAfterThrowingName());
  112. advices.put( "afterThrow", advice);
  113. }
  114. // 6、保存目标代理类业务方法和环绕通知类的关系。
  115. methodCache.put(method, advices);
  116. }
  117. }
  118. } catch (Exception e) {
  119. e.printStackTrace();
  120. }
  121. }
  122. //根据一个目标代理类的方法,获得其对应的通知
  123. public Map<String, MyAdvice> getAdvices(Method method, Object o) throws Exception {
  124. //享元设计模式的应用
  125. Map<String, MyAdvice> cache = methodCache.get(method);
  126. if ( null == cache) {
  127. Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());
  128. cache = methodCache.get(m);
  129. this.methodCache.put(m, cache);
  130. }
  131. return cache;
  132. }
  133. /**
  134. * 设置被代理类的实例化对象。
  135. *
  136. * @param target
  137. */
  138. public void setTarget(Object target) {
  139. this.target = target;
  140. }
  141. /**
  142. * 代理的目标类是否匹配切点。
  143. * 在ApplicationContext IoC中的对象初始化时调用,决定要不要生成代理类的逻辑。
  144. *
  145. * @return
  146. */
  147. public boolean pointCutMatch() {
  148. return pointCutClassPattern.matcher( this.targetClass.toString()).matches();
  149. }
  150. }

 


Advice

Advice用于定义通知方法的结构,包含了切面的实例化对象和定义的通知方法。


  
  1. package com.demo.spring.simulation.v5.aop.aspect;
  2. import lombok.Data;
  3. import java.lang.reflect.Method;
  4. /**
  5. * 通知定义接口,用于通知回调。
  6. */
  7. @Data public class MyAdvice {
  8. // 切面实例化对象
  9. private Object aspect;
  10. // AOP方法,非目标方法
  11. private Method adviceMethod;
  12. // 针对异常处理时,异常的名称
  13. private String throwName;
  14. public MyAdvice(Object aspect, Method adviceMethod) {
  15. this.adviceMethod = adviceMethod;
  16. this.aspect = aspect;
  17. }
  18. }

 


AopConfig

主要保存了定义的AOP配置,比较简单。


  
  1. package com.demo.spring.simulation.v5.aop.config;
  2. import lombok.Data;
  3. /**
  4. * 封装AOP的配置信息,包括切点、切面、切入环绕方法。
  5. */
  6. @Data
  7. public class MyAopConfig {
  8. // 切点
  9. private String pointCut;
  10. // 切面
  11. private String aspectClass;
  12. // before回调方法
  13. private String aspectBefore;
  14. // after回调方法
  15. private String aspectAfter;
  16. // 异常回调方法
  17. private String aspectAfterThrow;
  18. // 异常类型捕获
  19. private String aspectAfterThrowingName;
  20. }

 


ApplicationContext

上面的代码中,已经完成了AOP配置的获取,以及通过动态代理完成通知方法的执行。但是,我们需要把这段逻辑和IoC容器关联起来,以便能够在具体业务逻辑执行的时候自动完成AOP的调用。

其实逻辑非常简单,在IoC中我们介绍过,是通过反射将Bean的实例化对象放入IoC容器。但是如果是有AOP逻辑的Bean,只需要将AOP代理类实例化对象代替原始Bean对象,放入IoC容器即可。

 

所以需要再ApplicationContext加入这段逻辑,这部分是在IoC容器处理的时候加入的,我们只展示这段相关逻辑。


  
  1. /**
  2. * 创建真正的实例对象
  3. *
  4. * @param beanName
  5. * @param beanDefinition
  6. * @return
  7. */
  8. private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) {
  9. log.info( "ApplicationContext -> 通过反射创建Bean实例。");
  10. String className = beanDefinition.getBeanClassName();
  11. Object instance = null;
  12. try {
  13. Class<?> clazz = Class.forName(className);
  14. instance = clazz.newInstance();
  15. // AOP部分 -> Start
  16. // 在初始化是确定是返回原生Bean实例还是Bean Proxy实例。
  17. MyAdvisedSupport advisedSupport = instantionAopConfig();
  18. advisedSupport.setTargetClass(clazz);
  19. advisedSupport.setTarget(instance);
  20. // 符合PointCut规则,进行代理
  21. if (advisedSupport.pointCutMatch()) {
  22. instance = new MyJdkDynamicAopProxy(advisedSupport).getProxy();
  23. }
  24. // AOP部分 -> End
  25. // 默认的类名首字母小写
  26. this.factoryBeanObjectCache.put(beanName, instance);
  27. } catch (Exception e) {
  28. e.printStackTrace();
  29. }
  30. return instance;
  31. }
  32. /**
  33. * 获取AOP定义配置信息。
  34. *
  35. * @return
  36. */
  37. private MyAdvisedSupport instantionAopConfig() {
  38. log.info( "ApplicationContext -> 加载AOP配置。");
  39. // 从配置文件中获取AOP配置信息。
  40. MyAopConfig config = new MyAopConfig();
  41. // AOP应该是配置在XML或者Annotation上的,这里为了模拟简单,直接定义在Properties文件中。
  42. config.setPointCut( this.reader.getConfig().getProperty( "pointCut"));
  43. config.setAspectClass( this.reader.getConfig().getProperty( "aspectClass"));
  44. config.setAspectBefore( this.reader.getConfig().getProperty( "aspectBefore"));
  45. config.setAspectAfter( this.reader.getConfig().getProperty( "aspectAfter"));
  46. config.setAspectAfterThrow( this.reader.getConfig().getProperty( "aspectAfterThrow"));
  47. config.setAspectAfterThrowingName( this.reader.getConfig().getProperty( "aspectAfterThrowingName"));
  48. return new MyAdvisedSupport(config);
  49. }

 


Pt3.4 功能验证

  • 定义AOP切面


  
  1. package com.demo.spring.simulation.v5.test.aop;
  2. /**
  3. * 定义切面
  4. */
  5. public class LogAspect {
  6. public void before() {
  7. System.out.println( "Invoke Before Method.");
  8. }
  9. public void after() {
  10. System.out.println( "Invoke After Method.");
  11. }
  12. public void afterThrowing() {
  13. System.out.println( "Invoke Exception Handler.");
  14. }
  15. }

 

  • 定义切入业务方法


  
  1. package com.demo.spring.simulation.v5.test.aop;
  2. import com.demo.spring.simulation.v5.annotation.MyService;
  3. import com.demo.spring.simulation.v5.aop.MyAopProxy;
  4. import com.demo.spring.simulation.v5.servlet.MyModelAndView;
  5. import java.util.HashMap;
  6. import java.util.Map;
  7. /**
  8. * 业务类,执行AOP操作
  9. */
  10. @MyService("aspectService")
  11. public class AspectServiceImpl implements MyAopProxy {
  12. @Override
  13. public void print() {
  14. System.out.println( "Invoke Business Method.");
  15. Map<String, Object> model = new HashMap<String, Object>();
  16. model.put( "message", "hello ");
  17. new MyModelAndView( "hello", model);
  18. }
  19. }

 

  • 定义测试类


  
  1. package com.demo.spring.simulation.v5.test.aop;
  2. import com.demo.spring.simulation.v5.aop.MyAopProxy;
  3. import com.demo.spring.simulation.v5.context.MyApplicationContext;
  4. /**
  5. * Spring MVC测试。
  6. */
  7. public class MyAspectTest {
  8. public static void main(String[] args) {
  9. MyApplicationContext applicationContext = new MyApplicationContext( "classpath:application.properties");
  10. MyAopProxy aspectService = (MyAopProxy)applicationContext.getBean(AspectServiceImpl.class);
  11. aspectService.print();
  12. }
  13. }

 

  • 运行测试类,查看输出结果


  
  1. Invoke Before Method.
  2. Invoke Business Method.
  3. Invoke After Method.

 


参考学习资料和相关文章列表,请参照如下链接:

https://blog.csdn.net/moonlight821/article/details/116463513


转载:https://blog.csdn.net/cmm13655612162/article/details/116504785
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场