目录
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进行控制的。 过程大体分为以下几个步骤:
-
加载/解析Spring配置文件,扫描Bean;
-
将读取的Bean定义封装成BeanDefinition;
-
将Bean注册到IoC容器(未实例化);
-
完成依赖注入(自动);
Pt1.2 基础配置
application.properties
Spring IoC的自动扫描需要配置scanPackage,即扫描根路径,Spring会扫描根路径下所有Bean定义。当然在Spring中,该配置是在Spring的xml中配置的,这里我们直接以properties文件的形式进行配置,简化代码读取的逻辑,本质上是一样的。
-
# 配置类扫描包路径
-
scanPackage=com.demo.spring.simulation.v5.test
pom.xml
从依赖上可以看出,核心只有Servlet,引入日志、Lombok和Junit是用来简化开发和输出一些验证信息。没有任何的Spring组件依赖,我们要自己手写Spring的核心流程,实现上还是尽量干净一些。
-
<?xml version="1.0" encoding="UTF-8"?>
-
-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-
xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-
<modelVersion>4.0.0
</modelVersion>
-
-
<groupId>org.spring5
</groupId>
-
<artifactId>myspring5-action
</artifactId>
-
<version>1.0-SNAPSHOT
</version>
-
<packaging>war
</packaging>
-
-
<name>myspring5-action Maven Webapp
</name>
-
<!-- FIXME change it to the project's website -->
-
<url>http://www.example.com
</url>
-
-
<properties>
-
<project.build.sourceEncoding>UTF-8
</project.build.sourceEncoding>
-
<maven.compiler.source>1.8
</maven.compiler.source>
-
<maven.compiler.target>1.8
</maven.compiler.target>
-
</properties>
-
-
<dependencies>
-
<dependency>
-
<groupId>javax.servlet
</groupId>
-
<artifactId>javax.servlet-api
</artifactId>
-
<version>3.1.0
</version>
-
</dependency>
-
-
<dependency>
-
<groupId>org.projectlombok
</groupId>
-
<artifactId>lombok
</artifactId>
-
<version>1.18.12
</version>
-
</dependency>
-
-
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
-
<dependency>
-
<groupId>org.slf4j
</groupId>
-
<artifactId>slf4j-api
</artifactId>
-
<version>1.7.25
</version>
-
</dependency>
-
-
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
-
<dependency>
-
<groupId>ch.qos.logback
</groupId>
-
<artifactId>logback-classic
</artifactId>
-
<version>1.2.3
</version>
-
</dependency>
-
-
<dependency>
-
<groupId>org.junit.jupiter
</groupId>
-
<artifactId>junit-jupiter
</artifactId>
-
<version>RELEASE
</version>
-
<scope>test
</scope>
-
</dependency>
-
</dependencies>
-
-
<build>
-
<finalName>myspring5-action
</finalName>
-
<pluginManagement>
<!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
-
<plugins>
-
<plugin>
-
<artifactId>maven-clean-plugin
</artifactId>
-
<version>3.1.0
</version>
-
</plugin>
-
<plugin>
-
<artifactId>maven-resources-plugin
</artifactId>
-
<version>3.0.2
</version>
-
</plugin>
-
<plugin>
-
<artifactId>maven-compiler-plugin
</artifactId>
-
<version>3.8.0
</version>
-
</plugin>
-
<plugin>
-
<artifactId>maven-surefire-plugin
</artifactId>
-
<version>2.22.1
</version>
-
</plugin>
-
<plugin>
-
<artifactId>maven-war-plugin
</artifactId>
-
<version>3.2.2
</version>
-
</plugin>
-
<plugin>
-
<artifactId>maven-install-plugin
</artifactId>
-
<version>2.5.2
</version>
-
</plugin>
-
<plugin>
-
<artifactId>maven-deploy-plugin
</artifactId>
-
<version>2.8.2
</version>
-
</plugin>
-
</plugins>
-
</pluginManagement>
-
</build>
-
</project>
web.xml
配置非常简单,定义了Web容器启动Servlet(MyDispatcherServlet)和配置路径。
-
<?xml version="1.0" encoding="UTF-8"?>
-
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
-
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
-
xsi:schemaLocation=
"http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
-
version=
"3.1">
-
-
<display-name>myspring5-action
</display-name>
-
<welcome-file-list>
-
<welcome-file>index.jsp
</welcome-file>
-
</welcome-file-list>
-
-
<servlet>
-
<servlet-name>springMVC
</servlet-name>
-
<servlet-class>com.demo.spring.simulation.v5.servlet.MyDispatcherServlet
</servlet-class>
-
<init-param>
-
<param-name>contextConfigLocation
</param-name>
-
<param-value>classpath:application.properties
</param-value>
-
</init-param>
-
<load-on-startup>1
</load-on-startup>
-
<async-supported>true
</async-supported>
-
</servlet>
-
<servlet-mapping>
-
<servlet-name>springMVC
</servlet-name>
-
<url-pattern>/*
</url-pattern>
-
</servlet-mapping>
-
</web-app>
Pt1.3 注解定义
Spring注解比较多,根据模拟过程中的需要选择性的实现部分。
@MyController
-
package com.demo.spring.simulation.v5.annotation;
-
-
import java.lang.annotation.*;
-
-
/**
-
* 自定义Controller注解
-
*/
-
@Target({ElementType.TYPE})
-
@Retention(RetentionPolicy.RUNTIME)
-
@Documented
-
public
@interface MyController {
-
String value() default "";
-
}
@MyService
-
package com.demo.spring.simulation.v5.annotation;
-
-
import java.lang.annotation.*;
-
-
/**
-
* 自定义Service注解
-
*/
-
@Target({ElementType.TYPE})
-
@Retention(RetentionPolicy.RUNTIME)
-
@Documented
-
public
@interface MyService {
-
String value() default "";
-
}
@MyAutowired
-
package com.demo.spring.simulation.v5.annotation;
-
-
import java.lang.annotation.*;
-
-
/**
-
* 自定义Autowired注解
-
*/
-
@Target({ElementType.FIELD})
-
@Retention(RetentionPolicy.RUNTIME)
-
@Documented
-
public
@interface MyAutowired {
-
String value() default "";
-
}
@MyRequestMapping
-
package com.demo.spring.simulation.v5.annotation;
-
-
import java.lang.annotation.*;
-
-
/**
-
* 自定义RequestMapping注解
-
*/
-
@Target({ElementType.TYPE, ElementType.METHOD})
-
@Retention(RetentionPolicy.RUNTIME)
-
@Documented
-
public
@interface MyRequestMapping {
-
String value() default "";
-
}
@MyRequestParam
-
package com.demo.spring.simulation.v5.annotation;
-
-
import java.lang.annotation.*;
-
-
/**
-
* 自定义RequestParam注解
-
*/
-
@Target({ElementType.PARAMETER})
-
@Retention(RetentionPolicy.RUNTIME)
-
@Documented
-
public
@interface MyRequestParam {
-
String value() default "";
-
}
Pt1.4 核心代码
DispatcherServlet
DispatcherServlet作为启动的整个入口,init()负责初始化IoC、DI、MVC和AOP的环境。这里先介绍IoC和DI的加载过程,从代码可以看出,初始化过程是在ApplicationContext中完成的。
DispatherServlet#init是入口,我们从这里开始看整个流程的处理。
-
/**
-
* DispatcherServlet负责请求调度和分发。
-
*/
-
public
class MyDispatcherServlet extends HttpServlet {
-
-
// Spring配置文件路径
-
private
static
final String CONTEXT_CONFIG_LOCATION =
"contextConfigLocation";
-
-
// Spring上下文,Spring IoC容器
-
private MyApplicationContext applicationContext;
-
-
@Override
-
public void init(ServletConfig config) throws ServletException {
-
log.info(
"DispatcherServlet -> Create Web Server Starting.");
-
-
// 1、初始化ApplicationContext。ApplicationContext包含了Spring核心IoC容器,完成Bean扫描、初始化和DI。
-
log.info(
"DispatcherServlet -> Init Spring IoC/DI Starting.");
-
applicationContext =
new MyApplicationContext(config.getInitParameter(CONTEXT_CONFIG_LOCATION));
-
log.info(
"DispatcherServlet -> Init Spring IoC/DI Finished.");
-
-
// TODO
-
-
log.info(
"DispatcherServlet -> Create Web Server Finished.");
-
}
-
-
@Override
-
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-
this.doPost(req, resp);
-
}
-
-
@Override
-
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-
// TODO
-
}
-
}
ApplicationContext
DispatcherServlet#init直接调用ApplicationContext的构造器执行IoC初始化,根据ApplicationContext构造器的逻辑一步一步来看代码逻辑。
-
package com.demo.spring.simulation.v5.context;
-
-
-
import com.demo.spring.simulation.v5.annotation.MyAutowired;
-
import com.demo.spring.simulation.v5.annotation.MyController;
-
import com.demo.spring.simulation.v5.annotation.MyService;
-
import com.demo.spring.simulation.v5.aop.MyJdkDynamicAopProxy;
-
import com.demo.spring.simulation.v5.aop.config.MyAopConfig;
-
import com.demo.spring.simulation.v5.aop.support.MyAdvisedSupport;
-
import com.demo.spring.simulation.v5.beans.MyBeanWrapper;
-
import com.demo.spring.simulation.v5.beans.config.MyBeanDefinition;
-
import com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader;
-
import lombok.extern.slf4j.Slf4j;
-
-
import java.lang.reflect.Field;
-
import java.util.HashMap;
-
import java.util.List;
-
import java.util.Map;
-
import java.util.Properties;
-
import java.util.concurrent.ConcurrentHashMap;
-
-
/**
-
* 完成Bean的扫描、创建和DI。
-
*/
-
@Slf4j
-
public
class MyApplicationContext {
-
-
// 负责读取Bean配置
-
private MyBeanDefinitionReader reader;
-
-
// 存储注册Bean定义的IoC容器
-
private Map<String, MyBeanDefinition> beanDefinitionMap =
new ConcurrentHashMap<String, MyBeanDefinition>();
-
-
// 存放单例的IoC容器
-
private Map<String, Object> factoryBeanObjectCache =
new HashMap<String, Object>();
-
-
// 通用的IoC容器
-
private Map<String, MyBeanWrapper> factoryBeanInstanceCache =
new HashMap<String, MyBeanWrapper>();
-
-
/**
-
* Spring上下文环境初始化
-
*
-
* @param configLocations 配置文件路径
-
*/
-
public MyApplicationContext(String... configLocations) {
-
-
// 1、加载、解析配置文件,扫描相关的类。
-
reader =
new MyBeanDefinitionReader(configLocations);
-
log.info(
"ApplicationContext -> 1、加载、解析配置文件,扫描相关的类。");
-
-
try {
-
// 2、将扫描的Bean封装成BeanDefinition。
-
List<MyBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
-
log.info(
"ApplicationContext -> 2、将扫描的Bean封装成BeanDefinition。");
-
-
// 3、注册,把BeanDefintion缓存到容器。
-
doRegistBeanDefinition(beanDefinitions);
-
log.info(
"ApplicationContext -> 3、注册,把BeanDefintion缓存到容器。");
-
-
// 4、完成自动依赖注入。
-
doAutowrited();
-
log.info(
"ApplicationContext -> 4、完成自动依赖注入。");
-
}
catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
-
/**
-
* 完成Bean的实例化和自动依赖注入(非延迟加载的场景)。
-
*/
-
private void doAutowrited() {
-
// 到这步,所有的Bean并没有真正的实例化,还只是配置阶段。
-
for (Map.Entry<String, MyBeanDefinition> beanDefinitionEntry :
this.beanDefinitionMap.entrySet()) {
-
String beanName = beanDefinitionEntry.getKey();
-
// getBean才真正完成依赖注入
-
getBean(beanName);
-
}
-
}
-
-
/**
-
* 把BeanDefintion缓存起来
-
*
-
* @param beanDefinitions 通过扫描配置文件获取的Bean定义
-
* @throws Exception
-
*/
-
private void doRegistBeanDefinition(List<MyBeanDefinition> beanDefinitions) throws Exception {
-
log.info(
"ApplicationContext -> 缓存BeanDefinition信息。");
-
-
for (MyBeanDefinition beanDefinition : beanDefinitions) {
-
-
// Bean在IoC容器中名称必须唯一
-
if (
this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
-
throw
new Exception(
"The " + beanDefinition.getFactoryBeanName() +
"is exists");
-
}
-
-
// 分别用两种名称存储,便于查找
-
beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
-
beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
-
}
-
}
-
-
/**
-
* Bean的实例化和DI是从这个方法开始的。
-
*
-
* @param beanName
-
* @return
-
*/
-
public Object getBean(String beanName) {
-
log.info(
"ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。");
-
-
// 1、先拿到BeanDefinition配置信息
-
MyBeanDefinition beanDefinition =
this.beanDefinitionMap.get(beanName);
-
-
// 2、反射实例化newInstance();
-
Object instance = instantiateBean(beanName, beanDefinition);
-
-
// 3、封装成一个叫做BeanWrapper
-
MyBeanWrapper beanWrapper =
new MyBeanWrapper(instance);
-
-
// 4、保存到IoC容器
-
factoryBeanInstanceCache.put(beanName, beanWrapper);
-
-
// 5、执行依赖注入
-
populateBean(beanName, beanDefinition, beanWrapper);
-
-
// 6、返回对象
-
return beanWrapper.getWrapperInstance();
-
}
-
-
public Object getBean(Class<?> beanClass) {
-
return getBean(beanClass.getName());
-
}
-
-
/**
-
* DI核心逻辑。
-
*
-
* @param beanName
-
* @param beanDefinition
-
* @param beanWrapper
-
*/
-
private void populateBean(String beanName, MyBeanDefinition beanDefinition, MyBeanWrapper beanWrapper) {
-
log.info(
"ApplicationContext -> 完成依赖注入核心逻辑。");
-
-
// TODO 可能涉及到循环依赖待解决,如果依赖对象还未实例化,注入的示例为Null引发后续问题,这里需要考虑如何解决。
-
-
// 1、拿到当前Bean实例化对象
-
Object instance = beanWrapper.getWrapperInstance();
-
-
// 2、拿到当前Bean的类信息
-
Class<?> clazz = beanWrapper.getWrapperClass();
-
-
// 3、只有注解的类,才执行依赖注入
-
if (!(clazz.isAnnotationPresent(MyController.class) || clazz.isAnnotationPresent(MyService.class))) {
-
return;
-
}
-
-
// 把所有的包括private/protected/default/public 修饰字段都取出来
-
// TODO 这里只考虑接口注入的方式,实际还要考虑构造器注入和Setter注入。
-
for (Field field : clazz.getDeclaredFields()) {
-
-
// 是否被Autowired标记为自动注入
-
if (!field.isAnnotationPresent(MyAutowired.class)) {
-
continue;
-
}
-
-
MyAutowired autowired = field.getAnnotation(MyAutowired.class);
-
// 如果用户没有自定义的beanName,就默认根据类型注入
-
String autowiredBeanName = autowired.value().trim();
-
if ("".equals(autowiredBeanName)) {
-
-
// field.getType().getName() 获取字段的类型的全限定名
-
autowiredBeanName = toLowerFirstCase(field.getType().getSimpleName());
-
}
-
-
// 暴力访问
-
field.setAccessible(
true);
-
-
try {
-
// 获取对应名称的bean实例对象 TODO
-
// 此处没有考虑Bean的实例化顺序,可能需要注入的对象此时还没有完成实例化,在IoC容器中无法正确获取。不过除了在初始化时触发DI,
-
// 在实际调用的时候,通过getBean()获取对象时,仍然会触发DI操作。
-
if (
this.factoryBeanInstanceCache.get(autowiredBeanName) ==
null) {
-
continue;
-
}
-
-
// ioc.get(beanName) 相当于通过接口的全名拿到接口的实现的实例
-
field.set(instance,
this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
-
}
catch (IllegalAccessException e) {
-
e.printStackTrace();
-
continue;
-
}
-
}
-
}
-
-
/**
-
* 创建真正的实例对象
-
*
-
*
@param beanName
-
*
@param beanDefinition
-
*
@return
-
*/
-
private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) {
-
log.info(
"ApplicationContext -> 通过反射创建Bean实例。");
-
-
String className = beanDefinition.getBeanClassName();
-
Object instance =
null;
-
try {
-
Class<?> clazz = Class.forName(className);
-
instance = clazz.newInstance();
-
-
// 默认的类名首字母小写
-
this.factoryBeanObjectCache.put(beanName, instance);
-
}
catch (Exception e) {
-
e.printStackTrace();
-
}
-
return instance;
-
}
-
-
/**
-
* 已注册所有Bean的名称
-
*
-
* @return
-
*/
-
public String[] getBeanDefinitionNames() {
-
return
this.beanDefinitionMap.keySet().toArray(
new String[
this.beanDefinitionMap.size()]);
-
}
-
-
/**
-
* 已注册Bean的数量
-
*
-
* @return
-
*/
-
public int getBeanDefinitionCount() {
-
return
this.beanDefinitionMap.size();
-
}
-
-
public Properties getConfig() {
-
return
this.reader.getConfig();
-
}
-
-
/**
-
* 将大写字母转换为小写
-
*
-
* @param simpleName
-
* @return
-
*/
-
private String toLowerFirstCase(String simpleName) {
-
char[] chars = simpleName.toCharArray();
-
chars[
0] +=
32;
-
return String.valueOf(chars);
-
}
-
}
BeanDefinition
-
package com.demo.spring.simulation.v5.beans.config;
-
-
import lombok.Data;
-
-
/**
-
* Spring Bean定义信息
-
*/
-
@Data
-
public
class MyBeanDefinition {
-
// Bean全路径类名
-
private String beanClassName;
-
-
// Bean在IoC容器中名称
-
private String factoryBeanName;
-
}
-
-
-
BeanDefinitionReader
-
-
package com.demo.spring.simulation.v5.beans.support;
-
-
import com.demo.spring.simulation.v5.beans.config.MyBeanDefinition;
-
import lombok.extern.slf4j.Slf4j;
-
-
import java.io.File;
-
import java.io.IOException;
-
import java.io.InputStream;
-
import java.net.URL;
-
import java.util.ArrayList;
-
import java.util.List;
-
import java.util.Properties;
-
-
/**
-
* 扫描配置文件,读取Bean定义
-
*/
-
@Slf4j
-
public
class MyBeanDefinitionReader {
-
-
// 保存扫描的结果
-
private List<String> regitryBeanClasses =
new ArrayList<String>();
-
-
// 保存配置信息
-
private Properties contextConfig =
new Properties();
-
-
public MyBeanDefinitionReader(String... configLocations) {
-
log.info(
"BeanDefinitionReader -> 构造器执行开始。");
-
-
// 1、读取配置信息。
-
doLoadConfig(configLocations[
0]);
-
log.info(
"BeanDefinitionReader -> 1、读取配置信息。");
-
-
// 2、扫描配置文件中的配置的相关的类。
-
doScanner(contextConfig.getProperty(
"scanPackage"));
-
log.info(
"BeanDefinitionReader -> 2、扫描配置文件中的配置的相关的类。");
-
-
log.info(
"BeanDefinitionReader -> 构造器执行完成。");
-
}
-
-
/**
-
* 将Bean封装为BeanDefinition
-
*
-
* @return
-
*/
-
public List<MyBeanDefinition> loadBeanDefinitions() {
-
log.info(
"BeanDefinitionReader -> 将扫描的Bean信息封装成BeanDefinition。");
-
-
List<MyBeanDefinition> result =
new ArrayList<MyBeanDefinition>();
-
try {
-
for (String className : regitryBeanClasses) {
-
Class<?> beanClass = Class.forName(className);
-
-
// 接口不能实例化
-
if (beanClass.isInterface()) {
-
continue;
-
}
-
-
// BeanName有三种情况:
-
// 1、默认是类名首字母小写
-
// 2、自定义名称
-
// 3、接口注入
-
result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getSimpleName()), beanClass.getName()));
-
-
// 如果是多个实现类,只能覆盖
-
for (Class<?> i : beanClass.getInterfaces()) {
-
result.add(doCreateBeanDefinition(i.getName(), beanClass.getName()));
-
}
-
}
-
}
catch (Exception e) {
-
e.printStackTrace();
-
}
-
-
return result;
-
}
-
-
private MyBeanDefinition doCreateBeanDefinition(String beanName, String beanClassName) {
-
MyBeanDefinition beanDefinition =
new MyBeanDefinition();
-
beanDefinition.setFactoryBeanName(beanName);
-
beanDefinition.setBeanClassName(beanClassName);
-
return beanDefinition;
-
}
-
-
/**
-
* 从配置文件中加载Spring配置信息
-
*
-
* @param contextConfigLocation
-
*/
-
private void doLoadConfig(String contextConfigLocation) {
-
log.info(
"BeanDefinitionReader -> 加载Spring配置文件。");
-
-
InputStream is =
this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation.replaceAll(
"classpath:",
""));
-
try {
-
contextConfig.load(is);
-
}
catch (IOException e) {
-
e.printStackTrace();
-
}
finally {
-
if (
null != is) {
-
try {
-
is.close();
-
}
catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
}
-
-
/**
-
* 根据配置的basePackage扫描获取Bean定义
-
*
-
* @param scanPackage
-
*/
-
private void doScanner(String scanPackage) {
-
log.info(
"BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。");
-
-
//jar 、 war 、zip 、rar
-
URL url =
this.getClass().getClassLoader().getResource(scanPackage.replaceAll(
"\\.",
"/"));
-
File classPath =
new File(url.getFile());
-
-
//当成是一个ClassPath文件夹
-
for (File file : classPath.listFiles()) {
-
if (file.isDirectory()) {
-
doScanner(scanPackage +
"." + file.getName());
-
}
else {
-
if (!file.getName().endsWith(
".class")) {
-
continue;
-
}
-
//全类名 = 包名.类名
-
String className = (scanPackage +
"." + file.getName().replace(
".class",
""));
-
//Class.forName(className);
-
regitryBeanClasses.add(className);
-
}
-
}
-
}
-
-
/**
-
* 将大写字母转换为小写
-
*
-
* @param simpleName
-
* @return
-
*/
-
private String toLowerFirstCase(String simpleName) {
-
char[] chars = simpleName.toCharArray();
-
chars[
0] +=
32;
-
return String.valueOf(chars);
-
}
-
-
/**
-
* 获取配置信息
-
*
-
* @return
-
*/
-
public Properties getConfig() {
-
return
this.contextConfig;
-
}
-
}
BeanWrapper
-
package com.demo.spring.simulation.v5.beans;
-
-
/**
-
* Spring IoC容器对Bean生成的代理类
-
*/
-
public
class MyBeanWrapper {
-
// Bean的实例化对象
-
private Object wrappedInstance;
-
-
// Bean的Class信息
-
private Class<?> wrapperClass;
-
-
public MyBeanWrapper(Object wrappedInstance) {
-
this.wrappedInstance = wrappedInstance;
-
this.wrapperClass = wrappedInstance.getClass();
-
}
-
-
public Object getWrapperInstance() {
-
return
this.wrappedInstance;
-
}
-
-
// 返回代理Class
-
public Class<?> getWrapperClass() {
-
return
this.wrapperClass;
-
}
-
}
Pt1.5 功能验证
IoC测试我们直接以main启动ApplicationContext的模式来验证,推荐使用DEBUG模式看每一步数据的处理,这里为了简单描述,我直接输出测试代码和结果。
-
测试启动类
-
/**
-
* Spring IoC和DI测试类。
-
*/
-
public
class MyApplicationContextTest {
-
-
public static void main(String[] args) {
-
MyApplicationContext applicationContext =
new MyApplicationContext(
"classpath:application.properties");
-
// IoC容器初始化时,通过执行getBean完成DI。此处再次调用getBean防止有未被注入的属性。
-
DemoController demoController = (DemoController) applicationContext.getBean(DemoController.class);
-
demoController.say();
-
}
-
}
-
业务处理入口
-
@MyController
-
public
class DemoController {
-
-
@MyAutowired()
-
private DemoService demoService;
-
-
public void say() {
-
demoService.say();
-
}
-
}
-
业务处理核心类
-
@MyService
-
public
class DemoService {
-
public void say() {
-
System.out.println(
"执行自定义Service方法。");
-
}
-
}
-
测试结果输出
启动测试类,看输出结果。
-
17:02:30.638 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 构造器执行开始。
-
17:02:30.645 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 加载Spring配置文件。
-
17:02:30.648 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 1、读取配置信息。
-
17:02:30.648 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。
-
17:02:30.649 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。
-
17:02:30.652 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。
-
17:02:30.653 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。
-
17:02:30.653 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 2、扫描配置文件中的配置的相关的类。
-
17:02:30.653 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 构造器执行完成。
-
17:02:30.653 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 1、加载、解析配置文件,扫描相关的类。
-
17:02:30.653 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 将扫描的Bean信息封装成BeanDefinition。
-
17:02:30.658 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 2、将扫描的Bean封装成BeanDefinition。
-
17:02:30.658 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 缓存BeanDefinition信息。
-
17:02:30.658 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 3、注册,把BeanDefintion缓存到容器。
-
17:02:30.659 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.659 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.674 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.689 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.689 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.690 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.710 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.712 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.713 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.713 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.714 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.715 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.715 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.715 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.720 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.725 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.725 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.726 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.726 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.726 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.733 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.734 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.734 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.735 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.735 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.735 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.737 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.738 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.738 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.738 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.747 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.753 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.753 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.755 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.755 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.755 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.758 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.760 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.761 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.764 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.764 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.764 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.767 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.767 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.767 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.770 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.771 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.771 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.773 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.776 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.777 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.779 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.779 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.780 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.782 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
17:02:30.784 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 4、完成自动依赖注入。
-
17:02:30.785 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
-
17:02:30.785 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
-
17:02:30.818 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
-
执行自定义Service方法。
-
-
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 基础配置
在配置文件中,我们配置静态资源的根路径。
-
# 静态资源路径
-
templateRoot=/webapp/WEB-INF/view
Pt2.4 核心代码
DispatcherServlet
在DispatcherServlet中完成了IoC、DI和MVC的初始化动作。
-
package com.demo.spring.simulation.v5.servlet;
-
-
import com.demo.spring.simulation.v5.annotation.MyController;
-
import com.demo.spring.simulation.v5.annotation.MyRequestMapping;
-
import com.demo.spring.simulation.v5.context.MyApplicationContext;
-
import lombok.extern.slf4j.Slf4j;
-
-
import javax.servlet.ServletConfig;
-
import javax.servlet.ServletException;
-
import javax.servlet.http.HttpServlet;
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
import java.io.File;
-
import java.io.IOException;
-
import java.lang.reflect.Method;
-
import java.util.*;
-
import java.util.regex.Matcher;
-
import java.util.regex.Pattern;
-
-
/**
-
* DispatcherServlet负责请求调度和分发。
-
*/
-
@Slf4j
-
public
class MyDispatcherServlet extends HttpServlet {
-
-
// Spring配置文件路径
-
private
static
final String CONTEXT_CONFIG_LOCATION =
"contextConfigLocation";
-
-
// Spring上下文,Spring IoC容器
-
private MyApplicationContext applicationContext;
-
-
// 保存请求URL和处理方法的映射关系
-
private List<MyHandlerMapping> handlerMappings =
new ArrayList<MyHandlerMapping>();
-
-
// 保存请求映射和处理Handler的关系
-
private Map<MyHandlerMapping, MyHandlerAdapter> handlerAdapters =
new HashMap<MyHandlerMapping, MyHandlerAdapter>();
-
-
// 保存所有View解析器
-
private List<MyViewResolver> viewResolvers =
new ArrayList<MyViewResolver>();
-
-
@Override
-
public void init(ServletConfig config) throws ServletException {
-
log.info(
"DispatcherServlet -> Create Web Server Starting.");
-
-
// 1、初始化ApplicationContext。ApplicationContext包含了Spring核心IoC容器,完成Bean扫描、初始化和DI。
-
log.info(
"DispatcherServlet -> Init Spring IoC/DI Starting.");
-
applicationContext =
new MyApplicationContext(config.getInitParameter(CONTEXT_CONFIG_LOCATION));
-
log.info(
"DispatcherServlet -> Init Spring IoC/DI Finished.");
-
-
// 2、初始化Spring MVC九大组件
-
log.info(
"DispatcherServlet -> Init Spring MVC Starting.");
-
initStrategies(applicationContext);
-
log.info(
"DispatcherServlet -> Init Spring MVC Finished.");
-
-
log.info(
"DispatcherServlet -> Create Web Server Finished.");
-
}
-
-
@Override
-
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-
this.doPost(req, resp);
-
}
-
-
@Override
-
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-
log.info(
"DispatcherServlet -> Receive client request.");
-
-
try {
-
// 3、委派,根据URL去找到一个对应的Method并通过response返回
-
doDispatch(req, resp);
-
}
catch (Exception e) {
-
try {
-
processDispatchResult(req, resp,
new MyModelAndView(
"500"));
-
}
catch (Exception e1) {
-
e1.printStackTrace();
-
resp.getWriter().write(
"500 Exception,Detail : " + Arrays.toString(e.getStackTrace()));
-
}
-
}
-
-
log.info(
"DispatcherServlet -> Return client response.");
-
}
-
-
/**
-
* 完成Spring MVC组件的初始化。
-
*
-
* @param context
-
*/
-
private void initStrategies(MyApplicationContext context) {
-
// 1、多文件上传的组件 TODO
-
// initMultipartResolver(context);
-
// log.info("DispatcherServlet -> 1、多文件上传的组件");
-
-
// 2、初始化本地语言环境 TODO
-
// initLocaleResolver(context);
-
// log.info("DispatcherServlet -> 2、初始化本地语言环境");
-
-
// 3、初始化模板处理器 TODO
-
// initThemeResolver(context);
-
// log.info("DispatcherServlet -> 3、初始化模板处理器");
-
-
// 4、初始化HandlerMapping,必须实现。
-
initHandlerMappings(context);
-
log.info(
"DispatcherServlet -> 4、初始化HandlerMapping,必须实现。");
-
-
// 5、初始化参数适配器,必须实现。
-
initHandlerAdapters(context);
-
log.info(
"DispatcherServlet -> 5、初始化参数适配器,必须实现。");
-
-
// 6、初始化异常拦截器 TODO
-
// initHandlerExceptionResolvers(context);
-
// log.info("DispatcherServlet -> 6、初始化异常拦截器");
-
-
// 7、初始化视图预处理器 TODO
-
// initRequestToViewNameTranslator(context);
-
// log.info("DispatcherServlet -> 7、初始化视图预处理器");
-
-
// 8、初始化视图转换器,必须实现。
-
initViewResolvers(context);
-
log.info(
"DispatcherServlet -> 8、初始化视图转换器,必须实现。");
-
-
// 9、初始化FlashMap管理器 TODO
-
// initFlashMapManager(context);
-
// log.info("DispatcherServlet -> 9、初始化FlashMap管理器");
-
}
-
-
/**
-
* HandlerMapping:保存请求URL和处理方法的映射关系。
-
*
-
* @param context
-
*/
-
private void initHandlerMappings(MyApplicationContext context) {
-
log.info(
"DispatcherServlet -> 解析和缓存HandlerMapping");
-
-
if (
this.applicationContext.getBeanDefinitionCount() ==
0) {
-
return;
-
}
-
-
for (String beanName :
this.applicationContext.getBeanDefinitionNames()) {
-
Object instance = applicationContext.getBean(beanName);
-
Class<?> clazz = instance.getClass();
-
-
// 1、Controller注解的类才具备URL配置
-
if (!clazz.isAnnotationPresent(MyController.class)) {
-
continue;
-
}
-
-
//
2、提取
class上配置的base_url
-
String
baseUrl =
"";
-
if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
-
baseUrl = clazz.getAnnotation(MyRequestMapping.class).value();
-
}
-
-
//
3、获取
public的方法
-
for (Method method : clazz.getMethods()) {
-
if (!method.isAnnotationPresent(MyRequestMapping.class)) {
-
continue;
-
}
-
//
4、提取每个方法上面配置的url
-
MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
-
-
//
5、拼接URL
-
String regex = (
"/" + baseUrl +
"/" + requestMapping.value().replaceAll(
"\\*",
".*")).replaceAll(
"/+",
"/");
-
Pattern pattern = Pattern.compile(regex);
-
-
-
// 6、保存HandlerMapping映射关系
-
handlerMappings.add(
new MyHandlerMapping(pattern, instance, method));
-
}
-
}
-
}
-
-
/**
-
* 初始化参数适配器。
-
*
-
* @param context
-
*/
-
private void initHandlerAdapters(MyApplicationContext context) {
-
log.info(
"DispatcherServlet -> 创建HandlerAdapter处理类。");
-
-
// HandlerAdapter调用具体的方法对用户发来的请求来进行处理,所以每个HandlerMapping都对应一个HandlerAdapter。
-
for (MyHandlerMapping handlerMapping : handlerMappings) {
-
this.handlerAdapters.put(handlerMapping,
new MyHandlerAdapter());
-
}
-
}
-
-
/**
-
* 根据请求URL找对对应处理Handler完成请求,并返回Response。
-
*
-
* @param req
-
* @param resp
-
* @throws Exception
-
*/
-
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
-
log.info(
"DispatcherServlet -> 请求分发");
-
-
// 1、通过从Request获得请求URL,去匹配一个HandlerMapping
-
MyHandlerMapping handler = getHandler(req);
-
if (handler ==
null) {
-
processDispatchResult(req, resp,
new MyModelAndView(
"404"));
-
return;
-
}
-
-
// 2、根据一个HandlerMaping获得一个HandlerAdapter
-
MyHandlerAdapter ha = getHandlerAdapter(handler);
-
-
// 3、解析某一个方法的形参和返回值之后,统一封装为ModelAndView对象
-
MyModelAndView mv = ha.handler(req, resp, handler);
-
-
// 4、把ModelAndView变成一个ViewResolver
-
processDispatchResult(req, resp, mv);
-
}
-
-
/**
-
* 匹配到一个Handler处理器。
-
*
-
* @param handlerMapping
-
* @return
-
*/
-
private MyHandlerAdapter getHandlerAdapter(MyHandlerMapping handlerMapping) {
-
log.info(
"DispatcherServlet -> 获取请求对应的处理类。");
-
-
if (
this.handlerAdapters.isEmpty()) {
-
return
null;
-
}
-
return
this.handlerAdapters.get(handlerMapping);
-
}
-
-
/**
-
* 封装请求结果,输出到浏览器。
-
*
-
* @param req
-
* @param resp
-
* @param mv
-
* @throws Exception
-
*/
-
private void processDispatchResult(HttpServletRequest req, HttpServletResponse resp, MyModelAndView mv) throws Exception {
-
log.info(
"DispatcherServlet -> 封装请求结果并输出");
-
-
if (
null == mv) {
-
return;
-
}
-
-
if (
this.viewResolvers.isEmpty()) {
-
return;
-
}
-
-
for (MyViewResolver viewResolver :
this.viewResolvers) {
-
MyView view = viewResolver.resolveViewName(mv.getViewName());
-
//直接往浏览器输出
-
view.render(mv.getModel(), req, resp);
-
return;
-
}
-
}
-
-
/**
-
* 从Request中获取URL,然后匹配对应的HandlerMapping。
-
*
-
* @param req
-
* @return
-
*/
-
private MyHandlerMapping getHandler(HttpServletRequest req) {
-
log.info(
"DispatcherServlet -> 根据Request Url获取HandlerMapping");
-
-
if (
this.handlerMappings.isEmpty()) {
-
return
null;
-
}
-
-
// 从Request中获取请求URL
-
String url = req.getRequestURI();
-
String contextPath = req.getContextPath();
-
url = url.replaceAll(contextPath,
"").replaceAll(
"/+",
"/");
-
-
// 匹配HandlerMapping
-
for (MyHandlerMapping mapping : handlerMappings) {
-
Matcher matcher = mapping.getPattern().matcher(url);
-
if (!matcher.matches()) {
-
continue;
-
}
-
return mapping;
-
}
-
-
return
null;
-
}
-
-
/**
-
* 初始化视图解析器,根据配置的根路径,遍历解析所有的View。
-
*
-
* @param context
-
*/
-
private void initViewResolvers(MyApplicationContext context) {
-
log.info(
"DispatcherServlet -> 初始化视图解析器");
-
-
// 从配置中获取模板文件存放路径
-
String templateRoot = context.getConfig().getProperty(
"templateRoot");
-
// String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();
-
String templateRootPath =
this.getClass().getClassLoader().getResource(
"/").getPath().replaceAll(
"/target/classes",
"");
-
templateRootPath = templateRootPath +
"/src/main" + templateRoot;
-
templateRootPath = templateRootPath.replaceAll(
"/+",
"/");
-
-
File templateRootDir =
new File(templateRootPath);
-
for (File file : templateRootDir.listFiles()) {
-
this.viewResolvers.add(
new MyViewResolver(templateRootDir.getPath()));
-
}
-
}
-
}
HandlerMapping
HandlerMapping保存了请求URL和处理器Handler之前的映射关系。
-
package com.demo.spring.simulation.v5.servlet;
-
-
import lombok.Data;
-
-
import java.lang.reflect.Method;
-
import java.util.regex.Pattern;
-
-
/**
-
* HandlerMapping映射注册、根据url获取对应的处理器、拦截器注册。
-
*/
-
@Data
-
public
class MyHandlerMapping {
-
-
// 请求URL的正则匹配
-
private Pattern pattern;
-
-
// URL对应的Method
-
private Method method;
-
-
// Method对应的实例对象
-
private Object controller;
-
-
public MyHandlerMapping(Pattern pattern, Object controller, Method method) {
-
this.pattern = pattern;
-
this.method = method;
-
this.controller = controller;
-
}
-
}
HandlerAdapter
HandlerAdapter将Servlet的request-response模式进行封装,执行对应的处理器完成响应。
-
package com.demo.spring.simulation.v5.servlet;
-
-
import com.demo.spring.simulation.v5.annotation.MyRequestParam;
-
import lombok.extern.slf4j.Slf4j;
-
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
import java.lang.annotation.Annotation;
-
import java.util.Arrays;
-
import java.util.HashMap;
-
import java.util.Map;
-
-
/**
-
* HandlerAdapter调用具体的方法对用户发来的请求来进行处理。
-
*/
-
@Slf4j
-
public
class MyHandlerAdapter {
-
-
public boolean supports(Object handler) {
-
return (handler
instanceof MyHandlerMapping);
-
}
-
-
/**
-
* @param req
-
* @param resp
-
* @param handlerMapping
-
* @return
-
* @throws Exception
-
*/
-
public MyModelAndView handler(HttpServletRequest req, HttpServletResponse resp, MyHandlerMapping handlerMapping) throws Exception {
-
log.info(
"MyViewResolver -> Request请求处理核心逻辑");
-
-
// 1、将方法的形参列表和Request参数列表进行匹配和对应。
-
Map<String, Integer> paramIndexMapping =
new HashMap<String, Integer>();
-
-
// 2、获取请求处理方法的参数注解。
-
// 提取方法中加入了注解的参数。一个参数可以有多个注解,而一个方法又有多个参数,所以是一个二维数组。
-
Annotation[][] pa = handlerMapping.getMethod().getParameterAnnotations();
-
for (
int i =
0; i < pa.length; i++) {
-
for (Annotation a : pa[i]) {
-
if (a
instanceof MyRequestParam) {
-
String paramName = ((MyRequestParam) a).value();
-
if (!
"".equals(paramName.trim())) {
-
paramIndexMapping.put(paramName, i);
-
}
-
}
-
}
-
}
-
-
// 3、提取Request和Response参数。
-
Class<?>[] paramTypes = handlerMapping.getMethod().getParameterTypes();
-
for (
int i =
0; i < paramTypes.length; i++) {
-
Class<?> paramterType = paramTypes[i];
-
if (paramterType == HttpServletRequest.class || paramterType == HttpServletResponse.class) {
-
paramIndexMapping.put(paramterType.getName(), i);
-
}
-
}
-
-
-
//
4、获取请求方法的形参列表。
-
// Eg.http://localhost/web/query?name=Tom&Cat=1
-
Map<String, String[]> params = req.getParameterMap();
-
-
// 实参列表
-
Object[] paramValues =
new Object[paramTypes.length];
-
for (Map.Entry<String, String[]> param : params.entrySet()) {
-
String value = Arrays.toString(params.get(param.getKey()))
-
.replaceAll(
"\\[|\\]",
"")
-
.replaceAll(
"\\s+",
",");
-
-
if (!paramIndexMapping.containsKey(param.getKey())) {
-
continue;
-
}
-
-
int index = paramIndexMapping.get(param.getKey());
-
-
//允许自定义的类型转换器Converter
-
paramValues[index] = castStringValue(value, paramTypes[index]);
-
}
-
-
// 处理Request
-
if (paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
-
int index = paramIndexMapping.get(HttpServletRequest.class.getName());
-
paramValues[index] = req;
-
}
-
-
// 处理
Response
-
if
(paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
-
int index = paramIndexMapping.get(HttpServletResponse.class.getName());
-
paramValues[index] = resp;
-
}
-
-
//
5、通过反射执行方法体
-
Object result = handlerMapping.getMethod().invoke(handlerMapping.getController(), paramValues);
-
if (result ==
null || result
instanceof Void) {
-
return
null;
-
}
-
-
boolean isModelAndView = handlerMapping.getMethod().getReturnType() == MyModelAndView.class;
-
if (isModelAndView) {
-
return (MyModelAndView) result;
-
}
-
-
return
null;
-
}
-
-
private Object castStringValue(String value, Class<?> paramType) {
-
if (String.class == paramType) {
-
return value;
-
}
else
if (Integer.class == paramType) {
-
return Integer.valueOf(value);
-
}
else
if (Double.class == paramType) {
-
return Double.valueOf(value);
-
}
else {
-
if (value !=
null) {
-
return value;
-
}
-
return
null;
-
}
-
-
}
-
}
ModelAndView
保存请求响应的view和model信息,以便后续完成解析和渲染。
-
package com.demo.spring.simulation.v5.servlet;
-
-
import java.util.Map;
-
-
/**
-
* ModelAndView类用来存储处理完后的结果数据,以及显示该数据的视图。
-
*/
-
public
class MyModelAndView {
-
-
// 该属性用来存储返回的视图信息
-
private String viewName;
-
-
// Model代表模型数据
-
private Map<String,?> model;
-
-
public MyModelAndView(String viewName, Map<String, ?> model) {
-
this.viewName = viewName;
-
this.model = model;
-
}
-
-
public MyModelAndView(String viewName) {
-
this.viewName = viewName;
-
}
-
-
public String getViewName() {
-
return viewName;
-
}
-
-
public Map<String, ?> getModel() {
-
return model;
-
}
-
}
ViewResolver
ViewResolver用来将String类型的视图名解析为View类型的视图。ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。
-
package com.demo.spring.simulation.v5.servlet;
-
-
import lombok.extern.slf4j.Slf4j;
-
import java.io.File;
-
-
/**
-
* 视图解析器的作用是将逻辑视图转为物理视图,所有的视图解析器都必须实现ViewResolver接口。
-
*/
-
@Slf4j
-
public
class MyViewResolver {
-
// 视图默认后缀名
-
private
final String DEFAULT_TEMPLATE_SUFFIX =
".html";
-
-
// 视图文件根路径
-
private File templateRootDir;
-
-
public MyViewResolver(String templateRoot) {
-
// String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();
-
templateRootDir =
new File(templateRoot);
-
}
-
-
/**
-
* 根据视图名称获取视图定义信息
-
*
-
* @param viewName
-
* @return
-
*/
-
public MyView resolveViewName(String viewName) {
-
log.info(
"MyViewResolver -> 视图解析");
-
-
if (
null == viewName ||
"".equals(viewName.trim())) {
-
return
null;
-
}
-
-
viewName = viewName.endsWith(DEFAULT_TEMPLATE_SUFFIX) ? viewName : (viewName + DEFAULT_TEMPLATE_SUFFIX);
-
File templateFile =
new File((templateRootDir.getPath() +
"/" + viewName).replaceAll(
"/+",
"/"));
-
return
new MyView(templateFile);
-
}
-
}
View
View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。
-
package com.demo.spring.simulation.v5.servlet;
-
-
import lombok.extern.slf4j.Slf4j;
-
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
import java.io.File;
-
import java.io.RandomAccessFile;
-
import java.util.Map;
-
import java.util.regex.Matcher;
-
import java.util.regex.Pattern;
-
-
/**
-
* 存储视图对象数据
-
*/
-
@Slf4j
-
public
class MyView {
-
// 视图文件
-
private File viewFile;
-
-
public MyView(File templateFile) {
-
this.viewFile = templateFile;
-
}
-
-
public void render(Map<String, ?> model, HttpServletRequest req, HttpServletResponse resp) throws Exception {
-
log.info(
"MyViewResolver -> 解析视图文件");
-
-
StringBuffer sb =
new StringBuffer();
-
RandomAccessFile ra =
new RandomAccessFile(
this.viewFile,
"r");
-
-
String line =
null;
-
while (
null != (line = ra.readLine())) {
-
line =
new String(line.getBytes(
"ISO-8859-1"),
"utf-8");
-
Pattern pattern = Pattern.compile(
"¥\\{[^\\}]+\\}", Pattern.CASE_INSENSITIVE);
-
Matcher matcher = pattern.matcher(line);
-
while (matcher.find()) {
-
String paramName = matcher.group();
-
paramName = paramName.replaceAll(
"¥\\{|\\}",
"");
-
Object paramValue = model.get(paramName);
-
line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString()));
-
matcher = pattern.matcher(line);
-
}
-
sb.append(line);
-
}
-
resp.setCharacterEncoding(
"utf-8");
-
resp.getWriter().write(sb.toString());
-
}
-
-
// 处理特殊字符
-
public static String makeStringForRegExp(String str) {
-
return str.replace(
"\\",
"\\\\").replace(
"*",
"\\*")
-
.replace(
"+",
"\\+").replace(
"|",
"\\|")
-
.replace(
"{",
"\\{").replace(
"}",
"\\}")
-
.replace(
"(",
"\\(").replace(
")",
"\\)")
-
.replace(
"^",
"\\^").replace(
"$",
"\\$")
-
.replace(
"[",
"\\[").replace(
"]",
"\\]")
-
.replace(
"?",
"\\?").replace(
",",
"\\,")
-
.replace(
".",
"\\.").replace(
"&",
"\\&");
-
}
-
}
Pt2.5 功能验证
-
先编写测试代码,定义请求处理的Controller。
-
package com.demo.spring.simulation.v5.test.mvc;
-
-
import com.demo.spring.simulation.v5.annotation.MyAutowired;
-
import com.demo.spring.simulation.v5.annotation.MyController;
-
import com.demo.spring.simulation.v5.annotation.MyRequestMapping;
-
import com.demo.spring.simulation.v5.annotation.MyRequestParam;
-
import com.demo.spring.simulation.v5.servlet.MyModelAndView;
-
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
import java.util.HashMap;
-
import java.util.Map;
-
-
@MyController
-
@MyRequestMapping("/mvc/")
-
public
class MvcController {
-
-
@MyRequestMapping("/hello")
-
public MyModelAndView hello(HttpServletRequest request, HttpServletResponse response, @MyRequestParam(value = "id") String id, @MyRequestParam(value = "name") String name) {
-
Map<String, Object> model =
new HashMap<String, Object>();
-
model.put(
"message",
"hello " + id +
" " + name);
-
return
new MyModelAndView(
"hello", model);
-
}
-
}
-
定义View。
-
<!DOCTYPE html>
-
<html lang="en">
-
<head>
-
<meta charset="UTF-8">
-
<title>Title
</title>
-
</head>
-
<body>
-
<div>¥{message}
</div>
-
</body>
-
</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中便于读取方便。
-
# AOP配置
-
#切面表达式expression#
-
pointCut=public .* com.demo.spring.simulation.v5.test.aop.AspectServiceImpl.print(.*)
-
#切面类
-
aspectClass=com.demo.spring.simulation.v5.test.aop.LogAspect
-
#前置通知回调方法
-
aspectBefore=before
-
#后置通知回调方法
-
aspectAfter=after
-
#异常通知回调方法
-
aspectAfterThrow=afterThrowing
-
#异常类型捕获
-
aspectAfterThrowingName=java.lang.Exception
Pt3.3 核心代码
AopProxy
定义用于实现动态代理的接口类。
-
package com.demo.spring.simulation.v5.aop;
-
-
public
interface MyAopProxy {
-
public void print();
-
}
JdkDynamicAopProxy
基于JDK 动态代理实现的代理类,关于动态代理这里不深入,重点是两个方法:getProxy()用于生成代理类,invoke()用于完成代理逻辑的执行。
-
package com.demo.spring.simulation.v5.aop;
-
-
import com.demo.spring.simulation.v5.aop.aspect.MyAdvice;
-
import com.demo.spring.simulation.v5.aop.support.MyAdvisedSupport;
-
-
import java.lang.reflect.InvocationHandler;
-
import java.lang.reflect.InvocationTargetException;
-
import java.lang.reflect.Method;
-
import java.lang.reflect.Proxy;
-
import java.util.Map;
-
-
/**
-
* 基于JDK动态代理的AOP实现
-
*/
-
public
class MyJdkDynamicAopProxy implements InvocationHandler {
-
-
// AOP核心支撑类,保存了被代理类Class和实例化对象、AOP定义等。
-
private MyAdvisedSupport advisedSupport;
-
-
public MyJdkDynamicAopProxy(MyAdvisedSupport advisedSupport) {
-
this.advisedSupport = advisedSupport;
-
}
-
-
/**
-
* 通过动态代理,生成目标类的代理类。
-
*
-
* @return
-
*/
-
public Object getProxy() {
-
return Proxy
-
.newProxyInstance(
this.getClass().getClassLoader(),
this.advisedSupport.getTargetClass().getInterfaces(),
-
this);
-
}
-
-
/**
-
* 执行具体代理方法。
-
*
-
* @param proxy
-
* @param method
-
* @param args
-
* @return
-
* @throws Throwable
-
*/
-
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-
-
// 1、获取方法对应的AOP定义。
-
Map<String, MyAdvice> advices =
this.advisedSupport
-
.getInterceptorsAndDynamicInterceptionAdvice(method,
this.advisedSupport.getTargetClass());
-
-
// 2、执行AOP的before方法
-
invokeAdvice(advices.get(
"before"));
-
-
// 3、执行被代理类切点方法
-
Object returnValue =
null;
-
try {
-
returnValue = method.invoke(
this.advisedSupport.getTarget(), args);
-
}
catch (Exception ex) {
-
// 4、发生异常时,执行AOP的Exception方法
-
invokeAdvice(advices.get(
"afterThrow"));
-
throw ex;
-
}
-
-
// 5、调用AOP的after方法
-
invokeAdvice(advices.get(
"after"));
-
-
return returnValue;
-
}
-
-
/**
-
* 通过反射完成方法调用。
-
*
-
* @param advice
-
*/
-
private void invokeAdvice(MyAdvice advice) {
-
try {
-
advice.getAdviceMethod().invoke(advice.getAspect());
-
}
catch (IllegalAccessException e) {
-
e.printStackTrace();
-
}
catch (InvocationTargetException e) {
-
e.printStackTrace();
-
}
-
}
-
}
AdvisedSupport
AdvisedSupport是AOP核心类,负责解析AOP的配置(切点、切面、通知方法等),并完成对应关系的缓存。
-
package com.demo.spring.simulation.v5.aop.support;
-
-
import com.demo.spring.simulation.v5.aop.aspect.MyAdvice;
-
import com.demo.spring.simulation.v5.aop.config.MyAopConfig;
-
-
import java.lang.reflect.InvocationHandler;
-
import java.lang.reflect.Method;
-
import java.util.HashMap;
-
import java.util.Map;
-
import java.util.regex.Matcher;
-
import java.util.regex.Pattern;
-
-
/**
-
* AOP配置解析。
-
* 1、解析PointCut正则匹配;
-
* 2、解析切面Aspect方法逻辑;
-
* 3、解析被代理类切入方法;
-
* 4、建立被代理类切入方法和切面方法的关系;
-
*/
-
public
class MyAdvisedSupport {
-
-
// 代理的目标类
-
private Class<?> targetClass;
-
// 代理的目标类的实例对象
-
private Object target;
-
-
// AOP配置信息
-
private MyAopConfig config;
-
// AOP切点匹配规则
-
private Pattern pointCutClassPattern;
-
-
// 享元的共享池,用于保存被代理类方法和通知方法对应关系
-
private
transient Map<Method, Map<String, MyAdvice>> methodCache;
-
-
public MyAdvisedSupport(MyAopConfig config) {
-
this.config = config;
-
}
-
-
public Class<?> getTargetClass() {
-
return
this.targetClass;
-
}
-
-
public Object getTarget() {
-
return
this.target;
-
}
-
-
/**
-
* 获取方法对应的AOP信息。
-
*
-
* @param method
-
* @param targetClass
-
* @return
-
* @throws NoSuchMethodException
-
*/
-
public Map<String, MyAdvice> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass)
-
throws NoSuchMethodException {
-
-
// 获取AOP方法
-
Map<String, MyAdvice> cache = methodCache.get(method);
-
if (
null == cache) {
-
// 目标对象方法
-
Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());
-
cache = methodCache.get(m);
-
-
// 对代理方法进行兼容处理 TODO 这里没太看懂
-
this.methodCache.put(m, cache);
-
}
-
return cache;
-
}
-
-
/**
-
* 设置被代理类的Class类型。
-
*
-
* @param targetClass
-
*/
-
public void setTargetClass(Class<?> targetClass) {
-
this.targetClass = targetClass;
-
-
// 解析AOP配置,设置AOP匹配逻辑。
-
this.parse();
-
}
-
-
//解析配置文件的方法
-
private void parse() {
-
-
// 1、把PointCut的Spring Excpress转换成Java正则表达式
-
String pointCut =
-
config.getPointCut().replaceAll(
"\\.",
"\\\\.").replaceAll(
"\\\\.\\*",
".*").replaceAll(
"\\(",
"\\\\(")
-
.replaceAll(
"\\)",
"\\\\)");
-
// 保存专门匹配Class的正则
-
String pointCutForClassRegex = pointCut.substring(
0, pointCut.lastIndexOf(
"\\("));
-
pointCutClassPattern =
-
Pattern.compile(
"class " + pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(
" ") +
1, pointCutForClassRegex.lastIndexOf(
"\\.")));
-
// 保存专门匹配方法的正则
-
Pattern pointCutPattern = Pattern.compile(pointCut);
-
-
// 2、享元的共享池,用于保存被代理类方法和通知方法对应关系。
-
methodCache =
new HashMap<Method, Map<String, MyAdvice>>();
-
-
try {
-
// 3、获取切面中定义的所有Method。
-
Class aspectClass = Class.forName(
this.config.getAspectClass());
-
Map<String, Method> aspectMethods =
new HashMap<String, Method>();
-
for (Method method : aspectClass.getMethods()) {
-
aspectMethods.put(method.getName(), method);
-
}
-
-
// 4、获取目标代理类的所有方法
-
for (Method method :
this.targetClass.getMethods()) {
-
// 获取方法名称
-
String methodString = method.toString();
-
if (methodString.contains(
"throws")) {
-
methodString = methodString.substring(
0, methodString.lastIndexOf(
"throws")).trim();
-
}
-
-
// 5、如果匹配切点规则,进行切面逻辑设定。
-
Matcher matcher = pointCutPattern.matcher(methodString);
-
if (matcher.matches()) {
-
Map<String, MyAdvice> advices =
new HashMap<String, MyAdvice>();
-
-
if (!(
null == config.getAspectBefore() ||
"".equals(config.getAspectBefore()))) {
-
advices.put(
"before",
-
new MyAdvice(aspectClass.newInstance(), aspectMethods.get(config.getAspectBefore())));
-
}
-
if (!(
null == config.getAspectAfter() ||
"".equals(config.getAspectAfter()))) {
-
advices.put(
"after",
-
new MyAdvice(aspectClass.newInstance(), aspectMethods.get(config.getAspectAfter())));
-
}
-
if (!(
null == config.getAspectAfterThrow() ||
"".equals(config.getAspectAfterThrow()))) {
-
MyAdvice advice =
-
new MyAdvice(aspectClass.newInstance(), aspectMethods.get(config.getAspectAfterThrow()));
-
advice.setThrowName(config.getAspectAfterThrowingName());
-
advices.put(
"afterThrow", advice);
-
}
-
-
// 6、保存目标代理类业务方法和环绕通知类的关系。
-
methodCache.put(method, advices);
-
}
-
}
-
}
catch (Exception e) {
-
e.printStackTrace();
-
}
-
-
}
-
-
//根据一个目标代理类的方法,获得其对应的通知
-
public Map<String, MyAdvice> getAdvices(Method method, Object o) throws Exception {
-
//享元设计模式的应用
-
Map<String, MyAdvice> cache = methodCache.get(method);
-
if (
null == cache) {
-
Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());
-
cache = methodCache.get(m);
-
this.methodCache.put(m, cache);
-
}
-
return cache;
-
}
-
-
/**
-
* 设置被代理类的实例化对象。
-
*
-
* @param target
-
*/
-
public void setTarget(Object target) {
-
this.target = target;
-
}
-
-
/**
-
* 代理的目标类是否匹配切点。
-
* 在ApplicationContext IoC中的对象初始化时调用,决定要不要生成代理类的逻辑。
-
*
-
* @return
-
*/
-
public boolean pointCutMatch() {
-
return pointCutClassPattern.matcher(
this.targetClass.toString()).matches();
-
}
-
}
Advice
Advice用于定义通知方法的结构,包含了切面的实例化对象和定义的通知方法。
-
package com.demo.spring.simulation.v5.aop.aspect;
-
-
import lombok.Data;
-
-
import java.lang.reflect.Method;
-
-
/**
-
* 通知定义接口,用于通知回调。
-
*/
-
@Data
public
class MyAdvice {
-
// 切面实例化对象
-
private Object aspect;
-
// AOP方法,非目标方法
-
private Method adviceMethod;
-
// 针对异常处理时,异常的名称
-
private String throwName;
-
-
public MyAdvice(Object aspect, Method adviceMethod) {
-
this.adviceMethod = adviceMethod;
-
this.aspect = aspect;
-
}
-
}
AopConfig
主要保存了定义的AOP配置,比较简单。
-
package com.demo.spring.simulation.v5.aop.config;
-
-
import lombok.Data;
-
-
/**
-
* 封装AOP的配置信息,包括切点、切面、切入环绕方法。
-
*/
-
@Data
-
public
class MyAopConfig {
-
// 切点
-
private String pointCut;
-
// 切面
-
private String aspectClass;
-
// before回调方法
-
private String aspectBefore;
-
// after回调方法
-
private String aspectAfter;
-
// 异常回调方法
-
private String aspectAfterThrow;
-
// 异常类型捕获
-
private String aspectAfterThrowingName;
-
}
ApplicationContext
上面的代码中,已经完成了AOP配置的获取,以及通过动态代理完成通知方法的执行。但是,我们需要把这段逻辑和IoC容器关联起来,以便能够在具体业务逻辑执行的时候自动完成AOP的调用。
其实逻辑非常简单,在IoC中我们介绍过,是通过反射将Bean的实例化对象放入IoC容器。但是如果是有AOP逻辑的Bean,只需要将AOP代理类实例化对象代替原始Bean对象,放入IoC容器即可。
所以需要再ApplicationContext加入这段逻辑,这部分是在IoC容器处理的时候加入的,我们只展示这段相关逻辑。
-
/**
-
* 创建真正的实例对象
-
*
-
* @param beanName
-
* @param beanDefinition
-
* @return
-
*/
-
private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) {
-
log.info(
"ApplicationContext -> 通过反射创建Bean实例。");
-
-
String className = beanDefinition.getBeanClassName();
-
Object instance =
null;
-
try {
-
Class<?> clazz = Class.forName(className);
-
instance = clazz.newInstance();
-
-
// AOP部分 -> Start
-
// 在初始化是确定是返回原生Bean实例还是Bean Proxy实例。
-
MyAdvisedSupport advisedSupport = instantionAopConfig();
-
advisedSupport.setTargetClass(clazz);
-
advisedSupport.setTarget(instance);
-
-
// 符合PointCut规则,进行代理
-
if (advisedSupport.pointCutMatch()) {
-
instance =
new MyJdkDynamicAopProxy(advisedSupport).getProxy();
-
}
-
// AOP部分 -> End
-
-
// 默认的类名首字母小写
-
this.factoryBeanObjectCache.put(beanName, instance);
-
}
catch (Exception e) {
-
e.printStackTrace();
-
}
-
return instance;
-
}
-
-
-
/**
-
* 获取AOP定义配置信息。
-
*
-
* @return
-
*/
-
private MyAdvisedSupport instantionAopConfig() {
-
log.info(
"ApplicationContext -> 加载AOP配置。");
-
-
// 从配置文件中获取AOP配置信息。
-
MyAopConfig config =
new MyAopConfig();
-
-
// AOP应该是配置在XML或者Annotation上的,这里为了模拟简单,直接定义在Properties文件中。
-
config.setPointCut(
this.reader.getConfig().getProperty(
"pointCut"));
-
config.setAspectClass(
this.reader.getConfig().getProperty(
"aspectClass"));
-
config.setAspectBefore(
this.reader.getConfig().getProperty(
"aspectBefore"));
-
config.setAspectAfter(
this.reader.getConfig().getProperty(
"aspectAfter"));
-
config.setAspectAfterThrow(
this.reader.getConfig().getProperty(
"aspectAfterThrow"));
-
config.setAspectAfterThrowingName(
this.reader.getConfig().getProperty(
"aspectAfterThrowingName"));
-
-
return
new MyAdvisedSupport(config);
-
}
Pt3.4 功能验证
-
定义AOP切面
-
package com.demo.spring.simulation.v5.test.aop;
-
-
/**
-
* 定义切面
-
*/
-
public
class LogAspect {
-
public void before() {
-
System.out.println(
"Invoke Before Method.");
-
}
-
-
public void after() {
-
System.out.println(
"Invoke After Method.");
-
}
-
-
public void afterThrowing() {
-
System.out.println(
"Invoke Exception Handler.");
-
}
-
}
-
定义切入业务方法
-
package com.demo.spring.simulation.v5.test.aop;
-
-
import com.demo.spring.simulation.v5.annotation.MyService;
-
import com.demo.spring.simulation.v5.aop.MyAopProxy;
-
import com.demo.spring.simulation.v5.servlet.MyModelAndView;
-
-
import java.util.HashMap;
-
import java.util.Map;
-
-
/**
-
* 业务类,执行AOP操作
-
*/
-
@MyService("aspectService")
-
public
class AspectServiceImpl implements MyAopProxy {
-
-
@Override
-
public void print() {
-
System.out.println(
"Invoke Business Method.");
-
-
Map<String, Object> model =
new HashMap<String, Object>();
-
model.put(
"message",
"hello ");
-
new MyModelAndView(
"hello", model);
-
}
-
}
-
定义测试类
-
package com.demo.spring.simulation.v5.test.aop;
-
-
import com.demo.spring.simulation.v5.aop.MyAopProxy;
-
import com.demo.spring.simulation.v5.context.MyApplicationContext;
-
-
/**
-
* Spring MVC测试。
-
*/
-
public
class MyAspectTest {
-
-
public static void main(String[] args) {
-
MyApplicationContext applicationContext =
new MyApplicationContext(
"classpath:application.properties");
-
MyAopProxy aspectService = (MyAopProxy)applicationContext.getBean(AspectServiceImpl.class);
-
aspectService.print();
-
}
-
}
-
运行测试类,查看输出结果
-
Invoke Before Method.
-
Invoke Business Method.
-
Invoke After Method.
参考学习资料和相关文章列表,请参照如下链接:
https://blog.csdn.net/moonlight821/article/details/116463513
转载:https://blog.csdn.net/cmm13655612162/article/details/116504785