首先,这篇文章借鉴了网上项目为com.liugh.liughMVC的一个项目,不过其中的@MyRequestParam的作用却未实现,我这边加了一下,并对项目中加了很多解释
我的项目地址 https://gitee.com/xdsz/myMvc.git
1 新建一个普通的maven项目,pom文件里引入servlet依赖,
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
2 新建文件夹webapp,并创建web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>mymvc</servlet-name>
<servlet-class>com.sj.myservlet.SjServlet</servlet-class>
<init-param>
<!--在init方法中会用到-->
<param-name>contextConfig</param-name>
<!--对应resources文件夹下配置文件的名字-->
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mymvc</servlet-name>
<!--拦截所有-->
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
3 在resources文件下创建application.properties,文件名称对应web.xml中配置的名称,内容如下
#项目启动时扫描的文件目录
scanPackage=com.sj.mycontroller
4 新建文件夹
annotation:放注解的
mycontroller::放自己的控制类
myservlet:对注解的支持是在这个文件下的Sjservlet中
5 在annotation文件下创建注解接口
package com.sj.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SjRequestMapping {
String value() default "";//url
}
-------------------------------------------------------------------------------------------------------------------------
package com.sj.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)//表明注解的作用目标:接口、类、枚举、注解
@Retention(RetentionPolicy.RUNTIME)//表明注解的保留位置 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented//说明该注解将被包含在javadoc中
public @interface SjController {
String sjvalue()default "";//value
}
------------------------------------------------------------------------------------------------------------------------
package com.sj.annotation;
import java.lang.annotation.*;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SjRequesetParam {
String value();//表示参数的别名,必须得填
}
----------------------------------------------------------------------------------------------------------------------------
package com.sj.annotation;
import java.lang.annotation.*;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//这个是自己加着玩的,加在参数上的,可以给对应的参数拼接指定的字符串
public @interface SjAddStr {
String value();//拼接添加字符
int type() default 1;//类型 1是加在前面 其他是加在后面
}
6 在myservlet文件下创建Sjservlet,继承HttpServlet,重写其中的方法,类和方法级别的注解是在初始化的时候执行的,参数的注解处理是在dopost中处理的
package com.sj.myservlet;
import com.sj.annotation.SjAddStr;
import com.sj.annotation.SjController;
import com.sj.annotation.SjRequesetParam;
import com.sj.annotation.SjRequestMapping;
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.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;
public class SjServlet extends HttpServlet {
private Properties properties = new Properties();//配置文件中配置的信息
private List<String> classNames = new ArrayList<>();//所有扫描到的类的名字
private Map<String, Object> controllers = new HashMap<>();//类名 , 对象
private Map<String, Method> handlerMapping = new HashMap<>();// key是url value是对应的方法
private Map<String, Object> controllerMap =new HashMap<>();// key是url value是对应的对象
@Override
public void init(ServletConfig config) throws ServletException {
//1.加载配置文件
String configPath = config.getInitParameter("contextConfig");//得到配置文件的路径,对应web.xml中配置的名称
doLoadConfig(configPath);
//2.初始化所有相关联的类,扫描用户设定的包下面所有的类
String packagePath =properties.getProperty("scanPackage");//得到配置文件配置的要扫描的包路径,对应配置文件的配置信息
doScanner(packagePath);
//3.拿到扫描到的类,通过反射机制,实例化,并且放到controllers中(k-v beanName-bean) beanName默认是首字母小写
doInstance();
//4.初始化HandlerMapping(将url和method对应上)
initHandlerMapping();
}
@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 {
try {
//处理请求
doDispatch(req,resp);
} catch (Exception e) {
resp.getWriter().write("500!! Server Exception");
}
}
//对参数注解的支持 逻辑是在这里实现的
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
if(handlerMapping.isEmpty()){
return;
}
String url =req.getRequestURI();//得到访问路径
String contextPath = req.getContextPath();
//拼接url并把多个/替换成 初始化时往handlerMapping和controllerMap中放入的key
url=url.replace(contextPath, "").replaceAll("/+", "/");
if(!this.handlerMapping.containsKey(url)){
resp.getWriter().write("404 NOT FOUND!");
return;
}
Method method =this.handlerMapping.get(url);//得到url对应的方法
//获取方法的参数列表
Parameter[] paraNames=method.getParameters();
//获取请求的参数
Map<String, String[]> parameterMap = req.getParameterMap();
//保存参数值
Object [] paramValues= new Object[paraNames.length];
//方法的参数列表
for (int i = 0; i<paraNames.length; i++){
Parameter parameter=paraNames[i];
//根据参数名称,做某些处理
String paratype = parameter.getType().getName();//参数类型
String paraname = parameter.getName();//参数名称
if (paratype.contains("HttpServletRequest")){
//参数类型已明确,这边强转类型
paramValues[i]=req;
continue;
}
if (paratype.contains("HttpServletResponse")){
paramValues[i]=resp;
continue;
}
if(parameter.isAnnotationPresent(SjRequesetParam.class)){//如果改参数使用了SjRequesetParam注解
SjRequesetParam annotation = parameter.getAnnotation(SjRequesetParam.class);
paraname=annotation.value();//替换为SjRequesetParam注解中设置的参数名称
}
String paramvalue="";
if(paratype.equals("java.lang.String")){
paramvalue=Arrays.toString(parameterMap.get(paraname)).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");;
}
if(parameter.isAnnotationPresent(SjAddStr.class) && !"".equals(paramvalue)){
SjAddStr annotation = parameter.getAnnotation(SjAddStr.class);
String addstr=annotation.value();
int type=annotation.type();
if(type==1){//判断拼接的类型,是加在前面还是后面的
paramvalue=paramvalue+addstr;
}else{
paramvalue=addstr+paramvalue;
}
}
paramValues[i]=paramvalue;
}
try {//从controllerMap得到url对应的实例对象,利用反射机制来调用
method.invoke(this.controllerMap.get(url), paramValues);
} catch (Exception e) {
e.printStackTrace();
}
}
//加载配置文件
private void doLoadConfig(String location){
//把web.xml中的contextConfig对应value值的文件加载到留里面
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
try {
//用Properties文件加载文件里的内容
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}finally {
//关流
if(null!=resourceAsStream){
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//初始化所有相关联的类,扫描用户设定的包下面所有的类,使用了递归的写法读取所有的文件,渠道完整的类名方便使用反射
private void doScanner(String packageName) {
//把所有的.替换成/
URL url =this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/"));
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
if(file.isDirectory()){
//递归读取包
doScanner(packageName+"."+file.getName());
}else{
String className =packageName +"." +file.getName().replace(".class", "");
classNames.add(className);
}
}
}
//拿到扫描到的类,通过反射机制,实例化,并且放到controllers中(k-v beanName-bean) beanName默认是首字母小写
private void doInstance() {
if (classNames.isEmpty()) {
return;
}
for (String className : classNames) {
try {
//利用反射实例化类
Class<?> clazz =Class.forName(className);
if(clazz.isAnnotationPresent(SjController.class)){
String controllerName = clazz.getSimpleName();
Object obj= clazz.newInstance();
controllers.put(toLowerFirstWord(controllerName),obj);
}else{
continue;
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
//初始化HandlerMapping(将url和method对应上)
private void initHandlerMapping(){
if(controllers.isEmpty()){
return;
}
try {
for (Map.Entry<String, Object> entry: controllers.entrySet()) {
Class<? extends Object> clazz = entry.getValue().getClass();
if(clazz.isAnnotationPresent(SjController.class)){
//拼url时,是controller头的url拼上方法上的url
String baseUrl ="";
if(clazz.isAnnotationPresent(SjRequestMapping.class)){
SjRequestMapping annotation = clazz.getAnnotation(SjRequestMapping.class);
baseUrl=annotation.value();
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if(!method.isAnnotationPresent(SjRequestMapping.class)){
continue;
}
SjRequestMapping annotation = method.getAnnotation(SjRequestMapping.class);
String url = annotation.value();
url =(baseUrl+"/"+url).replaceAll("/+", "/");
//这里应该放置实例和method
handlerMapping.put(url,method);
controllerMap.put(url,clazz.newInstance());
System.out.println(url+","+method);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 把字符串的首字母小写
* @param name
* @return
*/
private String toLowerFirstWord(String name){
char[] charArray = name.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
}
7 试一下效果,创建TestController,在浏览器用get方式访问一下
http://localhost:8080/test?passram=a啊
package com.sj.mycontroller;
import com.sj.annotation.SjAddStr;
import com.sj.annotation.SjController;
import com.sj.annotation.SjRequesetParam;
import com.sj.annotation.SjRequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
@SjController
public class TestController {
@SjRequestMapping(value="test" )
public void sjtest(HttpServletRequest req, HttpServletResponse resp,@SjRequesetParam("passram")@SjAddStr(value = "str", type=2) String param){
try {
System.out.println(param);
String result="处理过的参数为:"+param;
OutputStream outputStream=resp.getOutputStream();//获取OutputStream输出流
resp.setHeader("content-type", "text/html;charset=UTF-8");//通过设置响应头控制浏览器以UTF-8的编码显示
byte[] dataByteArr=result.getBytes("UTF-8");//将字符转换成字节数组,指定以UTF-8编码进行转换
outputStream.write(dataByteArr);//使用OutputStream流向客户端输出字节数组
}catch (Exception e){
}
}
}