開始前的準備
開發環境: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