相关文章:
Spring5 - 30个类手写实战 - 打卡第二天(IOC与DI)
Spring5 - 30个类手写实战 - 打卡第三天(MVC)
Spring5 - 30个类手写实战 - 打卡第四天(AOP)
Spring5 - 30个类手写实战 - 打卡第五天(手绘IOC时序图)
1.实现基本思路
2.注解类的实现
2.1 LAutowired
package com.liulin.spring.framework.annotation;
import java.lang.annotation.*;
/**
* Create by DbL on 2020/4/29 0029
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LAutowired {
String value() default "";
}
2.2 LController
package com.liulin.spring.framework.annotation;
import java.lang.annotation.*;
/**
* Create by DbL on 2020/4/29 0029
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LController {
String value() default "";
}
2.3 LRequestMapping
package com.liulin.spring.framework.annotation;
import java.lang.annotation.*;
/**
* Create by DbL on 2020/4/29 0029
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LRequestMapping {
String value() default "";
}
2.4 LRequestParam
package com.liulin.spring.framework.annotation;
import java.lang.annotation.*;
/**
* Create by DbL on 2020/4/29 0029
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LRequestParam {
String value() default "";
}
2.5 LService
package com.liulin.spring.framework.annotation;
import java.lang.annotation.*;
/**
* Create by DbL on 2020/4/29 0029
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LService {
String value() default "";
}
3.pom.xml 引入需要用到的jar
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
4.web.xml配置
为了方便把需要扫描的包配置在application.properties
文件中,servlet-class配置为自己写的LDispatcherServlet
<?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_4_0.xsd"
version="4.0">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>lmvc</servlet-name>
<servlet-class>com.liulin.spring.framework.servlet.LDispatcherServlet</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>lmvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
5. application.properties文件
里面很简单,只配置了需要扫描的包的路径scanPackage=com.liulin.demo
6.扫描的demo包,用于测试
MyAction :
package com.liulin.demo.action;
import com.liulin.demo.service.IModifyService;
import com.liulin.demo.service.IQueryService;
import com.liulin.spring.framework.annotation.LAutowired;
import com.liulin.spring.framework.annotation.LController;
import com.liulin.spring.framework.annotation.LRequestMapping;
import com.liulin.spring.framework.annotation.LRequestParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Create by DbL on 2020/4/29 0029
*/
@LController
@LRequestMapping("/web")
public class MyAction {
@LAutowired
IQueryService queryService;
@LAutowired
IModifyService modifyService;
@LRequestMapping("/query.json")
public void query(HttpServletRequest request, HttpServletResponse response,
@LRequestParam("name") String name) {
String result = queryService.query(name);
out(response, result);
}
@LRequestMapping("/add*.json")
public void add(HttpServletRequest request, HttpServletResponse response,
@LRequestParam("name") String name, @LRequestParam("addr") String addr) {
String result = modifyService.add(name, addr);
out(response, result);
}
@LRequestMapping("/remove.json")
public void remove(HttpServletRequest request, HttpServletResponse response,
@LRequestParam("id") Integer id) {
String result = modifyService.remove(id);
out(response, result);
}
@LRequestMapping("/edit.json")
public void edit(HttpServletRequest request, HttpServletResponse response,
@LRequestParam("id") Integer id,
@LRequestParam("name") String name) {
String result = modifyService.edit(id, name);
out(response, result);
}
private void out(HttpServletResponse resp, String str) {
try {
resp.getWriter().write(str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
IModifyService :
package com.liulin.demo.service;
/**
* Create by DbL on 2020/4/29 0029
*/
public interface IModifyService {
/**
* 增加
*/
public String add(String name, String addr);
/**
* 修改
*/
public String edit(Integer id, String name);
/**
* 删除
*/
public String remove(Integer id);
}
ModifyServiceImpl :
package com.liulin.demo.service.impl;
import com.liulin.demo.service.IModifyService;
import com.liulin.spring.framework.annotation.LService;
/**
* Create by DbL on 2020/4/29 0029
*/
@LService
public class ModifyServiceImpl implements IModifyService {
/**
* 增加
*/
public String add(String name,String addr) {
return "modifyService add,name=" + name + ",addr=" + addr;
}
/**
* 修改
*/
public String edit(Integer id,String name) {
return "modifyService edit,id=" + id + ",name=" + name;
}
/**
* 删除
*/
public String remove(Integer id) {
return "modifyService id=" + id;
}
}
IQueryService :
package com.liulin.demo.service;
/**
* Create by DbL on 2020/4/29 0029
*/
public interface IQueryService {
/**
* 查询
*/
public String query(String name);
}
QueryServiceImpl :
package com.liulin.demo.service.impl;
import com.liulin.spring.framework.annotation.LService;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Create by DbL on 2020/4/29 0029
*/
@LService
public class QueryServiceImpl implements IQueryService{
/**
* 查询
*/
public String query(String name) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sdf.format(new Date());
String json = "{name:\"" + name + "\",time:\"" + time + "\"}";
System.out.println("这是在业务方法中打印的:" + json);
return json;
}
}
7.LDispatcherServlet (V1版本)
后面再对各个功能进行拆分。
package com.liulin.spring.framework.servlet;
import com.liulin.spring.framework.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.awt.print.PrinterAbortException;
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.Method;
import java.net.URL;
import java.util.*;
/**
* Create by DbL on 2020/4/29 0029
*/
public class LDispatcherServlet extends HttpServlet {
private Properties contextConfig = new Properties();
// 享元模式 , 缓存
private List<String> classNames = new ArrayList<String>();
// IOC容器,key默认是类名首字母小写,value是对应的实例对象
private Map<String, Object> ioc = new HashMap<String, Object>();
private Map<String, Method> handlerMapping = new HashMap<String, Method>();
@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 {
// 6.委派,根据URL去找到一个对应的Method并通过response返回
try {
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write("500 Exception , Detail : " + Arrays.toString(e.getStackTrace()));
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
if (!this.handlerMapping.containsKey(url)) {
resp.getWriter().write("404 Not Found !!!");
return;
}
Map<String, String[]> params = req.getParameterMap();
Method method = this.handlerMapping.get(url);
// 获取形参列表
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] paramValues = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
Class paramterType = parameterTypes[i];
if (paramterType == HttpServletRequest.class) {
paramValues[i] = req;
} else if (paramterType == HttpServletResponse.class) {
paramValues[i] = resp;
} else if (paramterType == String.class) {
// 通过运行时的状态去拿到注解的值
Annotation[][] pa = method.getParameterAnnotations();
for (int j = 0; j < pa.length; j++) {
for (Annotation a : pa[j]) {
if (a instanceof LRequestParam) {
String paramName = ((LRequestParam) a).value();
if (!"".equals(paramName.trim())) {
String value = Arrays.toString(params.get(paramName))
.replaceAll("\\[|\\]", "")
.replaceAll("\\s+", "");
paramValues[i] = value;
}
}
}
}
/* LRequestParam requestParam = (LRequestParam) paramterType.getAnnotation(LRequestParam.class);
String paramName = requestParam.value();
String value = Arrays.toString(params.get(paramName))
.replaceAll("\\[|\\]", "")
.replaceAll("\\s+","");
paramValues[i] = value;*/
}
}
// 暂时写死,用于测试
String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
method.invoke(ioc.get(beanName), new Object[]{req, resp, params.get("name")[0]});
}
@Override
public void init(ServletConfig config) throws ServletException {
// 1.加载配置文件 contextConfigLocation对应web.xml中的init-param ==》 param-name
doLoadConfig(config.getInitParameter("contextConfigLocation"));
// 2.扫描相关的类
doScanner(contextConfig.getProperty("scanPackage"));
//=================IOC部分=============//
// 3.初始化IOC容器,将扫描到的相关的类实例化,保存到IOC容器中
doInstance();
// AOP,DI之前,新生成的代理对象
//================DI部分==============//
// 4.完成依赖注入
doAutoWired();
//================MVC部分==============//
// 5.初始化HandlerMapping
doInitHandlerMapping();
System.out.println("L Spring framework init success ");
}
private void doInitHandlerMapping() {
if (ioc.isEmpty()) {
return;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
// 类没有加注解的跳过
if (!clazz.isAnnotationPresent(LController.class)) {
continue;
}
// 如果类上定义了路径,方法上的路径需要拼接上此路径
String baseUrl = "";
if (clazz.isAnnotationPresent(LRequestMapping.class)) {
LRequestMapping Mapping = clazz.getAnnotation(LRequestMapping.class);
baseUrl = Mapping.value();
}
// 只获取public的方法
for (Method method : clazz.getMethods()) {
// 方法没有加注解的跳过
if (!method.isAnnotationPresent(LRequestMapping.class)) {
continue;
}
LRequestMapping requestMapping = method.getAnnotation(LRequestMapping.class);
// 对于配置了“/” 和没有配置“/”的通过正则表达式统一处理
String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
handlerMapping.put(url, method);
System.out.println("mapped : " + url + " , " + method);
}
}
}
private void doAutoWired() {
if (ioc.isEmpty()) {
return;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
// 获取private/protected/default/public修饰的所有字段
for (Field field : entry.getValue().getClass().getDeclaredFields()) {
if (!field.isAnnotationPresent(LAutowired.class)) {
return;
}
LAutowired autowired = field.getAnnotation(LAutowired.class);
// 如果用户没有自定义的beanName,就默认根据类型注入
String beanName = autowired.value().trim();
if ("".equals(beanName)) {
// 接口全名
beanName = field.getType().getName();
}
// 对于类中的private属性的成员进行暴力访问
field.setAccessible(true);
try {
// ioc.get(beanName) 相当于通过接口的全名从IOC中拿到接口的实现的实例
field.set(entry.getValue(), ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
private void doInstance() {
if (classNames.isEmpty()) {
return;
}
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
// 加了LController或者LService注解的才加到IOC容器,因为像接口类不能new
if (clazz.isAnnotationPresent(LController.class)) {
// 类名首字母小写作为key
String beanName = toLowerFirstCase(clazz.getSimpleName());
Object instance = clazz.newInstance();
ioc.put(beanName, instance);
} else if (clazz.isAnnotationPresent(LService.class)) {
// 1.在多个包下出现相同的类名,只能自己起一个全局唯一的名字
// 自定义名字
String beanName = clazz.getAnnotation(LService.class).value();
// 如果设置了名字就用设置的,没有就用默认的
if ("".equals(beanName.trim())) {
beanName = toLowerFirstCase(clazz.getSimpleName());
}
// 2. 默认的类名首字母小写
Object instance = clazz.newInstance();
ioc.put(beanName, instance);
// 3.如果是接口
// 判断有多少个实现类,如果只有一个,默认选择这个实现类,
// 如果有多个,抛异常
for (Class<?> i : clazz.getInterfaces()) {
if (ioc.containsKey(i.getName())) {
throw new Exception("The " + i.getName() + " is exists !!! ");
}
ioc.put(i.getName(), instance);
}
} else {
continue;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 首字母小写
*
* @param simpleName
* @return
*/
private String toLowerFirstCase(String simpleName) {
char[] chars = simpleName.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
private void doScanner(String scanPackage) {
// com.liulin.demo 需要转换成文件夹的路径形式便于扫描路径下的文件
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 {
// 非class文件跳过
if (!file.getName().endsWith(".class")) continue;
String className = file.getName().replace(".class", "");
// Class.forName(className)
// 防止名字重复,这里使用包名加上类名
classNames.add(scanPackage + "." + className);
}
}
}
private void doLoadConfig(String contextConfigLocation) {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
contextConfig.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != resourceAsStream) {
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}