Spring入门 - 利用Servlet构建简单MVC逻辑

通过实现servlet接口函数来实现了简单的MVC逻辑,通过web.xml将servlet引入到tomcat容器,顺便加了暂时用不上的filter和listener;利用java标注装配controller和viewer,controller实现了简单的地址映射处理、ModelView数据回传,viewer实现了文本资源(html)读取及简单数据渲染。
1、pom.xml配置
由于需要利用web.xml引入servlet到容器,因此在war打包配置需要指明web.xml位置:

<plugin>
	<artifactId>maven-war-plugin</artifactId>
	<version>2.3</version>
	<configuration>
		<warSourceDirectory>WebContent</warSourceDirectory>
		<webXml>F:\java_demo\rumia_spring_web3\res\web.xml</webXml>
		<failOnMissingWebXml>true</failOnMissingWebXml>
	</configuration>
</plugin>

如果不想用web.xml,可以实现WebApplicationInitializer接口或继承AbstractAnnotationConfigDispatcherServletInitializer可以实现xml零配置。

2、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">

    <context-param> <!-- 更换上下文,从xml配置到java配置 -->
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <context-param> <!-- 利用java配置上下文直接指定配置类 -->
        <param-name>contextConfigLocation</param-name>
        <param-value>
            gensoku.config.appConfig
        </param-value>
    </context-param>

    <listener> <!-- java配置上下文需加入内容监听组件 -->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <listener> <!-- 测试的监听组件 -->
        <listener-class>gensoku.myListener</listener-class>
    </listener>

    <servlet> <!-- 引入自定义的主servlet -->
        <servlet-name>firstServlet</servlet-name>
        <servlet-class>gensoku.myServlet</servlet-class>
        
        <init-param> <!-- 自定义的初始配置参数,用于指定webConfig配置类 -->
            <param-name>webConfigClass</param-name>
            <param-value>
                gensoku.config.webConfig
            </param-value>
        </init-param>
		<!-- 创建servlet的优先级,无指定则按需创建 -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping> <!-- 指定上述主servlet的映射地址 -->
        <servlet-name>firstServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

    <filter> <!-- 测试用过滤器 -->
        <filter-name>firstFilter</filter-name>
        <filter-class>gensoku.myFilter</filter-class>
        <init-param>
            <param-name>charset</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>

    <filter-mapping> <!-- 指定过滤器的映射地址 -->
        <filter-name>firstFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

3、ModelView数据结构
用于controller将数据传给viewer,这里利用HashMap储存model数据的键值对。

package gensoku.data;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class ModelView 
{
    private String viewName;
    private Map<String, String> modelData;

    public ModelView()
    {
        modelData = new HashMap<>();
    }
	
	//读写viewname
    public void setViewName(String name)
    {
        viewName = name;
    }

    public String getViewName()
    {
        return viewName;
    }

	//读写model数据
    public void setModelData(String k, String v)
    {
        modelData.put(k, v);
    }

    public String getModelData(String k)
    {
        if(modelData.containsKey(k))
        {
            return modelData.get(k);
        }
        else
            return null;
    }

	//返回迭代器供viewer渲染时遍历model数据
    public Iterator getMapKeyIterator()
    {
        return modelData.keySet().iterator();
    }
}

4、webConfig配置类
利用spring的java标注技术对controller和viewer进行组件初始化,供后面servlet使用。

package gensoku.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import gensoku.*;

@Configuration
public class webConfig
{
	//初始化controller
    @Bean(name = "C")
    public myController retMyController()
    {
        return new myController();
    }

	//初始化viewer及资源路径
    @Bean(name = "V")
    public myViewer retMyViewer()
    {
        return new myViewer("F:\\java_demo\\rumia_spring_web3\\target\\TouhouProject-1.0\\WEB-INF\\classes\\pages\\");
    }
}

5、servlet前端控制器
在初始化时需要利用java装配上下文进行装配,拉取装配完成的controller、viewer;在实际处理请求时先将请求传给controller处理,然后将返回的ModelView数据传给viewer拉取、渲染页面,最后返回渲染结果。
一个惨痛的教训是springMVC并不将servlet当做bean参与装配初始化,而仅仅构建controller和viewer的bean;而且web.xml中的webConfig仅仅只是指出配置类,容器本身并不会主动进行装配——因此如果是自己实现的servlet就需要利用AnnotationConfigApplicationContext进行装配。

package gensoku;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import gensoku.data.ModelView;

public class myServlet implements Servlet 
{
	//servlet的controller和viewer
    private myController sController;
    private myViewer sView;

    public myServlet()
    {
        ;
    }

    @Override //servlet初始化
    public void init(ServletConfig config) throws ServletException 
    {
        System.out.println("A new servlet is born");
        try
        {
        	//读取web.xml中servlet的配置参数,得到配置类名
            String myWebConfig = config.getInitParameter("webConfigClass");
            System.out.println("Loading webConfig ... " + myWebConfig);
            
            //利用反射得到配置类并构建java装配上下文进行装配
            ApplicationContext context = new AnnotationConfigApplicationContext(Class.forName(myWebConfig));
            
            //拉取装配完成后的controller和viewer
            sController = context.getBean("C", myController.class);
            sView = context.getBean("V", myViewer.class);
	
			//检查是否拉取成功
            if(sController==null || sView==null)
            {
                System.out.println("Loading fail..");
            }

        }
        catch(Exception e)
        {
            System.out.println("Loading fail..");
            e.printStackTrace();
        }
    }

    @Override
    public ServletConfig getServletConfig()
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override //请求到来时的实际处理函数
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException 
    {
        HttpServletRequest hr = (HttpServletRequest)req;
        System.out.println("GET <- "+hr.getRequestURI());
        
        //检查controller和viewer是否正常
        if(sController==null || sView==null)
        {
        	//直接返回error信息
            PrintWriter pw = res.getWriter();
            pw.println("<h1> Error 1 </h1>");
            return;
        }
        
        //将请求交由controller处理,得到返回的ModelView数据
        ModelView mv = sController.process(req);
        if(mv == null)
        {
            mv = new ModelView();
            mv.setViewName("error.html");
        }

        System.out.println("Control -> view :  " + mv.getViewName());
        
        //将返回的ModelView数据交给viewer读取、渲染页面
        String content = sView.process(mv);
        if(content == null)
        {
            content = "<h1> Error 2 <h1>";
        }
	
		//将最终结果返回
        PrintWriter pw = res.getWriter();
        pw.println(content);
    }

    @Override
    public String getServletInfo() 
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void destroy() 
    {
        System.out.println("A servlet is dead");
    }
}

6、Controller控制器
将用户请求URL与处理函数进行绑定,在收到请求时用对应的函数进行处理,返回ModelView。

package gensoku;

import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Component;

import gensoku.data.ModelView;

@Component
public class myController 
{
    //(请求URL,处理函数)映射
    private Map<String, Method> requestMapping;

	//处理函数1
    public ModelView retMain(ServletRequest sr)
    {
        ModelView mv = new ModelView();
        mv.setViewName("home.html"); //设置ModelView的视图名
        mv.setModelData("nowTime", retTime()); //设置ModelView的模型数据
        mv.setModelData("imgsrc", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1596036560048&di=27f10e87e0df7e82da6800a7636b267d&imgtype=0&src=http%3A%2F%2Fimg1.imgtn.bdimg.com%2Fit%2Fu%3D4021827866%2C61334938%26fm%3D214%26gp%3D0.jpg");
        return mv;
    }

	//处理函数2
    public ModelView retRumia(ServletRequest sr)
    {
        ModelView mv = new ModelView();
        mv.setViewName("rumia.html");
        mv.setModelData("nowTime", retTime());
        mv.setModelData("imgsrc", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1596036659527&di=be8e5c541b833ad3ee348819796e7a4a&imgtype=0&src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2Fa626eafc37d29c79f16a32b8f66460280f8e6b57.jpg");
        return mv;
    }

	//初始化
    public myController()
    {
        requestMapping = new HashMap<>();
        //为请求url添加相应的处理函数
        addMapping("/TouhouProject/", "retMain");
        addMapping("/TouhouProject/best", "retRumia");
    }

    //添加(URL,处理函数)映射
    public void addMapping(String url, String funcName)
    {
        try
        {
        	//利用反射取得类函数指针
            Class cl = getClass();
            Method m = cl.getMethod(funcName, ServletRequest.class); //注意形参的限定
            if(m.getReturnType() == ModelView.class) //检查函数的返回值是否为ModelView
            {
                System.out.println(url +" -> "+ funcName);
                requestMapping.put(url, m);
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    //根据请求url调用相应的处理函数
    public ModelView process(ServletRequest sr)
    {
        try
        {
        	//取得请求URL
            String targetUrl = ((HttpServletRequest)sr).getRequestURI();
            if(requestMapping.containsKey(targetUrl)) //检查映射是否包含该URL
            {
            	//如果包含则取得函数指针并执行
                Method m = requestMapping.get(targetUrl);
                return (ModelView) m.invoke(this, sr);
            }
            else
                return null;
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

	//返回当前日期、时间
    private String retTime()
    {
        DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.ENGLISH);
        DateFormat tf = DateFormat.getTimeInstance(DateFormat.LONG, Locale.ENGLISH);
        return df.format(new Date())+" "+tf.format(new Date());
    }
}

7、viewer渲染器
根据controller返回的ModelView的视图名读取本地文本文件(html),然后根据ModelView的模型数据对读取结果进行渲染并返回。

package gensoku;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.Iterator;

import org.springframework.stereotype.Component;

import gensoku.data.ModelView;

@Component
public class myViewer 
{
    private String resPath;

    public myViewer()
    {
        resPath = "";
    }

    public myViewer(String p)
    {
        resPath = p;
    }

    public String process(ModelView mv)
    {
        try
        {
            //读取视图名所指文件
            String content = readTextFile(resPath + mv.getViewName());
            //简单渲染并返回
            content = renderView(content, mv);
            return content;
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

	//读取文本文件
    String readTextFile(String filepath)
    {
        try
        {
            FileInputStream fs = new FileInputStream(filepath);
            InputStreamReader isr = new InputStreamReader(fs);
            BufferedReader br = new BufferedReader(isr);
            String content = "", buf;
            while((buf = br.readLine())!=null)
            {
                content = content + buf;
            }
            br.close();
            return content;
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

	//简单渲染(其实就是字符串匹配替换)
    private String renderView(String wait, ModelView mv)
    {
        try
        {
            String ans = wait;
            //轮询ModelView的模型数据
            Iterator it = mv.getMapKeyIterator();
            while(it.hasNext())
            {
            	//将文本中所有匹配[@key]的子串替换为value
                String k = (String)it.next();
                ans = ans.replace(String.format("[@"+ k +"]"), mv.getModelData(k));
            }
            return ans;
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return wait;
        }
    }
}

8、filter过滤器组件

package gensoku;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class myFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException 
    {
        System.out.println("A new filter is born");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException 
    {
        System.out.println("Filter gets a request");
        chain.doFilter(request, response); //移交给下一个filter
    }

    @Override
    public void destroy() 
    {
        System.out.println("A filter is dead");
    }
    
}

9、listener监听器组件

package gensoku;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class myListener implements ServletContextListener 
{
    @Override
    public void contextInitialized(ServletContextEvent sce) 
    {
        System.out.println("Listener context initialized");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) 
    {
        System.out.println("Listener context destroyed");
    }
}

10、HTTP页面
其中形如[@xxxx]的字符串为需要在渲染时填充数据的标识。

<h1>Welcome home !</h1>
<h2>[@nowTime]<h2>
<img src="[@imgsrc]">
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值