手写SpringMVC首先理清思路,具体要做什么 我分为以下几个步骤
1.扫描包下带有@Controller @Service注解的类
2.实例化:将扫描的类通过反射实例化并保存到ioc容器中
3.依赖注入:将存在依赖的bean进行注入:列如被@Autowired修饰的属性进行注入
4.urlMapping:http请求路径与方法建立映射关系
理清思路我们就开始快乐之旅吧~~~~~~
1.新建一个web工程添加servlet jar包
<?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.work</groupId>
<artifactId>StudySpring</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<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>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
1.1 web.xml配置
<?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">
<servlet>
<servlet-name>DisparcherServlet</servlet-name>
<servlet-class>com.work.servlet.DispatcherServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DisparcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
2 .新建注解(@Controller,@Awtowired,@RequestMapping,@Service,@RequestParams)(为了区分Spring框架我这里注解前缀都会有Zp)
@Target(ElementType.TYPE) //表示只能作用在类上
@Retention(RetentionPolicy.RUNTIME) //表示可以在运行时通过反射获取
@Documented //javadoc
public @interface ZpController {
String value() default "";
}
@Target(ElementType.FIELD) //表示只能作用在属性上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ZpAutowired {
String value() default "";
}
@Target({ElementType.TYPE,ElementType.METHOD}) //表示可以作用在类上和方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ZpRequestMapping {
String value() default "";
}
@Target(ElementType.TYPE) //表示可以作用在类上
@Retention(RetentionPolicy.RUNTIME) //表示可以在运行时通过反射获取
@Documented
public @interface ZpService {
String value() default "";
}
@Target(ElementType.PARAMETER) //表示只能作用在参数上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ZpRequestParams {
String value() default "";
}
3.新建接口类
public interface StudentBiz {
String helloSpring();
}
4.接口实现类
@ZpService("studentBizImpl")
public class StudentBizImpl implements StudentBiz{
public String helloSpring() {
return "hello Spring";
}
}
5.新建controller控制层
@ZpController
@ZpRequestMapping("/student")
public class StudentController {
@ZpAutowired("studentBizImpl")
public StudentBiz studentBiz;
@ZpRequestMapping("/query.do")
public void queryInfo(HttpServletRequest req, HttpServletResponse resp,@ZpRequestParams("name") String name){
try {
resp.setContentType("application/json;charset=utf-8");
PrintWriter pw=resp.getWriter();
//调用业务层方法
String str = studentBiz.helloSpring();
pw.print(str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.新建DispatcherServlet处理器
package com.work.servlet;
import com.work.annotation.*;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
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.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ProjectName: SpringPrinciple
* @Package: PACKAGE_NAME
* @ClassName: DispatcherServlet
* @Description: java类作用描述: 模仿springMVC处理器
* @Author: 祝平
* @CreateDate: 2019-02-26 17:21
* @UpdateUser: 更新者
* @UpdateDate: 2019-02-26 17:21
* @UpdateRemark: 更新说明
* @Version: 1.0
*/
public class DispatcherServlet extends HttpServlet{
//保存class文件地址
List<String> classPaths=new ArrayList<String>();
//IOC容器
Map<String,Object> beans=new HashMap<String, Object>();
//存储方法的容器
Map<String,Object> handlerMap=new HashMap<String, Object>();
@Override
public void init() throws ServletException {
//扫描所有类
doScanPackage("com.work");
//实例化
doInstance();
//依赖注入
doAutowired();
//请求地址和方法映射
urlMapping();
}
/**
* 扫描所有带有@ZpController,@ZpService的类进行装载
* @param basePackage 类的路径
*/
public void doScanPackage(String basePackage){
//获取com.work下所有的类文件地址
//对于java来说只认识com/work格式,所以需要把.转换成/
URL url=this.getClass().getClassLoader().getResource("/"+basePackage.replaceAll("\\.","/"));
String fileStr=url.getFile(); //相当于获取到com/work..
File file=new File(fileStr); //根据获取到的地址转成文件
String[] filesStr=file.list(); //获取所有文件
//遍历所有文件判断是否是.class结尾的
for(String path:filesStr){
File filePath=new File(fileStr+path);
//判断是否是路径,如果是就递归,直至拿到class文件
if(filePath.isDirectory()){
doScanPackage(basePackage+"."+path);
}else{
// .class文件
classPaths.add(basePackage+"."+path); //其实就是com.work.controller...下的类路径
}
}
}
/**
* 进行实例化
*/
public void doInstance(){
//遍历所有地址进行类加载,判断是否被@ZpController和@ZpService注解修饰的类
for (String classPath:classPaths){
try {
//拿到的地址是com.work.controller.类.class所以需要把.class转换一下
Class<?> clazz=Class.forName(classPath.replace(".class",""));
//判断是否被@ZpController修饰
if (clazz.isAnnotationPresent(ZpController.class)){
//说明是控制类
beans.put(lowerFirstChar(clazz.getSimpleName()),clazz.newInstance());
}else if (clazz.isAnnotationPresent(ZpService.class)){
//说明是Service
ZpService zpService=clazz.getAnnotation(ZpService.class);
String key=zpService.value();
if(!"".equals(key)){
beans.put(key,clazz.newInstance());
continue;
}
//如果@ZpService注解没设置值就用接口的名字接口的名字首字母小写
Class[] inters=clazz.getInterfaces();
//此处简单处理了,假定ServiceImpl只实现了一个接口
for (Class c : inters) {
//举例 modifyService->new ModifyServiceImpl()
beans.put(lowerFirstChar(c.getSimpleName()), clazz.newInstance());
break;
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
/**
* 依赖注入
*/
public void doAutowired(){
for (Map.Entry<String,Object> bean:beans.entrySet()){
Class<?> clazz=bean.getValue().getClass();
if(clazz.isAnnotationPresent(ZpController.class)){
//获取private修饰的属性
Field[] fields=clazz.getDeclaredFields();
//遍历所有属性是否被@ZpAutowired修饰
for(Field field:fields){
if(!field.isAnnotationPresent(ZpAutowired.class)){
continue;
}
//获取@ZpAutowired的值进行匹配注入
ZpAutowired zpAutowired=field.getAnnotation(ZpAutowired.class);
String beanName;
if("".equals(zpAutowired.value())){
beanName = lowerFirstChar(field.getType().getSimpleName());
}else{
beanName = zpAutowired.value();
}
//因为是private修饰 需要开启权限才能注入实例
field.setAccessible(true);
if(beans.get(beanName) != null){
try {
//注入实例
field.set(bean.getValue(),beans.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* 进行请求地址和方法映射
*/
public void urlMapping(){
for(Map.Entry<String,Object> entry:beans.entrySet()){
Class<?> clazz=entry.getValue().getClass();
//判断是否是控制类
if(clazz.isAnnotationPresent(ZpController.class)){
//获取ZpController注解上的值
ZpRequestMapping zrm=clazz.getAnnotation(ZpRequestMapping.class);
String classPath=zrm.value();
//获取方法上ZpRequestMapping注解的值
Method[] methods=clazz.getDeclaredMethods();//获取所有方法
//遍历方法
for (Method method:methods){
if(method.isAnnotationPresent(ZpRequestMapping.class)){
ZpRequestMapping zpRequestMapping=method.getAnnotation(ZpRequestMapping.class);
String methodPath=zpRequestMapping.value();
//进行拼接
handlerMap.put(classPath+methodPath,method);
}else{
continue;
}
}
}
}
}
/**
* tomcat启动完成后做得处理
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@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 {
//获取请求的地址
String url=req.getRequestURI();
int index=url.indexOf("/",1);
String controllerURL=url.substring(0,index);//截取到/hello
Method method=(Method) handlerMap.get(url);
for(Map.Entry<String,Object> bean:beans.entrySet()){
Class<?> clazz=bean.getValue().getClass();
if(clazz.isAnnotationPresent(ZpController.class)){
ZpRequestMapping zrm=clazz.getAnnotation(ZpRequestMapping.class);
String zcValue=zrm.value();
if(controllerURL.equals(zcValue)){
try {
Object[] args=getArgs(req,resp,method);
method.invoke(bean.getValue(),args);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 找出方法所需要的方法参数
* @param req
* @param resp
* @param method
* @return
*/
private static Object[] getArgs(HttpServletRequest req, HttpServletResponse resp,Method method){
//拿到当前类待执行的方法参数
Class<?>[] clazzParams=method.getParameterTypes();
//根据参数的个数,new一个参数的数组,将方法里的所有参数赋值到args里
Object [] args=new Object[clazzParams.length];
int args_i=0;
int index=0;
for(Class<?> clazz:clazzParams){
//判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口
if(ServletRequest.class.isAssignableFrom(clazz)){
args[args_i++]=req;
}
if(ServletResponse.class.isAssignableFrom(clazz)){
args[args_i++]=resp;
}
//判断有没有@ZpRequestParams注解
Annotation[] pramsAns=method.getParameterAnnotations()[index];
if(pramsAns.length>0){
for(Annotation paramAn:pramsAns){
if(ZpRequestParams.class.isAssignableFrom(paramAn.getClass())){
ZpRequestParams zrp=(ZpRequestParams)paramAn;
//找到注解的name
args[args_i++]=req.getParameter(zrp.value());
}
}
}
index++;
}
return args;
}
/***
* 转小写方法
* @param className
* @return
*/
private String lowerFirstChar(String className) {
char[] chars = className.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
至此就已经完成我们的功能了