自定义一个简单的web MVC 框架,实现一个Controller控制处理多个请求。
在IDEA中创建Maven web项目,最终项目层级结构如下:
这个注解打在类身上,主要是为了解决我们在总的控制器类里面解析太多类的问题以后只要解析哪个类身上打上这个注解,我们就解析哪个类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
此注解注解方法,表示映射路径
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value();
}
所有有关用户操作的请求,都交给这个controller来完成
@Controller
public class UserController {
@RequestMapping("/user/register")
public void register(HttpServletRequest req, HttpServletResponse resp){
System.out.println("执行了UserController的register方法~!");
}
@RequestMapping("/user/login")
public void login(HttpServletRequest req , HttpServletResponse resp){
System.out.println("执行了UserController的login方法~!");
}
}
用来封装 被调用的方法和 调用这个方法用到的实例对象,这里用Lombok注解生成构造方法。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MethodBean {
private Method method;//具体的方法对象
private Object obj;//调用这个方法要用的实例对象
}
public class DispatcherServlet extends HttpServlet {
//在类的成员变量中定义map集合
Map<String , MethodBean> map = new HashMap<>();
@Override
public void init(ServletConfig config) throws ServletException {
try {
//1. 读取初始化参数
String packageName = config.getInitParameter("packageName");
//2. 扫描这个包
List<Class<?>> classList = ClassScannerUtils.getClasssFromPackage(packageName);
//3. 遍历集合中的每一个类
for (Class<?> clazz : classList) {
//4. 判定这个类身上有没有 @Controller注解
boolean flag = clazz.isAnnotationPresent(Controller.class);
if (flag) {
//5. 得到这个类里面所有方法
Method[] methods = clazz.getMethods();
//6. 遍历每一个方法
for (Method method : methods) {
//7. 判断方法上是否有注解 @RequestMapping , 要注意有的方法身上没有这个注解,那么返回的是null
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
if(annotation != null){//获取注解身上的value值。
String mapping = annotation.value();
//8. 使用map集合来装映射管理 key : mapping ,value : method & clazz.newInstance();
MethodBean methodBean = new MethodBean(method, clazz.newInstance());
map.put(mapping,methodBean);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String uri = req.getRequestURI();
String path = uri.substring(req.getContextPath().length() , uri.lastIndexOf('.'));
//2. 拿着地址去map集合里面找匹配的集合
MethodBean bean = map.get(path);
//3. 要记得判定map返回的结果,因为这个映射地址在map集合里面并不一定有方法与之对应
if(bean != null){
//如果进入这个if分支,即表示找到匹配的记录,找到之后,就让方法执行即可
Method method = bean.getMethod();
Object object = bean.getObj();
method.invoke(object ,req , resp );
}else{
System.out.println(path + " ,没有找到匹配的方法可以执行!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
这是一个工具类,用来获取包下的所有class的实例。
public static List<Class<?>> getClasssFromPackage(String packageName) {
List clazzs = new ArrayList<>();
// 是否循环搜索子包
boolean recursive = true;
// 包名对应的路径名称
String packageDirName = packageName.replace('.', '/');
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
findClassInPackageByFile(packageName, filePath, recursive, clazzs);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return clazzs;
}
/**
* 在package对应的路径下找到所有的class
*/
public static void findClassInPackageByFile(String packageName, String filePath, final boolean recursive,
List<Class<?>> clazzs) {
File dir = new File(filePath);
if (!dir.exists() || !dir.isDirectory()) {
return;
}
// 在给定的目录下找到所有的文件,并且进行条件过滤
File[] dirFiles = dir.listFiles(new FileFilter() {
public boolean accept(File file) {
boolean acceptDir = recursive && file.isDirectory();// 接受dir目录
boolean acceptClass = file.getName().endsWith("class");// 接受class文件
return acceptDir || acceptClass;
}
});
for (File file : dirFiles) {
if (file.isDirectory()) {
findClassInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, clazzs);
} else {
String className = file.getName().substring(0, file.getName().length() - 6);
try {
clazzs.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + "." + className));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
配置DispatcherServlet,这配置抓取 *.do 的请求,可任意配置。
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>dispatcherservlet-name>
<servlet-class>com.primo.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>packageNameparam-name>
<param-value>com.primo.Controllerparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherservlet-name>
<url-pattern>*.dourl-pattern>
servlet-mapping>
web-app>
配置项目依赖jar包
<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.0modelVersion>
<groupId>com.primogroupId>
<artifactId>myMVCartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>warpackaging>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.18version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
dependencies>
project>
此案例实现了一个简单的MVC框架,对学习理解Java中最常用的SpringMVC框架有一定的帮助。