1. 首先创建注解类
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
String value() default "";
}
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
String value() default "";
}
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
String value() default "";
}
@Documented
@Target({
ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
String value() default "";
}
2. 创建MyDispatcherServlet.java类继承HttpServlet
private static final Properties PROPERTIES = new Properties();
/**
* 缓存扫描到的类的全限定类名
*/
private static final List<String> CLASS_NAME_LIST = new ArrayList<>();
/**
* IOC 容器
*/
private static final Map<String, Object> IOC = new HashMap<>(16);
private static final List<Handler> HANDLER_MAPPING = new ArrayList<>();
@Override
public void init(ServletConfig config) {
// 1 加载配置文件
String contextConfigLocation = config.getInitParameter("contextConfigLocation");
doLoadConfig(contextConfigLocation);
// 2 扫描相关的类,扫描注解
doScan(PROPERTIES.getProperty("scanPackage"));
// 3 初始化bean对象(实现ioc容器,基于注解)
doInstance();
// 4 实现依赖注入
doAutoWired();
// 5 构造一个HandleMapping处理器映射器,建立映射关系
initHandlerMapping();
System.out.println("mvc 初始化完成");
// 等待请求进入,处理请求
}
/**
* 首字母小写
*/
private String lowerFirst(String simpleName) {
if(isNotEmpty(simpleName)) {
char[] chars = simpleName.toCharArray();
if('A' <= chars[0] && 'Z' >= chars[0]) {
chars[0] += 32;
}
simpleName = String.valueOf(chars);
}
return simpleName;
}
private boolean isNotEmpty(String s) {
boolean flag = false;
if(s != null && !"".equals(s.trim())) {
flag = true;
}
return flag;
}
这里Handler实体用于储存解析出来的信息,在最后requestMapping里会用到
实体如下:
/**
* 封装handler方法相关的信息
*/
public class Handler {
private Object controller; // method.invoke(obj,)
private Method method;
private Pattern pattern; // spring中url是支持正则的
private Map<String,Integer> paramIndexMapping; // 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 <name,2>
public Handler(Object controller, Method method, Pattern pattern) {
this.controller = controller;
this.method = method;
this.pattern = pattern;
this.paramIndexMapping = new HashMap<>();
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Pattern getPattern() {
return pattern;
}
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
public Map<String, Integer> getParamIndexMapping() {
return paramIndexMapping;
}
public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
this.paramIndexMapping = paramIndexMapping;
}
}
一共分为5步
- 加载配置文件
- 扫描相关的类,扫描注解
- 初始化bean对象(实现ioc容器,基于注解)
- 实现依赖注入
- 构造一个HandleMapping处理器映射器,建立映射关系
1到4步和手写Spring自定义注解类的思路一样 代码入下:
- 加载配置文件
/**
* 加载配置文件
* @param contextConfigLocation 路径
*/
private void doLoadConfig(String contextConfigLocation) {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
PROPERTIES.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
}
- 扫描相关的类,扫描注解
/**
* 扫描类
* scanPackage: com.lossdate.demo package----> 磁盘上的文件夹(File) com/lossdate/demo
*/
private void doScan(String scanPackage) {
String path = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("")).getPath() + scanPackage.replaceAll("\\.", "/");
File packages = new File(path);
File[] files = packages.listFiles();
for (File file : Objects.requireNonNull(files)) {
if(file.isDirectory()) {
// com.lossdate.demo.controller
doScan(scanPackage + "." + file.getName());
} else if(file.getName().endsWith(".class")) {
CLASS_NAME_LIST.add(scanPackage + "." + file.getName().replaceAll(".class", ""));
}
}
}
- 初始化bean对象(实现ioc容器,基于注解)
/**
* IOC容器
*/
private void doInstance() {
if(CLASS_NAME_LIST.size() > 0) {
CLASS_NAME_LIST.forEach(className -> {
//com.lossdate.demo.controller.DemoController
try {
Class<?> aClass = Class.forName(className);
// DemoController
String simpleName = aClass.getSimpleName();
// 首字母转小写 demoController
String lowerSimpleName = lowerFirst(simpleName);
// 区分controller,区分service'
if(aClass.isAnnotationPresent(MyController.class)) {
// controller的id此处不做过多处理,就拿类的首字母小写作为id,保存到ioc中
Object o = aClass.newInstance();
IOC.put(lowerSimpleName, o);
} else if(aClass.isAnnotationPresent(MyService.class)) {
Object o = aClass.newInstance();
MyService annotation = aClass.getAnnotation(MyService.class);
//获取注解value值 如果指定了id,就以指定的为准
if(isNotEmpty(annotation.value())) {
lowerSimpleName = annotation.value();
}
IOC.put(lowerSimpleName, o);
// service层往往是有接口的,面向接口开发,此时再以接口名为id,放入一份对象到ioc中,便于后期根据接口类型注入
Class<?>[] interfaces = aClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
// 以接口的全限定类名作为id放入
IOC.put(anInterface.getName(), o);
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
- 实现依赖注入
/**
* 实现依赖注入
*/
private void doAutoWired() {
// 有对象,再进行依赖注入处理
if(!IOC.isEmpty()) {
// 遍历ioc中所有对象,查看对象中的字段,是否有@MyAutowired注解,如果有需要维护依赖注入关系
IOC.forEach((key, value) -> {
// 获取bean对象中的字段信息
Field[] declaredFields = value.getClass().getDeclaredFields();
// 遍历判断处理
for (Field declaredField : declaredFields) {
// @Autowired
MyAutowired annotation = declaredField.getAnnotation(MyAutowired.class);
String beanName = declaredField.getType().getName();
// 有该注解
if(isNotEmpty(annotation.value())) {
beanName = annotation.value();
}
// 开启赋值
declaredField.setAccessible(true);
try {
declaredField.set(value, IOC.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
});
}
}
第五步:构造一个HandleMapping处理器映射器,建立映射关系
这里要将类,方法,url, 参数在方法中的顺序解析存储到Handler中存储,用于doPost时的匹配。
Handler参数有:
- Object controller
- Method method
- Pattern pattern
- Map<String,Integer> paramIndexMapping
其中类的存储时用于方法的invoke,url用了Pattern方便后面的正则匹配。
/**
* 构造一个HandleMapping处理器映射器
* 将url和method建立关联
*/
private void initHandlerMapping() {
if(!IOC.isEmpty()) {
IOC.forEach((key, value) -> {
// 获取ioc中当前遍历的对象的class类型
Class<?> aClass = value.getClass();
if(aClass.isAnnotationPresent(MyController.class)) {
String baseUrl = "";
if(aClass.isAnnotationPresent(MyRequestMapping.class)) {
MyRequestMapping annotation = aClass.getAnnotation(MyRequestMapping.class);
// 等同于/demo
baseUrl = annotation.value();
}
// 获取方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
// 方法没有标识MyRequestMapping,就不处理
if(method.isAnnotationPresent(MyRequestMapping.class)) {
MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
// 计算出来的url /demo/query
String url = baseUrl + annotation.value();
//生成handler
Handler handler = new Handler(value, method, Pattern.compile(url));
//获取方法需要的参数
Parameter[] parameters = method.getParameters();
//记录参数位置信息
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
if(parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
//参数为request或response
handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
} else {
//普通参数
handler.getParamIndexMapping().put(parameter.getName(), i);
}
}
HANDLER_MAPPING.add(handler);
}
}
}
});
}
}
3. 初始化完成mvc后,进行get和post的请求处理
1. get
这里直接调post
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
2. post
分为三步
1. 获取handler
2. 处理方法的参数及位置
3. handler的method属性调用方法
代码如下:
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1. 获取handler
Handler handler = getHandler(req);
if(handler == null) {
resp.getWriter().write("404 not found");
return;
}
//2. 处理方法的参数及位置
//获取方法参数长度,创建参数数组
Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
Object[] params = new Object[parameterTypes.length];
//获取普通传参
Map<String, String[]> parameterMap = req.getParameterMap();
//匹配参数
parameterMap.forEach((key, arr) -> {
// name=1&name=2 name [1,2] -> 如同 1,2
String paramValue = StringUtils.join(arr, ",");
//获取位置
Integer index = handler.getParamIndexMapping().get(key);
if(index != null) {
params[index] = paramValue;
}
});
//HttpServletRequest
Integer reqIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
if(reqIndex != null) {
params[reqIndex] = req;
}
//HttpServletRequest
Integer respIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
if(respIndex != null) {
params[respIndex] = resp;
}
//3. handler的method属性调用方法
try {
handler.getMethod().invoke(handler.getController(), params);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* 获取handler
*/
private Handler getHandler(HttpServletRequest req) {
if(!HANDLER_MAPPING.isEmpty()) {
for (Handler handler : HANDLER_MAPPING) {
Matcher matcher = handler.getPattern().matcher(req.getRequestURI());
if(matcher.matches()) {
return handler;
}
}
}
return null;
}
4. 配置文件
- springmvc.properties
scanPackage=com.lossdate.demo
- web.xml
<servlet>
<servlet-name>mymvc</servlet-name>
<servlet-class>com.lossdate.edu.mvcframework.servlet.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>springmvc.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>mymvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>