实现一个依赖注入的小程序

先用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;
    }
    
}
完成的initClassUtil类

最后 回到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方法(有待考证)。

看个乐得了。