目录
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