0
点赞
收藏
分享

微信扫一扫

2 手写实现SpringMVC,第二节:自定义注解及反射赋值


2 手写实现SpringMVC,第二节:自定义注解及反射赋值_java

还是回到最终要实现的效果。

可以发现,这里面使用了大量的自定义注解,并且还有autuwire的属性也需要被赋值(Spring的IOC功能)。

先来创建自定义注解



2 手写实现SpringMVC,第二节:自定义注解及反射赋值_xml_02




注意,根据不同的注解使用的范围来定义@Target,譬如Controller,Service能注解到类,RequestMapping能注解到类和方法,AutoWired只能注解到属性。

Autowired

/**
* Created by wuwf on 17/6/27.
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
String value() default "";
}


Controller

import java.lang.annotation.*;

/**
* Created by wuwf on 17/6/27.
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {

String value() default "";
}

RequestMapping

import java.lang.annotation.*;

/**
* Created by wuwf on 17/6/27.
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}

RequestParam


import java.lang.annotation.*;

/**
* Created by wuwf on 17/6/27.
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value() default "";

boolean required() default true;
}

Service

import java.lang.annotation.*;

/**
* Created by admin on 17/6/27.
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}


创建基本功能类


2 手写实现SpringMVC,第二节:自定义注解及反射赋值_mvc_03


添加2个最简单的Service和实现类,模拟查询和修改。
ModifyService

public interface ModifyService {

String add(String name, String addr);
String remove(Integer id);
}

QueryService

public interface QueryService {
String search(String name);
}

ModifyServiceImpl

import com.tianyalei.mvc.annotation.Service;
import com.tianyalei.mvc.service.ModifyService;

/**
* Created by wuwf on 17/6/27.
*/
@Service
public class ModifyServiceImpl implements ModifyService {
@Override
public String add(String name, String addr) {
return "invoke add name = " + name + " addr = " + addr;
}

@Override
public String remove(Integer id) {
return "remove id = " + id;
}
}


QueryServiceImpl

package com.tianyalei.mvc.service.impl;

import com.tianyalei.mvc.annotation.Service;
import com.tianyalei.mvc.service.QueryService;

/**
* Created by admin on 17/6/27.
*/
@Service("myQueryService")
public class QueryServiceImpl implements QueryService {
@Override
public String search(String name) {
return "invoke search name = " + name;
}
}



WebController

package com.tianyalei.mvc.controller;

import com.tianyalei.mvc.annotation.Autowired;
import com.tianyalei.mvc.annotation.Controller;
import com.tianyalei.mvc.annotation.RequestMapping;
import com.tianyalei.mvc.annotation.RequestParam;
import com.tianyalei.mvc.service.ModifyService;
import com.tianyalei.mvc.service.QueryService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* Created by wuwf on 17/6/28.
*/
@Controller
@RequestMapping("/web")
public class WebController {
@Autowired("myQueryService")
private QueryService queryService;
@Autowired
private ModifyService modifyService;

@RequestMapping("/search")
public void search(@RequestParam("name") String name, HttpServletRequest request, HttpServletResponse response) {
String result = queryService.search(name);
out(response, result);
}

@RequestMapping("/add")
public void add(@RequestParam("name") String name,
@RequestParam("addr") String addr,
HttpServletRequest request, HttpServletResponse response) {
String result = modifyService.add(name, addr);
out(response, result);
}

@RequestMapping("/remove")
public void remove(@RequestParam("name") Integer id,
HttpServletRequest request, HttpServletResponse response) {
String result = modifyService.remove(id);
out(response, result);
}

private void out(HttpServletResponse response, String str) {
try {
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(str);
} catch (IOException e) {
e.printStackTrace();
}
}
}



至此,项目的基本结构就是这样了。下面需要做的就是正文了,如何让上面的注解生效,如何让请求根据地址进到对应的方法。

扫描项目类,并实例化参数


这一步的目的是得到一个类名和类实例的映射对象,如 "webController"->WebController的实例,"searchService"->SearchServiceImpl的实例,类似于Spring的BeanFactory,将对应的实例赋给对应的beanName。譬如,searchService在Controller中被定义了,它并不需要去做new这个对象的处理,而是由我们主动注入进去。前提就是我们需要保存一个beanName->bean对象的映射关系。这种处理就是ioc,也就是早期在spring配置文件application.xml经常配的bean id="XXX" class="XXX".


下面来看怎么实例化参数。


思路就是扫描目录下所有需要被我们的山寨Spring托管的类,并将其实例化,然后保存映射关系。在Spring里就是所有标注了@Component注解的类,需要被托管。我们这里就只处理@Controller和@Service注解的类,然后实例化。


扫描所有需要被映射的类


修改一下web.xml,指定我们需要扫描的包。


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">

<servlet>
<servlet-name>dispatchServlet</servlet-name>
<servlet-class>com.tianyalei.mvc.DispatcherServlet</servlet-class>
<init-param>
<param-name>scanPackage</param-name>
<param-value>com.tianyalei.mvc</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatchServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

</web-app>

这里添加了一个init-param,属性名为scanPackage(可任意起名),value为包名。



然后在DispatcherServlet的init方法中,接收并处理。


package com.tianyalei.mvc;

import com.tianyalei.mvc.annotation.Controller;
import com.tianyalei.mvc.annotation.Service;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
* Created by wuwf on 17/6/28.
* 入口Sevlet
*/
public class DispatcherServlet extends HttpServlet {
private List<String> classNames = new ArrayList<>();

@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("我是初始化方法");
scanPackage(config.getInitParameter("scanPackage"));
System.out.println(classNames);
}

/**
* 扫描包下的所有类
*/
private void scanPackage(String pkgName) {
//获取指定的包的实际路径url,将com.tianyalei.mvc变成目录结构com/tianyalei/mvc
URL url = getClass().getClassLoader().getResource("/" + pkgName.replaceAll("\\.", "/"));
//转化成file对象
File dir = new File(url.getFile());
//递归查询所有的class文件
for (File file : dir.listFiles()) {
//如果是目录,就递归目录的下一层,如com.tianyalei.mvc.controller
if (file.isDirectory()) {
scanPackage(pkgName + "." + file.getName());
} else {
//如果是class文件,并且是需要被spring托管的
if (!file.getName().endsWith(".class")) {
continue;
}
//举例,className = com.tianyalei.mvc.controller.WebController
String className = pkgName + "." + file.getName().replace(".class", "");
//判断是否被Controller或者Service注解了,如果没注解,那么我们就不管它,譬如annotation包和DispatcherServlet类我们就不处理
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class)) {
classNames.add(className);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

}
}
}

@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 {
out(resp, "请求到我啦");
}

private void out(HttpServletResponse response, String str) {
try {
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(str);
} catch (IOException e) {
e.printStackTrace();
}
}

}


这里定义了一个scanPackage的方法,是用于递归处理web.xml定义的需要被扫描的包下的所有等待被接管的类。我们将所有需要被托管的类,保存到一个list中。供下一步实例化时使用。


写完后,执行看一下被扫描的托管类:



2 手写实现SpringMVC,第二节:自定义注解及反射赋值_xml_04



这样就取到了所有被Controller和Service标注的类了。


实例化bean


我们取到了所有被托管的类,下一步就是要实例化这些类了,也就是像Spring的ioc一样,按规则给bean注入实例值。


像如果在任何地方定义webController,那么我们就默认给他赋值为WebController的实例,定义了modifyService,那么就默认给它注入ModifyServiceImpl的实例。倘若用户自定义了beanName,那么就给beanName注入值,如果不定义,就走上面默认的。如果在ModifyServiceImpl上写了@Service("abc"),那么我们就保存一个"abc"->ModifyServiceImpl的映射,如果没有定义,就保存一个"modifyService"->ModifyServiceImpl的映射。


保存映射的目的是在将来给AutoWired注解的属性注入值时,好根据beanName来判断该注入什么实例。



添加一个map保存beanName和实例的映射。


private Map<String, Object> instanceMapping = new HashMap<>();

添加doInstance

@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("我是初始化方法");
scanPackage(config.getInitParameter("scanPackage"));

doInstance();

System.out.println(instanceMapping);
}


/**
* 实例化
*/
private void doInstance() {
if (classNames.size() == 0) {
return;
}
//遍历所有的被托管的类,并且实例化
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
//如果是Controller
if (clazz.isAnnotationPresent(Controller.class)) {
//举例:webController -> new WebController
instanceMapping.put(lowerFirstChar(clazz.getSimpleName()), clazz.newInstance());
} else if (clazz.isAnnotationPresent(Service.class)) {
//获取注解上的值
Service service = clazz.getAnnotation(Service.class);
//举例:QueryServiceImpl上的@Service("myQueryService")
String value = service.value();
//如果有值,就以该值为key
if (!"".equals(value.trim())) {
instanceMapping.put(value.trim(), clazz.newInstance());
} else {//没值时就用接口的名字首字母小写
//获取它的接口
Class[] inters = clazz.getInterfaces();
//此处简单处理了,假定ServiceImpl只实现了一个接口
for (Class c : inters) {
//举例 modifyService->new ModifyServiceImpl()
instanceMapping.put(lowerFirstChar(c.getSimpleName()), clazz.newInstance());
break;
}
}
}

} catch (Exception e) {
e.printStackTrace();
}
}
}


private String lowerFirstChar(String className) {
char[] chars = className.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}


重新启动执行,来看看映射的结果:

2 手写实现SpringMVC,第二节:自定义注解及反射赋值_xml_05

可以看到,已经按照我们的规则保存好了beanName和实例之间的映射关系了。

下一步就是处理AutoWired了,真正的ioc赋值。请看下一篇。

举报

相关推荐

0 条评论