300行代码Spring1.0版本初体验
Spring主要功能
IoC,DI,MVC,AOP,JDBC…
话不多说,直接上码
package com.kxjiang.spring.servlet;
import cn.hutool.core.util.StrUtil;
import com.kxjiang.spring.annotation.*;
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.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
/**
* @author kxjiang
* 时间: 2021/11/4
* 描述: dispatcherServlet的模仿类
*/
public class JDispatchServlet extends HttpServlet {
/**
* 保存用户配置文件
*/
private final Properties contextConfig = new Properties();
/**
* 保存从包路径下扫描的全类名
*/
private final List<String> classNames = new ArrayList<String>();
/**
* 保存所有的扫描的类的实例
*/
private final Map<String,Object> ioc = new HashMap<String,Object>();
/**
* 保存所有的url与method的对应关系
*/
private final Map<String,Method> handlerMapping = new HashMap<String, Method>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 根据URL委派给具体的调用方法
doDispatch(req,resp);
}
/**
* 继承HttpServlet类,会在初始化时调用此方法
* @param config 配置参数
* @throws ServletException ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
// 加载配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));
// 扫描相关的类
doScanner(contextConfig.getProperty("packageScan"));
// IoC功能,初始化IoC容器
doInstance();
// DI功能,完成依赖注入
doAutowired();
// MVC功能,初始化HandlerMapping
doInitHandlerMapping();
System.out.println("kxjiang spring framework is init.");
}
/**
* 根据contextConfigLocation的路径去ClassPath下找到对应的配置文件
* @param contextConfigLocation contextConfigLocation
*/
private void doLoadConfig(String contextConfigLocation){
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
contextConfig.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(resourceAsStream!=null){
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 初始化访问路径与url之间的关系
*/
private void doInitHandlerMapping() {
if(!ioc.isEmpty()){
for (Map.Entry<String,Object> entry:ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
// 类上的url
String baseUrl = "";
if(clazz.isAnnotationPresent(KXRequestMapping.class)){
baseUrl = "/"+clazz.getAnnotation(KXRequestMapping.class).value();
}
// 获取所有的方法,进行处理
for(Method method:clazz.getMethods()){
if(!method.isAnnotationPresent(KXRequestMapping.class)){continue;}
KXRequestMapping kxRequestMapping = method.getAnnotation(KXRequestMapping.class);
// 拼接url,并保证url的规则性,如果出现多个//只能保留一个/
String url = ("/" + baseUrl + "/" + kxRequestMapping.value()).replaceAll("/+","/");
// 如果获取的注解配置的url不为空,设置上注解中的url与方法的对应关系
if(StrUtil.isNotBlank(kxRequestMapping.value())){
handlerMapping.put(url,method);
}
System.out.println("init url : "+url);
}
}
}
}
/**
* 根据访问的url调用具体的方法,实现请求的访问
* @param req 请求
* @param rsp 相应
* @throws IOException 抛出异常
*/
private void doDispatch(HttpServletRequest req,HttpServletResponse rsp) throws IOException {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replaceAll(contextPath,"").replaceAll("/+","/");
if(!this.handlerMapping.containsKey(url)){
rsp.getWriter().write("404 Not Found!!!!");
return;
}
Method method = this.handlerMapping.get(url);
// 1.先把形参的位置和参数名字建立映射关系,并且缓存下来
Map<String,Integer> paramIndexMapping = new HashMap<String, Integer>(8);
// 每个方法上可以有多个参数,每个参数也可以又多个注解,所以是个二维数组
Annotation[][] pa = method.getParameterAnnotations();
for (int i = 0; i < pa.length; i++) {
for (Annotation a : pa[i]){
// 判断注解类型
if(a instanceof KXRequestParam){
String paramName = ((KXRequestParam)a).value();
if(StrUtil.isNotBlank(paramName.trim())){
// 记录加上了@KXRequestParam注解的参数位置
paramIndexMapping.put(paramName,i);
}
}
}
}
// 判断参数类型是否为HttpServletRequest和HttpServletResponse,将他们的位置也缓存到容器中
Class<?> [] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
Class<?> type = paramTypes[i];
if(type == HttpServletRequest.class || type == HttpServletResponse.class ){
paramIndexMapping.put(type.getName(),i);
}
}
// 2.根据参数位置匹配参数名字,从url中取到参数名字对应的值
Object[] paramValues = new Object[paramTypes.length];
String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
Map<String,String[]> params = req.getParameterMap();
for (Map.Entry<String,String[]> param:params.entrySet()){
String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]","").replaceAll("\\s","");
// 如果在后台方法中的参数缓存map中,没有与前端传过来的参数名相同的参数名,则不处理
if(!paramIndexMapping.containsKey(param.getKey())){continue;}
// 记录参数位置
int index = paramIndexMapping.get(param.getKey());
// 给参数赋值
paramValues[index] = value;
}
if(paramIndexMapping.containsKey(HttpServletRequest.class.getName())){
int index = paramIndexMapping.get(HttpServletRequest.class.getName());
paramValues[index] = req;
}
if(paramIndexMapping.containsKey(HttpServletResponse.class.getName())){
int index = paramIndexMapping.get(HttpServletResponse.class.getName());
paramValues[index] = rsp;
}
// 3.组成动态实际参数列表,传给反射调用
try {
method.invoke(ioc.get(beanName),paramValues);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* 初始化依赖注入过程
*/
private void doAutowired() {
if(!ioc.isEmpty()){
for (Map.Entry<String,Object> entry:ioc.entrySet()) {
//忽略字段的修饰符,不管是private,protected public default
for(Field field:entry.getValue().getClass().getDeclaredFields()){
// 如果字段上未加KXAutowired注解,跳过循环不处理
if(!field.isAnnotationPresent(KXAutowired.class)){continue;}
// 获取注解中的一些属性-bean名称
KXAutowired kxAutowired = field.getAnnotation(KXAutowired.class);
String beanName = kxAutowired.value().trim();
// 如果未给注解的value属性写值,则使用类型作为ioc容器中的key
if(StrUtil.isBlank(beanName)){
beanName = field.getType().getName();
}
// 设置属性的强制访问,无论是不是private
field.setAccessible(true);
try {
// 给依赖注入的属性复制
field.set(entry.getValue(),ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 初始化ioc容器中的所有bean
*/
private void doInstance() {
if(classNames.isEmpty()){return;}
try{
for (String className: classNames) {
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(KXController.class)){
// 获得首字母小写的类名
String beanName = toLowerFirstCase(clazz.getSimpleName());
// 利用反射加载类实例
Object instance = clazz.newInstance();
// 加入ioc容器
ioc.put(beanName,instance);
}else if(clazz.isAnnotationPresent(KXService.class)){
// 默认首字母小写的类名作为ioc容器中的key
String beanName = toLowerFirstCase(clazz.getSimpleName());
// 如果给类起了别名,以别名优先
KXService service = clazz.getAnnotation(KXService.class);
if(StrUtil.isBlank(service.value())){
beanName = service.value();
}
Object instance = clazz.newInstance();
ioc.put(beanName,instance);
// 如果是接口,则只能初始化他的实现类
for(Class<?> i : clazz.getInterfaces()){
// 如果存在多个实现类
if(ioc.containsKey(i.getName())){
throw new RuntimeException("The "+i.getName() + " is exists.");
}
ioc.put(i.getName(),instance);
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 扫描到classPath下的符合包路径规则的所有的类文件
* @param packageScan 扫包路径
*/
private void doScanner(String packageScan) {
URL url = this.getClass().getClassLoader().getResource("/" + packageScan.replaceAll("\\.", "/"));
if(url!=null){
File classPath = new File(url.getFile());
for (File file:classPath.listFiles()) {
// 如果文件是文件名,递归调用
if(file.isDirectory()){
doScanner(packageScan+"."+file.getName());
}else{
// 如果文件名不是class文件结尾的,跳过本次循环,不加载
if(!file.getName().endsWith(".class")){continue;}
// 包名.类名 全类名 可以通过反射用来实例化该类
String className = packageScan+"."+file.getName().replace(".class","");
// 缓存到容器中,最后统一初始化加载
classNames.add(className);
}
}
}
}
/**
* 将类名首字母大写改为小写
* @param className 类名
* @return 返回转换后的名字
*/
private String toLowerFirstCase(String className){
char[] chars = className.toCharArray();
//ASCII码,大写字母与小写字符相差32
chars[0] +=32;
return String.valueOf(chars);
}
}
配置文件内容:扫包路径
packageScan=com.kxjiang.spring