手写MVC框架
拉勾Java高薪训练营-阶段一模块三编程题
代码路径:https://gitee.com/szile20/lagou_stage1_module3.git
解题思路视频讲解:
链接:https://pan.baidu.com/s/1rFuahvyFoMS-LNoPsoVRUg
提取码:2qzf
一、编程题
手写MVC框架基础上增加如下功能
1)定义注解@Security(有value属性,接收String数组),该注解用于添加在Controller类或者Handler方法上,表明哪些用户拥有访问该Handler方法的权限(注解配置用户名)
2)访问Handler时,用户名直接以参数名username紧跟在请求的url后面即可,比如http://localhost:8080/demo/handle01?username=zhangsan
3)程序要进行验证,有访问权限则放行,没有访问权限在页面上输出
注意:自己造几个用户以及url,上交作业时,文档提供哪个用户有哪个url的访问权限
接下来先说一下项目的结构,然后在说解题思路
二、项目结构
- self-context : 容器的基础,共用的内容,如:容器接口BeanFactory, 注解 @Autowired,@Component,@Service以及它们的处理器等。
- self-mvc: mvc相关的内容,如:@Controller,@RequestMapping,@Security和@RequestParam及他们的注解;mvc容器MvcApplicationContext ,DispatcherServlet等。
- self-mvc-example: mvc功能的测试案例
- self-homework:是前三个模块的总和
三、思路及实现
定义类 DispatcherServlet
继承 HttpServlet
,在 init()
方法中初始化 mvc容器 MvcApplicationContext
public class DispatcherServlet extends HttpServlet {
@Override
public void init() throws ServletException {
try {
MvcApplicationContext.getInstance().refresh();
} catch (Exception e) {
e.printStackTrace();
}
}
// ...... 省略
}
MvcApplicationContext
的构造函数中会将 ControllerAnnotationHandler
添加到容器中,用于对@Controller
注解的处理
private MvcApplicationContext() {
super();
// 添加Controller注解处理器
addTypeAnnotationHandler(new ControllerAnnotationHandler(this));
}
MvcApplicationContext
的doRefresh()
中进行初始化
@Override
public void doRefresh() throws Exception {
// 通过new创建mvc容器,构造函数中将该对象交给BeanFactoryContextUtils,向外提供获取该对象的接口
// 加载配置文件
InputStream resourceAsStream = MvcApplicationContext.class.getClassLoader().getResourceAsStream("self-mvc.properties");
properties.load(resourceAsStream);
String scanPackages = properties.getProperty("scanPackages");
// 扫描包,加载Class
scanPackagesLoadClass(scanPackages.split(","));
// 初始化Bean
initInstance();
// 处理RequestMapping
initRequestMapping();
// 处理Security
initSecurity();
}
定义@Controller
注解及处理类 ControllerAnnotationHandler
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String name() default "";
}
public class ControllerAnnotationHandler extends AbstractTypeAnnotationHandler {
private final AbstractFieldAnnotationHandler fieldAnnotationHandler;
public ControllerAnnotationHandler(MvcApplicationContext context) {
super(context);
fieldAnnotationHandler = new AutowiredAnnotationHandler(context);
}
@Override
public Object doHandle(Class<?> cls) throws Exception {
Method[] declaredMethods = cls.getDeclaredMethods();
declaredMethods[0].getParameters();
Object newInstance = null;
boolean annotationPresent = cls.isAnnotationPresent(Controller.class);
if (annotationPresent) {
newInstance = BeanProxyFactory.newInstance(cls);
autowiredField(newInstance, cls, fieldAnnotationHandler);
MvcApplicationContext beanContext = (MvcApplicationContext) this.beanContext;
// 将Controller的bean注册到容器
beanContext.registerControllerBean(newInstance);
}
return newInstance;
}
}
定义@RequestMapping
注解及RequestMappingAnnotationHandler
处理类
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
public class RequestMappingAnnotationHandler {
public static List<RequestMappingHandler> handle(Object o) {
Class<?> aClass = o.getClass();
String url = "";
RequestMapping baseRequestMapping = aClass.getDeclaredAnnotation(RequestMapping.class);
if (baseRequestMapping != null) {
url = baseRequestMapping.value();
}
List<RequestMappingHandler> handlers = new ArrayList<>();
Method[] methods = aClass.getMethods();
for (Method method : methods) {
RequestMapping requestMapping = method.getDeclaredAnnotation(RequestMapping.class);
if (requestMapping != null) {
String value = requestMapping.value();
handlers.add(new RequestMappingHandler(o, Pattern.compile(url + value), method));
}
}
return handlers;
}
}
定义@Security注解及 SecurityFilter过滤器
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Security {
/**
* @return 需要的权限标识
*/
String[] value() default {};
final String SECURITY_STR = "username";
}
public class SecurityFilter implements Filter {
private final Security security;
public SecurityFilter(Security security) {
this.security = security;
}
@Override
public void doFilter(String... args) throws IllegalAccessException {
if (security.value().length == 0) return;
List<String> list = Arrays.asList(args);
// 请求参数中包含value中的任意一个,就是有权限访问
boolean flag = false;
for (String s : security.value()) {
if (list.contains(s)) {
flag = true;
break;
}
}
if (!flag) {
throw new IllegalAccessException("没有权限访问");
}
}
}
定义RequsetMappingHandler
请求处理类
public class RequestMappingHandler {
private final Object controller;
private final Pattern url;
private final Method method;
// 记录方法的参数名和位置, request和response除外
private final Map<String, Integer> parameterIndexMap;
// 方法参数HttpServletRequest 的位置
private int requestIndex = -1;
// 方法参数HttpServletResponse 的位置
private int responseIndex = -1;
// 过滤器
private List<Filter> filters = new ArrayList<>();
public RequestMappingHandler(Object controller, Pattern url, Method method) {
this.controller = controller;
this.url = url;
this.method = method;
// 记录方法参数及位置
parameterIndexMap = new HashMap<>();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
if (parameter.getType().isAssignableFrom(HttpServletRequest.class)) {
requestIndex = i;
} else if (parameter.getType().isAssignableFrom(HttpServletResponse.class)) {
responseIndex = i;
} else {
boolean present = parameter.isAnnotationPresent(RequestParam.class);
if (present) {
RequestParam requestParam = parameter.getDeclaredAnnotation(RequestParam.class);
parameterIndexMap.put(requestParam.value(), i);
}
}
}
}
/**
* 请求URL是否匹配
*
* @param requestUrl
* @return
*/
public boolean match(String requestUrl) {
return url.matcher(requestUrl).matches();
}
/**
* 方法调用
*
* @param request
* @param response
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public void invoke(HttpServletRequest request, HttpServletResponse response) throws InvocationTargetException, IOException, IllegalAccessException {
String[] securityValues = request.getParameterValues(Security.SECURITY_STR);
// 先使用Filter过滤
for (Filter filter : filters) {
filter.doFilter(securityValues);
}
// 获取Request中参数,封装
Object[] args = new Object[method.getParameterCount()];
if (requestIndex != -1) {
args[requestIndex] = request;
}
if (responseIndex != -1) {
args[responseIndex] = response;
}
for (Map.Entry<String, Integer> entry : parameterIndexMap.entrySet()) {
args[entry.getValue()] = request.getParameter(entry.getKey());
}
// 方法调用
try {
method.invoke(controller, args);
} catch (IllegalAccessException e) {
e.printStackTrace();
response.getWriter().write(e.getLocalizedMessage());
}
}
// ...... 省略
}
DispatcherServlet
中接收请求的处理
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
MvcApplicationContext context = MvcApplicationContext.getInstance();
// 根据Request 获取handler
RequestMappingHandler handler = context.getHandler(req);
if (handler == null) {
resp.getWriter().write("404!");
return;
}
try {
// 调用
handler.invoke(req, resp);
} catch (Exception e) {
e.printStackTrace();
resp.setHeader("Content-type", "text/html;charset=UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(new String(e.getLocalizedMessage().getBytes(), StandardCharsets.UTF_8));
}
}
四、测试案例
案例
定义一个资源服务类,存放在zhangsan,lisi,wangwu的资源
@Service
public class ResourceServiceImpl implements ResourceService {
private Map<String, String> resource;
public ResourceServiceImpl() {
resource = new HashMap<>();
resource.put("zhangsan", "张三的资源");
resource.put("lisi", "李四的资源");
resource.put("wangwu", "王五的资源");
}
@Override
public String getResource(String username) {
return resource.get(username);
}
}
定义资源控制器 ResourceController
类,定义/resource/security, 要求zhangsan 和 lisi可以访问,wangwu不可访问
@Controller
@RequestMapping("/resource")
public class ResourceController {
@Autowired
private ResourceService resourceService;
@RequestMapping("/security")
@Security({"zhangsan", "lisi"})
public void getSecurityResource(HttpServletResponse response, @RequestParam("username") String username) throws IOException {
response.setHeader("Content-type", "text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
String resource = resourceService.getResource(username);
String s = username + "获取资源:" + resource;
response.getWriter().write(new String(s.getBytes(), StandardCharsets.UTF_8));
}
}