手写MVC框架

手写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的访问权限

接下来先说一下项目的结构,然后在说解题思路

二、项目结构

  1. self-context : 容器的基础,共用的内容,如:容器接口BeanFactory, 注解 @Autowired,@Component,@Service以及它们的处理器等。
  2. self-mvc: mvc相关的内容,如:@Controller,@RequestMapping,@Security和@RequestParam及他们的注解;mvc容器MvcApplicationContext ,DispatcherServlet等。
  3. self-mvc-example: mvc功能的测试案例
  4. 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));
    }

MvcApplicationContextdoRefresh() 中进行初始化

    @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));

    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超人@不会飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值