在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
先用servlet+jsp 实现一个小功能,传一个参数,输出当前时间 @WebServlet("/hello/showDate") public class HelloServlet extends HttpServlet{ @Override public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException{ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String date = sdf.format(new Date()); String name = request.getParameter("name"); request.setAttribute("date", "hi "+name+",Now is:"+date); request.getRequestDispatcher("/jsp/hello.jsp").forward(request, response); } } <body> <h1>${date}</h1> </body> 访问地址 /hello/showDate?name=lee 效果 功能很简单,现在模仿Spring 注解方式来实现这段代码 ModelAndView类 存储页面返回信息 public class ModelView { private String view; private Map<String, Object> modelMap; public String getView() { return view; } public void setView(String view) { this.view = view; } public Map<String, Object> getModelMap() { return modelMap; } public void setModelMap(Map<String, Object> modelMap) { this.modelMap = modelMap; } } controller类 负责做分发,@Controller注解这个类是controller类,@RequestMapping配置类路径和方法路径 @Controller @RequestMapping("/hello") public class HelloController { private HelloService helloService; @RequestMapping("/showDate") public ModelView showDate(String name){ ModelView modelView = helloService.showDate(name); return modelView; } } service 实现类 @Service 标识这个是service类 @Service public class HelloService { public ModelView showDate(String name){ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String date = sdf.format(new Date()); ModelView modelView = new ModelView(); modelView.setView("/jsp/hello.jsp"); Map<String,Object> map = new HashMap<String, Object>(); map.put("date", "hi "+name+",Now is:"+date); modelView.setModelMap(map); return modelView; } } 写完之后一堆错,这些注解我们都没有定义。没关系 一步一步来 ,先定义注解类 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Controller { } @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RequestMapping { String value() default "/"; } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Service { } 到目前位置编译错误没有了,但是还没有实现我们想要的功能。 理想中我们想要的应该是,启动JVM,自动实例化我们的controller,service类,将service类的实例注入到controller中,根据访问路径反射相应的类方法, 首先,我们先在web.xml里定义一个DispatcherServlet类 用来处理所有项目路径请求,做总的分发器 <servlet> <servlet-name>servlet</servlet-name> <servlet-class>com.servlet.DispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>servlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> public class DispatcherServlet extends HttpServlet { @Override public void init(ServletConfig config){ } } 在初始化DispatcherServlet类时,我们希望程序扫描我们自己定义的base-package下的所有controller类和service类,并将service类实例注入到controller中,而且我们希望在程序启动时就做这些事情,并加载一些我们配置,如数据库连接等等,当然这个项目中只需要配一个base-package。 所以我们补充一下web.xml中的配置 <servlet> <servlet-name>servlet</servlet-name> <servlet-class>com.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>config.properties</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>servlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> 还有配置资源文件 base-package=com
下面开始具体实现,先写一个工具类ClassUtil,定义一个方法getClass()来加载base-package包下的所有类 public class ClassUtil { private static ClassLoader classLoader=Thread.currentThread().getContextClassLoader(); public static ClassLoader getClassLoader(){ return classLoader; } public static Set<Class<?>> getClass(String path){ Set<Class<?>> classSet = new HashSet<Class<?>>(); String filePath = classLoader.getResource(path).getFile(); load(filePath, path,classSet); return classSet; } private static void load(String path,String packageName,Set<Class<?>> classSet){ File[] files = new File(path).listFiles(); for(File file:files){ String fileName = file.getName(); if(file.isFile()){ if(fileName.endsWith(".class")){ try { //并不是所有类都是需要实例化的,是否实例化置成false,等需要时再实例化 Class cls = Class.forName(packageName+"."+fileName.substring(0,fileName.lastIndexOf('.')), false, classLoader); classSet.add(cls); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }else{ load(file.getPath(),packageName+"."+fileName, classSet); } } } } 现在我们有一个Set集合存储了扫描的类,接下来还需要一个工具类来做以下事情; 1.实例化Set集合中的service类 2.实例化Set集合中的controller类,并将controller类中service成员变量注入service实例 3.将访问路径和controller类一一对应。 所以 我们需要几个属性 Map<String,Object> controllerMap 存储请求和controller的对应关系, Set<Class<?>> classSet 接收ClassUtil返回的加载过的类集合 getService()方法实例化service, initController()方法实现目标2,3 并返回controllerMap。 public class InitClassUtil { private static Map<String,Object> controllerMap; private static Set<Class<?>> classSet; public InitClassUtil(String config){ classSet=scanClass(config); controllerMap=initController(); } public static Map<String, Object> getControllerMap() { return controllerMap; } private static Set<Class<?>> scanClass(String config){ Properties props = PropsUtil.loadProps(config); return ClassUtil.getClass(props.getProperty("base-package")); } private static Map<Class<?>,Object> getService(){ Map<Class<?>,Object> serviceMap = new HashMap<Class<?>,Object>(); return serviceMap; } private static Map<String,Object> initController(){ return controllerMap; } } 具体实现 private static Map<Class<?>,Object> getService(){ Map<Class<?>,Object> serviceMap = new HashMap<Class<?>,Object>(); for(Class<?> service:classSet){ if(service.isAnnotationPresent(Service.class)){ try { serviceMap.put(service, service.newInstance()); } catch (InstantiationException | IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return serviceMap; } private static Map<String,Object> initController(){ Map<Class<?>,Object> serviceMap = getService(); Map<String,Object> controllerMap = new HashMap<String,Object>(); for(Class<?> controller:classSet){ if(controller.isAnnotationPresent(Controller.class)){ try { Object object = controller.newInstance(); if(controller.isAnnotationPresent(RequestMapping.class)){ controllerMap.put(controller.getAnnotation(RequestMapping.class).value(), object); } //注入service实例 for(Field field :object.getClass().getDeclaredFields()){ if(serviceMap.containsKey(field.getType())){ field.setAccessible(true); field.set(object, serviceMap.get(field.getType())); } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return controllerMap; } public class InitClassUtil { private static Map<String,Object> controllerMap; private static Set<Class<?>> classSet; public InitClassUtil(String config){ classSet=scanClass(config); controllerMap=initController(); } private static Set<Class<?>> scanClass(String config){ Properties props = PropsUtil.loadProps(config); return ClassUtil.getClass(props.getProperty("base-package")); } private static Map<Class<?>,Object> getService(){ Map<Class<?>,Object> serviceMap = new HashMap<Class<?>,Object>(); for(Class<?> service:classSet){ if(service.isAnnotationPresent(Service.class)){ try { serviceMap.put(service, service.newInstance()); } catch (InstantiationException | IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return serviceMap; } private static Map<String,Object> initController(){ Map<Class<?>,Object> serviceMap = getService(); Map<String,Object> controllerMap = new HashMap<String,Object>(); for(Class<?> controller:classSet){ if(controller.isAnnotationPresent(Controller.class)){ try { Object object = controller.newInstance(); if(controller.isAnnotationPresent(RequestMapping.class)){ controllerMap.put(controller.getAnnotation(RequestMapping.class).value(), object); } //注入service实例 for(Field field :object.getClass().getDeclaredFields()){ if(serviceMap.containsKey(field.getType())){ field.setAccessible(true); field.set(object, serviceMap.get(field.getType())); } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return controllerMap; } public static Map<String, Object> getControllerMap() { return controllerMap; } } 最后 回到DispatcherServlet,在初始化时调用这个工具类就可以了 @Override public void init(ServletConfig config){ System.out.println("初始化程序"); new InitClassUtil(config.getInitParameter("config")); } 最后在service()中,解析请求路径,并反射对应的controller类中的方法就OK了 public class DispatcherServlet extends HttpServlet { @Override public void init(ServletConfig config){ System.out.println("初始化程序"); new InitClassUtil(config.getInitParameter("config")); } @Override public void doGet(HttpServletRequest request,HttpServletResponse response){ doPost(request, response); } @Override public void doPost(HttpServletRequest req,HttpServletResponse resp){ Object result; String url =req.getServletPath(); String controllerUrl=url; Map<String,Object> controllerMap = InitClassUtil.getControllerMap(); while(!controllerMap.containsKey(controllerUrl)&&!controllerUrl.equals("")){ controllerUrl = controllerUrl.substring(0, controllerUrl.lastIndexOf('/')); } if(controllerUrl.equals("")){ System.out.println("找不到路径对应的controller"); } String methodUrl = url.substring(url.indexOf(controllerUrl)+controllerUrl.length());//获取方法路径 Object controller = controllerMap.get(controllerUrl); for(Method method:controller.getClass().getDeclaredMethods()){ if(method.isAnnotationPresent(RequestMapping.class)){ if(method.getAnnotation(RequestMapping.class).value().equals(methodUrl)){ List<Object> params = new ArrayList<Object>(); Enumeration<String> enumer = req.getParameterNames(); while(enumer.hasMoreElements()){ String paramName = enumer.nextElement(); params.add(req.getParameter(paramName)); } try { result=method.invoke(controller, params.toArray()); if(result instanceof ModelView){ ModelView modelView = (ModelView) result; for(Entry<String, Object> entry:modelView.getModelMap().entrySet()){ req.setAttribute(entry.getKey(),entry.getValue()); } req.getRequestDispatcher(modelView.getView()).forward(req, resp); return; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } @Override public void destroy(){ System.out.println("销毁程序"); } } 完成。 这段代码很粗糙,没有处理异常、有些判断并不完善,像扫描类时只扫描了.class 并没有Jar文件,而且解析路径反射调用的那段也应该抽象出来,而且方法中的参数赋值也必须按照顺序来,没有按照name或是@RequestParam 来注入。主要是因为太懒了,在JDK1.8中反射是可以获取方法的参数名,但是1.7获取参数名并不是很容易,Spring中获取参数名并注入其实调用了native方法(有待考证)。 看个乐得了。
|
请发表评论