效果
收到请求并且鉴权后会自动映射请求到方法上来
image.png
实现
1、定义@Controller 注解,标识类需要被扫描
/**
* 标识类需要被扫描
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
2、定义@RequestMapping 注解,标识类需要被扫描
/**
* 映射url到类和方法上
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
//url
String value();
//默认为GET
RequestMethod method() default RequestMethod.DEFAULT;
//是否需要身份验证
boolean auth() default true;
}
3、RequestMethod 用于枚举请求的方法
/**
* 请求方法枚举,只枚举了我们需要的方法
*/
public enum RequestMethod {
GET, POST, DELETE, DEFAULT
}
4、RequestHandler 用于存放url映射到方法的相关信息
/**
* 请求处理者类
*
* @author Jerry
* @date 2019/7/22 11:01
*/
public class RequestHandler {
//映射的路径
public String path;
//方法GET、POST等
public RequestMethod requestMethod;
//类对象
public Class clazz;
//方法对象
public Method method;
//类的实例对象
public Object instance;
//是否需要身份验证
public boolean isAuth;
}
5、RequestMappingProcessor 用于扫描注解
/**
* 注解扫描器
*
* @author Jerry
* @date 2019/7/22 10:51
*/
public class RequestMappingProcessor {
//存放url到RequestHandler的映射
public static HashMap requestMappingMap = new HashMap<>();
/**
* 扫描解析指定包下的@Controller和@RequestMapping注解
*
* 只能扫描class文件,暂未实现扫描jar包
*
* @return 生成的映射表
*/
public static HashMap scanRequestMapping() {
//扫描的包路径
final String pkgPath = "com/zlt/controller";
//用于字符拼接
final String pkgClassPath = pkgPath.replace("/", ".") + ".";
requestMappingMap.clear();
Enumeration enumeration = null;
try {
//获取该包下的class文件
enumeration = Thread.currentThread().getContextClassLoader().getResources(pkgPath);
} catch (IOException e) {
e.printStackTrace();
}
if (enumeration == null) {
System.out.println("扫描失败");
return null;
}
try {
while (enumeration.hasMoreElements()) {
URL url = enumeration.nextElement();
File file = new File(url.getFile());
String[] fileList = file.list();
if (fileList == null) {
System.err.println(file.getAbsolutePath() + "包没有类");
return null;
}
for (String path : fileList) {
Class clazz = Thread.currentThread().getContextClassLoader().loadClass(pkgClassPath + path.substring(0, path.length() - 6));
Controller controller = (Controller) clazz.getAnnotation(Controller.class);
if (controller != null) {
System.out.println(clazz.getName());
RequestMapping parentRequestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
//类注解上的一级路径
String basePath = null;
RequestMethod baseMethod = null;
if (parentRequestMapping != null) {
//获取类注解上的一级路径
basePath = ensurePath(parentRequestMapping.value());
baseMethod = parentRequestMapping.method();
//忽略类注解上的auth
//baseAuth = parentRequestMapping.auth();
//默认为RequestMethod.GET
if (baseMethod == RequestMethod.DEFAULT) baseMethod = RequestMethod.GET;
}
//生成类的实例对象
Object instance = clazz.newInstance();
System.out.println("扫描到类 " + instance.getClass().getCanonicalName());
for (Method method : clazz.getDeclaredMethods()) {
System.out.println(" " + method.getName() + "()");
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
if (requestMapping != null) {//获取到方法上的注解
method.setAccessible(true);
//生成RequestHandler实例
RequestHandler warp = new RequestHandler();
//拼接生成最终的url
warp.path = basePath + ensurePath(requestMapping.value());
//获取方法注解上的RequestMethod
RequestMethod requestMethod = requestMapping.method();
if (requestMethod == RequestMethod.DEFAULT) {
//如果类注解设置了RequestMethod,而方法注解没有设置RequestMethod,则类注解会覆盖方法注解的请求方法
warp.requestMethod = baseMethod != null ? baseMethod : RequestMethod.GET;//默认为GET
} else {//否则使用方法注解上的RequestMethod
warp.requestMethod = requestMethod;
}
warp.method = method;
//默认为true
warp.isAuth = requestMapping.auth();
warp.clazz = clazz;
warp.instance = instance;
//检查参数,这里还可以再检查一下返回值类型什么的
//这里的方法参数类型和个数都是定死了,可以拓展支持不同参数类型和返回类型
if (method.getParameterCount() == 2) {
Class>[] classes = method.getParameterTypes();
if (HttpServletRequest.class == classes[0] &&
HttpServletResponse.class == classes[1]) {
requestMappingMap.put(warp.path, warp);
continue;
}
}
System.err.println(method.getName() + " 参数错误");
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
if (requestMappingMap != null) {
System.out.println("url映射:");
for (Map.Entry entry : requestMappingMap.entrySet()) {
RequestHandler warp = entry.getValue();
System.out.println(warp.path + " " + warp.requestMethod + " " + warp.clazz.getCanonicalName() + "." + warp.method.getName() + "()");
}
}
return requestMappingMap;
}
/**
* 规范url格式
*
* 标准格式为前有ur开头l“/”而后面没有 如 /user、/user/login等
*/
private static String ensurePath(String path) {
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
return path;
}
public static HashMap getRequestMappingMap() {
return requestMappingMap;
}
}
6、定义过滤器 RequestFilter ,过滤没有映射的url
/**
* Request过滤类
*
* 通过url、method等过滤未映射的Request
*
* @author Jerry
* @date 2019/7/23 15:57
*/
public class RequestFilter implements Filter {
private HashMap handlerMap = RequestMappingProcessor.getRequestMappingMap();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = ((HttpServletRequest) servletRequest);
String fullPath = request.getPathInfo();
RequestHandler handler = handlerMap.get(fullPath);
if (handler != null) {
//把处理者添加到request的attribute中
request.setAttribute(Config.KEY_REQUEST_HANDLER, handler);
filterChain.doFilter(servletRequest, servletResponse);
} else {
//直接就不往下传递请求了,这里可以处理一下,排除一下页面 如druid
System.out.println(request.getRequestURI() + ":没有映射");
}
}
}
7、定义过滤器 AuthenticationFilter ,用于请求鉴权
/**
* 用来过滤未登录的请求
*
* @author Jerry
* @date 2019/7/22 14:16
*/
public class AuthenticationFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (servletRequest.getAttribute(Config.KEY_REQUEST_DRUID) != null) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
RequestHandler handler = (RequestHandler) servletRequest.getAttribute(Config.KEY_REQUEST_HANDLER);
if (handler != null) {
if (handler.isAuth) {//如果需要验证身份
boolean isLogin = false;
try {
//这里始终为false,需要实现鉴权逻辑
isLogin = false;
} catch (Exception e) {
e.printStackTrace();
}
if (isLogin) {//如果鉴权通过
filterChain.doFilter(servletRequest, servletResponse);
} else {
//如果鉴权没通过
}
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
} else {
//处理者为空情况
System.out.println("AuthenticationFilter:" + ((HttpServletRequest) servletRequest).getRequestURI());
}
}
}
8、DispatchServlet 用于执行分发请求到对应方法上
/**
* 负责分发请求的Servlet
*
* 根据配置,分发请求到不同的方法
*
* @author Jerry
* @date 2019/7/20 17:41
*/
public class DispatchServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
RequestHandler handler = (RequestHandler) request.getAttribute(Config.KEY_REQUEST_HANDLER);
if (handler != null) {
try {
Object data = handler.method.invoke(handler.instance, request, response);
//自己处理返回值
} catch (Exception e) {
response.getWriter().write(ResponseBody.JSON_ERROR_SYSTEM);
e.printStackTrace();
}
} else {
System.err.println(request.getRequestURI());
}
}
}