开始前的准备
开发环境:jdk8+tomcat7+IDEA+maven
所需jar包:Servlet2.x
那么现在就开始吧
开发过程(绝对详细)
首先,启动IDEA创建一个maven项目,并创建下图所示的包结构,包名你们自己起就可以了
然后配置pom.xml,在里面引入Servlet依赖就可以了,这里我引入的2.x的,你们不要引入3.0的,虽然3.0的可以不需要web.xml配置,不过这里需要加载配置文件的
</dependency >
<dependency >
<groupId > javax.servlet</groupId >
<artifactId > servlet-api</artifactId >
<version > 2.5</version >
<scope > provided</scope >
</dependency >
在Action中创建一个类,这里我创建的类为DemoAction.java,该类的作用在SSM框架中等同于Controller类,在里面写入下面代码:
@Controller ("demoAction" )
@RequestMapping ("/web" )
public class DemoAction {
@Autowired ("tom" )
private IDemoService demoService;
@RequestMapping ("/query" )
public void query (HttpServletRequest request, HttpServletResponse response,
@RequestParam ("name") String name) throws IOException {
request.setCharacterEncoding("utf-8" );
response.setCharacterEncoding("utf-8" );
String result = demoService.get(name);
PrintWriter writer = response.getWriter();
writer.print(result);
writer.close();
}
}
这里要注意一点,里面我所用到的注解都是需要自己实现的,而不是Spring框架中的
然后我们来将所有类一个个的写上,首先先写Service吧,IDemoService是一个接口类,代码如下:
public interface IDemoService {
public String get (String name);
}
它有一个实现类DemoService:
@Service ("tom" )
public class DemoService implements IDemoService {
public String get (String name){
return "my name is " + name;
}
}
到这里,可以看到前面已经使用了很多的注解,但是都是报错的,所以现在我们来一个个的实现这些注解吧,很简单,我就直接贴到下面了:
Controller.java
@Target ({ElementType.TYPE})
@Retention (RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "" ;
}
RequestMapping.java
@Target ({ElementType.TYPE, ElementType.METHOD})
@Retention (RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "" ;
}
RequestParam.java
@Target ({ElementType.PARAMETER})
@Retention (RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value() default "" ;
}
Autowired.java
@Target ({ElementType.FIELD})
@Retention (RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
String value() default "" ;
}
Service.java
@Target ({ElementType.TYPE})
@Retention (RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "" ;
}
到这里为止,所有的代码都没有报错了,这个时候我们来配置web.xml,同Spring,使用的时候需要配置一个核心Servlet类:DispatcherServlet,先暂时不管有没有,先按照Spring的方式来配置:
<servlet >
<servlet-name > mymvc</servlet-name >
<servlet-class > org.chengxi.mvnframework.servlet.DispatcherServlet</servlet-class >
<init-param >
<param-name > contextConfigLocation</param-name >
<param-value > application.properties</param-value >
</init-param >
<load-on-startup > 1</load-on-startup >
</servlet >
<servlet-mapping >
<servlet-name > mymvc</servlet-name >
<url-pattern > /*</url-pattern >
</servlet-mapping >
在配置中我给这个DispatcherServlet初始化了一个参数contextConfigLocation,它的值是属性文件的位置,application.properties和applicationContext.xml功能一样,提供Spring所需的一些属性值,这里就简单的使用键值对的形式进行解析而不解析XML了。先来编写这个属性文件吧,里面很简单,就一个键值对,表示需要进行IOC/DI的类所在的包:
//application.properties
scanPackage = org.chengxi .demo
该配置文件需要放在main/resources下面;然后接下来就是真正的关键实现了:DispatcherServlet的实现,它就是一个Servlet,先将它的基础打起来:
public class DispatcherServlet extends HttpServlet {
public void init (){}
public void doGet (){}
public void doPost (){}
public void destroy (){}
}
然后我们来一步步的实现,首先我们需要在init初始化阶段获取属性文件对应的位置,即在web.xml中初始化的ContextConfigLocation变量,通过ServletConfig来获取:
String application = config.getInitParameter("contextConfigLocation" );
在获取了属性文件的位置之后,就来解析该属性文件里的值,在这里也就是获取需要进行IOC/DI的类的包:
InputStream is = this .getClass().getClassLoader().
getResourceAsStream(location);
p.load(is );
if (null != is ){
is .close();
}
这里的p定义成一个成员变量,以便于后面使用,获取了Properties对象之后,就可以获取需要扫描的包,然后将所有的包进行扫描,并将所有的类都保存起来:
URL url = this.getClass ().getClassLoader ().getResource ("/" + packageName.replaceAll ("\\." ,"/" ))
File dir = new File(url.getFile ())
for(File f: dir.listFiles ()){
if(f.isDirectory ()){
doScanner(packageName + "." + f.getName ())
}
else{
classNames.add (packageName + "." + f.getName ().replace (".class" ,"" ).trim ())
}
}
这里的classNames是一个List,用于保存指定包下的所有的类的位置
接下来,就需要我们来将该包下的所有Controller注解和Service注解修饰的类都进行实例化,并一一与beanName进行对应(这里我们需要知道,Spring中维护这一个IOC容器,依赖注入就是通过该容器根据beanName进行控制反转获取来进行对应实例化的),ioc容器的定义:private Map<String, Object> ioc = new HashMap<String, Object>();
if(classNames.isEmpty ()){
return
}
for(String className: classNames){
Class<?> clazz = Class.forName (className)
//只初始化controller容器和service容器
if(clazz.isAnnotationPresent (Controller.class )){
//默认首字母小写称为beanName
String beanName = lowerFirst(clazz.getSimpleName ())
ioc.put (beanName, clazz.newInstance ())
}
else if(clazz.isAnnotationPresent (Service.class )){
//第一种形式:默认首字母小写
//第二种形式:注解自己提供名字(优先)
//第三种形式:利用接口本身全称作为key,其对应实现类作为值
Service service = clazz.getAnnotation (Service.class )
String beanName = service.value ()
Object instance = clazz.newInstance ()
//注解本身提供名字@service("sss" )
if(!"" .equals (beanName.trim ())){
ioc.put (beanName, instance)
continue
}
Class<?>[] interfaces = clazz.getInterfaces ()
for(Class<?> inter: interfaces){
ioc.put (inter.getName (), instance)
}
}
}
然后将使用Autowired注解修饰了的属性进行依赖注入:
if(ioc.isEmpty ()){
return
}
for(Map.Entry <String, Object> entry: ioc.entrySet ()){
//获取所有字段
Field[] fields = entry.getValue ().getClass ().getDeclaredFields ()
for(Field field: fields){
if(!field.isAnnotationPresent (Autowired.class )){
continue
}
Autowired autowired = field.getAnnotation (Autowired.class )
String beanName = autowired.value ().trim ()
if("" .equals (beanName)){
beanName = field.getType ().getName ()
}
//即使是private也要进行强制注入
field.setAccessible (true)
//开始赋值
field.set (entry.getValue (), ioc.get (beanName))
}
}
到这里的时候,就差最后一步了:url与method进行对应,虽然这里可以使用Map<String,Method>
进行保存对应,但是需要注意的是,到后面进行method.invoke的时候需要的参数就无法获得了,所以这里我们定义一个外部类:
class Handler{
protected Object controller;
protected Method method;
protected Pattern pattern;
protected Map<String, Integer> paramIndexMapping;
protected Handler (Object controller, Method method, Pattern pattern){
this .controller = controller;
this .method = method;
this .pattern = pattern;
paramIndexMapping = new HashMap<String, Integer>();
putParamIndexMapping(method);
}
public void putParamIndexMapping (Method method){
Annotation[][] pa = method.getParameterAnnotations();
for (int i=0 ; i<pa.length; i++){
for (Annotation a: pa[i]){
if (a instanceof RequestParam){
String paramName = ((RequestParam) a).value ();
if (!"" .equals(paramName)){
paramIndexMapping.put(paramName,i);
}
}
}
}
Class<?>[] paramsTypes = method.getParameterTypes();
for (int i=0 ; i<paramsTypes.length; i++){
Class<?> type = paramsTypes[i];
if (type == HttpServletRequest.class || type == HttpServletResponse.class){
paramIndexMapping.put(type.getName(), i);
}
}
}
}
然后我们在DispatcherServlet中直接定义一个List<HandlerMapping>
进行保存即可,进行保存的方法如下:
if(ioc.isEmpty ()){
return
}
for(Map.Entry <String, Object> entry: ioc.entrySet ()){
Class<?> clazz = entry.getValue ().getClass ()
if(!clazz.isAnnotationPresent (Controller.class )){
continue
}
String url = ""
if(clazz.isAnnotationPresent (RequestMapping.class )){
RequestMapping requestMapping = clazz.getAnnotation (RequestMapping.class )
url = requestMapping.value ()
}
Method[] methods = clazz.getMethods ()
for(Method method: methods){
if(!method.isAnnotationPresent (RequestMapping.class )){
continue
}
RequestMapping requestMapping = method.getAnnotation (RequestMapping.class )
String regex = ("/" + url + requestMapping.value ()).replaceAll ("/+" ,"/" )
Pattern pattern = Pattern.compile (regex)
handlerMapping.add (new Handler(entry.getValue (), method, pattern))
}
}
最后一步:请求的处理,这里定义一个方法来进行处理:doDispatcherServlet(req, resp),处理过程如下:
Handler handler = getHandler(req)
if(handler == null){
resp.getWriter ().print ("404 not found" )
return
}
//获取方法的参数列表
Class<?>[] paramTypes = handler.method .getParameterTypes ()
//保存所有需要自动赋值的参数值
Object[] paramValues = new Object[paramTypes.length ]
Map<String, String[]> params = req.getParameterMap ()
for(Map.Entry <String, String[]> param: params.entrySet ()){
String value = Arrays.toString (param.getValue ()).replaceAll ("\\[|\\]" ,"" ).replaceAll (",\\s" ,"," )
if(!handler.paramIndexMapping .containsKey (param.getKey ())){
continue
}
int index = handler.paramIndexMapping .get (param.getKey ())
paramValues[index] = convert(paramTypes[index], value)
}
//设置方法中的request和response对象
int reqIndex = handler.paramIndexMapping .get (HttpServletRequest.class .getName ())
paramValues[reqIndex] = req
int respIndex = handler.paramIndexMapping .get (HttpServletResponse.class .getName ())
paramValues[respIndex] = resp
handler.method .invoke (handler.controller , paramValues)
其中里面用到了几个方法,定义在下面,就不一一介绍了,这几个方法还是很好理解的:
public Object convert (Class<?> type, String value ){
if (Integer.class == type){
return Integer.valueOf(value );
}
return value ;
}
public Handler getHandler (HttpServletRequest req){
if (handlerMapping.isEmpty()){
return null ;
}
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replace(contextPath, "" ).replaceAll("/+" ,"/" );
for (Handler handler: handlerMapping) {
Matcher matcher = handler.pattern.matcher(url);
if (!matcher.matches()){
continue ;
}
return handler;
}
return null ;
}
private String lowerFirst (String str){
char [] chars = str.toCharArray();
chars[0 ] += 32 ;
return String.valueOf(chars);
}
最后总结
写这篇博客主要是用于自己记录的,大致的实现应该介绍的比较详细了,代码里面也做了很多的笔记,你们可以多看看。我已经将代码上传到了CSDN上,你们可以下载边看边学,地址:mymvc