一 目标
1,了解看源代码最有效的方式,先猜测后验证,不要一开始就去调试代码
2,用300行最简洁的代码提炼Spring的基础设计思想
3,结合设计模式,掌握Spring框架的基本脉络
二 Spring如何下手,从哪里开始看?
Spring的如何开始的,我们先从原理来了解一下。
Spring主要有3个阶段:
第一阶段:配置阶段
在web.xml中设定DispatchServle,设置Spring-*.xml相关的配置信息或者直接设置*.properties文件,包含@Controller @Service @Autowrited @RequestMapping 等等
第二阶段:初始化阶段
1,调用init()方法,加载配置文件
2,IOC容器初始化,根据spring-*.xml或者*.properties中的配置扫描相关的类。
3,根据扫描的类,创建实例化类并保存到IOC容器中,主要是通过反射机制将类实例化放入IOC容器中
4,进行DI操作,即依赖注入,通过扫描IOC容器中的实例,给没有赋值的属性自动赋值
5,初始化HandelerMappping,就是将URL和Method进行一对一的关联映射到Map<String,Object>中
第三个阶段:运行阶段
1,掉用doPost()/doGet()请求,获取request/response。
2,匹配handlerMapping,从request对象中获得用户输入的url,并从hangdlerMapping中找到对应的Method方法。
3,反射调用method.invoker(),返回结果
4,response.getWrite().wirte(),将结果返回给浏览器
三 代码实现
第一步我们要新建一个maven web工程。
这个不会建的请移步度娘,自己去百度。
我的项目结构如下图:
第二步,新建我们自己的相关的注解以及配置web.xml,pom.xml以及application.properties文件
ChService注解代码如下:
package com.ch.framework.annotation;
import java.lang.annotation.*;
/**
* Created by lijianfang on 2021/10/25.
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChService {
String value() default "";
}
ChRequestMapping注解代码如下:
package com.ch.framework.annotation;
import java.lang.annotation.*;
/**
* Created by lijianfang on 2021/10/25.
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChRequestMapping {
String name() default "";
@ChAliasFor("path")
String[] value() default {};
@ChAliasFor("value")
String[] path() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
ChController注解代码如下:
package com.ch.framework.annotation;
import java.lang.annotation.*;
/**
* Created by lijianfang on 2021/10/25.
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChController {
String value() default "";
}
ChAutowired注解代码如下:
package com.ch.framework.annotation;
import java.lang.annotation.*;
/**
* Created by lijianfang on 2021/10/25.
*/
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChAutowired {
boolean required() default true;
String value() default "";
}
ChAliasFor注解代码如下:
package com.ch.framework.annotation;
import java.lang.annotation.*;
/**
* Created by lijianfang on 2021/10/25.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface ChAliasFor {
// @ChAliasFor("attribute");
String value() default "";
// @ChAutowired("value");
String attribute() default "";
Class<? extends Annotation> annotation() default Annotation.class;
}
pom.xml文件中需要配置编译相关的组件,pom.xml具体如下:
<?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>com.ch.framework</groupId>
<artifactId>springTest</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>springTest 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.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>bean-validator</artifactId>
<version>3.0-JBoss-4.0.2</version>
</dependency>
</dependencies>
<build>
<finalName>springTest</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>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<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>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>resources</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.jpg</include>
<include>**/*.ttf</include>
<include>**/*.xml</include>
</includes>
<excludes>
<exclude>**/*.yml</exclude>
</excludes>
</resource>
</resources>
</build>
</project>
web.xml中设置我们自己定义的dispatchServlet类,详情如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<display-name>springTest</display-name>
<servlet>
<servlet-name>znnmvc</servlet-name>
<servlet-class>com.ch.framework.dispatcher.servlet.ChDispatchServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>znnmvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
application.properties文件中定义需要扫描的类包名。
scanPackage=com.ch.framework.demo
第三步,开发我们自己的dispatchSetvlet类,我新建的是ChDispatchServlet.java
具体代码如下:
package com.ch.framework.dispatcher.servlet;
import com.ch.framework.annotation.ChAutowired;
import com.ch.framework.annotation.ChController;
import com.ch.framework.annotation.ChRequestMapping;
import com.ch.framework.annotation.ChService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
/**
* 手写自己的servlet
* Created by lijianfang on 2021/10/25.
*/
public class ChDispatchServlet extends HttpServlet{
private static final Logger logger = LoggerFactory.getLogger(ChDispatchServlet.class);
private Properties contextConfig = new Properties();
private List<String> classNames = new ArrayList<String>();
private Map<String, Object> ioc = new HashMap<String, Object>();
private Map<String, Object> handlerMapping = new HashMap<String, Object>();
@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 {
try{
//6,调用
doDispatch(req,resp);
}catch (Exception e){
logger.error("调用报错,错误信息",e);
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
if(this.handlerMapping.isEmpty()){
return;
}
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replaceAll(contextPath, "").replaceAll("/", "/");
//用户访问的路径是不是在hangdlerMapping中
if(!this.handlerMapping.containsKey(url)){
resp.getWriter().write("没有找到相关的路径,请确认路径是否正确!");
return;
}
Method method = (Method) this.handlerMapping.get(url);
//获取方法的参数列表
Class<?>[] paramterTypes = method.getParameterTypes();
//获取请求的参数
Map<String,String[]> parameterMap = req.getParameterMap();
Object [] parameValues = new Object[paramterTypes.length];
//方法的参数列表
for(int i = 0;i<paramterTypes.length;i++){
Class paramterType = paramterTypes[i];
if(paramterType==HttpServletRequest.class){
parameValues[i] = req;
continue;
}else if(paramterType==HttpServletResponse.class){
parameValues[i] = resp;
continue;
}else if(paramterType==String.class){
for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
System.out.println("param.getValue()==="+param.getValue()[0]);
String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]","").
replaceAll(",\\s",",");
parameValues[i] = value;
i++;
}
}
}
String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
//反射调用方法
method.invoke(this.ioc.get(beanName), parameValues);
}
/**
* 初始化相关参数
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
//1、加载配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));
//2、扫描相关的类
doScanner(contextConfig.getProperty("scanPackage"));
//3、初始化扫描的类,并将他们放到IOC容器中
doInstance();
//4、完成依赖注入
doAutowired();
//5、初始化handerMapping
initHandlerMapping();
//6、调用
}
/**
* 初始化url跟method的一一对应关系
*/
private void initHandlerMapping() {
if (ioc.isEmpty()) {
return;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
//当前类的注解是不是controller,不是直接跳过
if (!clazz.isAnnotationPresent(ChController.class)) {
continue;
}
String baseUrl = "";
//是controller注解,获取注解上的路径名称
if (clazz.isAnnotationPresent(ChRequestMapping.class)) {
ChRequestMapping requestMapping = clazz.getAnnotation(ChRequestMapping.class);
baseUrl = requestMapping.value()[0];
}
// 默认获取所有的public,获取class类的方法(公共方法)
for (Method method : clazz.getMethods()) {
//判断方法上的注解是不是requestMap注解,不是就直接跳过
if (!method.isAnnotationPresent(ChRequestMapping.class)) {
continue;
}
//获取注解上的路径
ChRequestMapping requestMapping = method.getAnnotation(ChRequestMapping.class);
//合成最终访问的路径
String url = ("/"+baseUrl + "/" + requestMapping.value()[0]).replaceAll("/", "/");
// 将访问的路径放到handlerMapping中
handlerMapping.put(url, method);
logger.info("Mapped" + url + method);
}
}
}
/**
* 完成依赖注入
*/
private void doAutowired() {
if(ioc.isEmpty()){
return;
}
//循环ioc中的类,给对应的类中的依赖的属性注入相应的bean
for(Map.Entry<String,Object> entry:ioc.entrySet()){
//获取对应类的私有属性
Field[] fields = entry.getValue().getClass().getDeclaredFields();
//循环私有属性,看是否是autowired注解
for(Field field:fields){
//看是不是service注解
if(field.isAnnotationPresent(ChService.class)){
continue;
}
//查看是不是依赖注入的注解
ChAutowired autowired = field.getAnnotation(ChAutowired.class);
//获取对应注解的名字,先查看是否注解上是不是有写了默认的值,没有值时,使用类型注入
String beanName = autowired.value().trim();
if("".equals(beanName)){
beanName=field.getType().getName();
}
//设置注解类的可用属性
field.setAccessible(true);
try {
field.set(entry.getValue(),ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 初始化扫描的类
*/
private void doInstance(){
if(classNames.isEmpty()){
return;
}
try {
for(String className:classNames){
Class<?> clazz = Class.forName(className);
// 什么样的类才需要初始化,添加了注解的类才初始化
//controller,service
if (clazz.isAnnotationPresent(ChController.class)){
Object instance = clazz.newInstance();
String beanName = toLowerFirstCase(clazz.getSimpleName());
ioc.put(beanName, instance);
}else if(clazz.isAnnotationPresent(ChService.class)){
ChService service = clazz.getAnnotation(ChService.class);
// 1、默认首字母小写
String beanName = service.value();
// 2、自定义bean名字
if ("".equals(beanName.trim())) {
beanName = toLowerFirstCase(clazz.getSimpleName());
}
Object instance = clazz.newInstance();
ioc.put(beanName, instance);
// 3、根据类自动赋值
for (Class<?> i : clazz.getInterfaces()) {
if (ioc.containsKey(i.getName())) {
try {
throw new Exception("这个" + i.getName() + " 已经存在!");
} catch (Exception e) {
logger.error("这个" + i.getName() + " 已经存在!");
}
}
ioc.put(i.getName(), instance);
}
}else {
continue;
}
}
} catch (ClassNotFoundException e) {
logger.error("报错信息:",e);
} catch (InstantiationException e) {
logger.error("报错信息:",e);
} catch (IllegalAccessException e) {
logger.error("报错信息:",e);
}
}
/**
* <p>
* 首字母小写
* </p>
*
* @param simpleName
* @return
*/
private String toLowerFirstCase(String simpleName) {
char[] chars = simpleName.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
/**
* <p>
* 扫描配置文件配置的类路径
* </p>
*
* @param scanPackage
*/
private void doScanner(String scanPackage) {
URL url = this.getClass().getClassLoader().getResource(""+scanPackage.replaceAll("\\.","/"));
File classPath = new File(url.getFile());
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", ""));
classNames.add(className);
}
}
}
/**
* <p>
* 加载配置文件,直接从类路径下找到spring主配置文件夹所在的路径并且将其读取出来放到Properties对象中<br/>
* 将application.properties文件中的scanPackage=com.ch.framework.demo 放到properties对象中
* </p>
* @param contextConfigLocation
*/
private void doLoadConfig(String contextConfigLocation){
InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try{
contextConfig.load(fis);
}catch (Exception e){
logger.error("加载配置失败,错误原因:", e);
}finally{
if(null!=fis){
try {
fis.close();
} catch (IOException e) {
logger.error("关闭流失败,错误原因:", e);
}
}
}
}
}
第四步,编写测试类
我新建了一个demo包,
TestController.java文件代码如下:
package com.ch.framework.demo.controller;
import com.ch.framework.annotation.ChAutowired;
import com.ch.framework.annotation.ChController;
import com.ch.framework.annotation.ChRequestMapping;
import com.ch.framework.demo.service.DomeService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by lijianfang on 2021/11/15.
*/
@ChController
@ChRequestMapping("test")
public class TestController {
@ChAutowired
private DomeService domeService;
@ChRequestMapping("getData")
public void getData(HttpServletRequest request, HttpServletResponse response){
String name = request.getParameter("name");
System.out.println("name=="+name);
try {
String result = domeService.getData(name);
response.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
@ChRequestMapping("addData")
public void addData(HttpServletRequest request, HttpServletResponse response){
int a = Integer.parseInt(request.getParameter("a"));
int b = Integer.parseInt(request.getParameter("b"));
System.out.println("name=="+a);
try {
int result = domeService.addData(a,b);
response.getWriter().write("result:a+b="+result);
} catch (IOException e) {
e.printStackTrace();
}
}
}
DomeService.java文件代码如下:
package com.ch.framework.demo.service;
/**
* Created by lijianfang on 2021/11/15.
*/
public interface DomeService {
public String getData(String name);
public int addData(int a,int b);
}
DomeServiceImpl.java文件代码如下:
package com.ch.framework.demo.service.impl;
import com.ch.framework.annotation.ChService;
import com.ch.framework.demo.service.DomeService;
/**
* Created by lijianfang on 2021/11/15.
*/
@ChService
public class DomeServiceImpl implements DomeService {
@Override
public String getData(String name) {
return "My name is "+name;
}
@Override
public int addData(int a, int b) {
return a+b;
}
}
第五步,测试结果