标签:SpringMVC、SSM、拦截器、代理对象
1.SpringMVC概述
- 是什么?
为了让web开发更简单、更高效、便于维护升级,引入了SpringMVC。
从名字的角度看,SpringMVC是Spring框架的一个子模块,可以理解为Spring+MVC,这里的MVC是Model、View、Controller是SpringMVC三个核心概念对象的缩写。
从底层实现来看,SpringMVC可以看成是Servlet的高级版本,是加上一些其他功能的框架而已。
从容器的角度看,Spring是一个容器,通过ioc控制管理对象,因此SpringMVC也是一个容器,不过存放的是控制器对象@Controller。
- 核心概念?
在Springmvc中,有三个核心概念,如上面的图所示:
第一个:Controller控制器
该控制器分为用于调度的中央(前端)控制器和用于响应的后台@Controller对象
第二个:Model数据
Model数据是后台数据调用Service方法得到的数据,将其放在Springmvc容器中。
第三个:View视图
View是视图,用于页面转发等功能。
2.SpringMVC配置初探
第一步:创建maven-web项目,加入Spring-mvc依赖,servlet依赖,jsp依赖
第二步:web.xml注册中央控制器,设置为初始时加载设置;
中央控制器控制范围→servlet mapping、重设springmvc配置文件路径乱码过滤器。
<servlet> <servlet-name>Spring_mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--表示tomcat服务器启动时就创建该对象,数字越低优先级越高--> <load-on-startup>1</load-on-startup> <!--等价于: webapplicationcontext ctx = new classpathxmlapplicationcontext(“springmvc.xml”) getServletContext().setAttribute(key,ctx) 容器底部可以看成是map.put("Mycontroller对象",mycontroller)--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:Spring_mvc.xml</param-value> </init-param> </servlet> <!-- 有两种映射方式,第一种:常规的通过路径,第二种,通过通配符--> <servlet-mapping> <servlet-name>Spring_mvc</servlet-name> <url-pattern>/</url-pattern> <!--<url-pattern>*.dO</url-pattern>--> </servlet-mapping> <filter> <filter-name>中文乱码过滤器</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <!--设置项目中使用的字符编码--> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> <init-param> <!--强制HttpServletRequest请求对象使用encoding编码--> <param-name>forceRequestEncoding</param-name> <param-value>true</param-value> </init-param> <init-param> <!--强制HttpServletResponse应答对象使用encoding编码--> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>中文乱码过滤器</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
【注意】在post方法中可能会出现乱码情况,此时需要配置上面的过滤器。
第三步:写处理方法,类头加上@Controller注解,方法加上@RequestMapping地址地址映射;
@Controller public class mycontrol { @RequestMapping("/some.do") public ModelAndView dosome(String name,int age) { System.out.println("mycontroller的dosome方法执行了"); ModelAndView mv = new ModelAndView(); mv.addObject("myname",name); //添加数据,框架在最后把该数据放到request作用域: mv.addObject("myage",age); mv.setViewName("show");//转发 return mv; } }
第五步:配置Springmvc配置文件,定义要创建哪些对象,配置拦截器、视图解析器。
<context:component-scan base-package="com.zhuge.control"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.zhuge.handler.myintercept"/> </mvc:interceptor> </mvc:interceptors>
第六步:创建结果页面,如show.jsp,此时springmvc自动转发到该页面。
第七步:配置Tomcat服务器,启动。
【注意1】中央控制器即dispatcherservlet,又名前端控制器,其本质是一个Servlet,从源代码看,其父类继承自HttpServlet。
【注意2】如果不配置,dispatcherservlet会在[/WEB-INF/Spring_mvc-servlet.xml]目录下寻找对应的XX.xml配置文件,因此一般情况下会自己手动重新设置。
【注意3】dispatcherservlet设置开机启动,因为在他启动时会扫描对应包然后创建controller对象,放在Springmvc容器中以便使用,以此减少后续访问的响应时间,从源代码看,其会调用init()方法读取配置文件然后获得WebApplicationContext容器,将对象生成放到ServletContext中。
那么你可能已经猜到从浏览器输入http://localhost:8080/myweb/index.jsp,然后点击超链接假设是http://localhost:8080/myweb/some.do,然后会发生什么?
首先,此时springmvc容器已经有中央控制器对象和一个controller对象,然后当遇到该请求时,中央控制器查找能处理/some.do的对象和方法【内部本质是中央处理器调用其doDispatch方法执行目的方法,类似于Servlet的service方法】,这里是mycontrol的dosome方法,然后进行执行该方法,返回ModelAndView,经过视图解析最后返回给用户。
3.SpringMVC间接访问设置_视图
从上面的实验,我们不难发现,直接通过http://localhost:8080/myweb/show.jsp也能直接访问到我们系统中的资源,显然这是不合理的,因为有可能我们不想该资源直接给用户,要对其进行控制。
因此,如何设置只通过controller对象访问呢?
值得注意的是,有一个事实是,位于webapp→WEB-INF下的文件天然不能通过上述方法访问。
考虑到有可能多个资源,故可以在webapp/WEB-INF新建view文件夹,放入show.jsp、other.jsp等视图页面【此时springmvc配置文件路径也需要修改】。
3.1 视图
3.1.1 视图解析器
就是视图文件的路径,如这里的show.jsp完整路径为webapp/WEB-INF/view/show.jsp,在配置之后,我们在controller对象中只需使用逻辑名show即可,不用写全名了,其背后就是简单的字符串拼接操作。
mv.setViewName("show");
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean>
3.1.2 框架的setViewName方法
当调用setViewName方法时,等价于以前的手动转发操作:
request.getrequestdispatcher("/show.jsp").forward(...)
3.1.3 设置视图的两种方法
法1:setViewName方法→转换为视图
mv.setViewName("show");
法2:直接调用set方法→新建视图
mv.setView(new RedirectView("/a.jsp"));
4.处理器方法_@RequestMapping_方法参数
这里讲解一下后端控制器的方法相关参数、返回值。
@Controller
@RequestMapping(value = "/test")
public class mycontrol {
@RequestMapping(value = "/some.do")//类似于Servlet.doGet()
//@RequestMapping(value = {"/other.do","/zhngsan.do"})
public ModelAndView dosome(){//service请求
ModelAndView mv = new ModelAndView();
//等价于request.setattribute(msg,欢迎来到北京大学)
mv.addObject("msg","欢迎来到北京大学");
mv.addObject("fun","执行了dosome方法");
mv.setViewName("show");
return mv;
}
}
第一步:类头加@Controller,表明我是来处理用户请求的控制器对象;
第二步:类头加上模块名,表明我这个对象中的方法都是处理/test开头的uri的;
第三步:dosome方法头加上地址名,表明dosome是处理网站根地址/test/some.do的uri的;
注意的是,这里的value值是一个字符串数组,可以处理多个uri;
第四步:写法
- 确定返回值类型和方法参数
- 值得注意的是,方法头requestmapping有一个method属性,为枚举值,表示可以接收的方法:
- method = RequestMethod.POST
- 缺省的话两个方法都支持,上述显式请求则只会允许post请求;
- 执行service业务方法
- 返回
4.1 方法参数_逐个接收
方法参数可以认为是3+1,3个自带参数,一个用户提交的参数:
HttpServletRequest request,HttpServletResponse response,HttpSession session,用户提交参数
【注意】以上四种参数可以在方法中随意混用。
4.1.1 同名赋值
假设用户点击如下的连接,那么怎么获取zhangsan和18的值呢?
http://localhost:8080/myweb/dome.do?name=zhangsan&age=18
回想以前写Servlet,我们通过request.getparametername("name"),因此在springmvc框架中,也有对应的方法:
@RequestMapping(value = "/received.do",method = RequestMethod.GET)
public ModelAndView dosome(String name,int age){//service请求
//index.jsp
<form action="received.do" method="post">
姓名:<input type="text" name="name"><BR/>
年纪:<input type="text" name="age"><BR/>
<input type="submit" value="点我提交参数">
</form>
不难发现,在处理器方法中,我们引入与发过来的请求的同名参数,通过这样的方式就可以一步到位获取对应的参数值;
其本质:springmvc框架通过DispatcherServlet调用mycontrol对象的dosome方法,调用时,按参数名称对应赋值,其中涉及到了自动转换功能!
public ModelAndView dosome(String name,int age){//service请求
//等价于:
String name = request.getParameter("name");
String age= request.getParameter("age");
你可能注意到这里转换的age是String类型,需要的是int类型:
String name = request.getParameter("name");
int age= Integer.valueof(request.getParameter("age"));
那么当用户输入的age一栏参数为空时,由于是int,显然无法赋值,发生空指针异常,那么怎么解决呢?
可以把它修改为Integer类型,当然也可以使用String,不过在实际开发中,我们要考虑到应该按照逻辑尽早暴露错误,而不是掩盖错误。
public ModelAndView dosome(String name,Integer age){//service请求
4.1.2 别名赋值
如果index.jsp的参数为rname,这里方法的形参为name,怎么映射呢?
public ModelAndView dosome(@RequestParam(value = "rname") String name,
@RequestParam(value = "rage") int age){//service请求
ModelAndView mv = new ModelAndView();
mv.addObject("myname",name);
mv.addObject("myage","age");
mv.setViewName("show");
return mv;
}
可以利用注解重命名参数,完成映射:
关于@RequestParam
* 属性1:value 表示请求中的参数名
* 属性2:required ,默认取true表示本次请求中必须包含参数,否则报错。
* 如果要求请求必须包括请求的参数,选true,否则选false,此时复制URL地址到浏览器也可以访问
4.2 方法参数_接收对象
顾名思义,该设置即把一个对象传入到控制器方法作为形参,在这个情况下,有如下要求:
第一:该对象的属性名必须和前台的形参名一致;
第二:此时RequestParam不能用在这里。
此时,框架会通过无参构造方法创建该对象,然后利用属性的setter方法进行赋值,实现set注入。
@RequestMapping(value = "/objectreceived.do")
public ModelAndView receiveobjec(Student stu){
System.out.println("getname:"+stu.getName()+"---getage:"+stu.getAge());
ModelAndView mv = new ModelAndView();
mv.addObject("myname",stu.getName());
mv.addObject("myage",stu.getAge());
mv.addObject("mystu",stu);
mv.setViewName("show");
return mv;
}
public class Student {
//要求这个名字和请求中的参数名一样
private String name;
private int age;
public String getName() {
return name;
}
public Student() {
System.out.println("我时student的无参构造方法");
}
public void setName(String name) {
System.out.println("我时setname"+name);
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public int getAge() {
return age;
}
public void setAge(int age) {
System.out.println("setage"+age);
this.age = age;
}
}
【注意】该方法可以接收多个对象;
【注意2】该方法非常适合形参个数很多的情况。
5.处理器方法_返回值类型
5.1 ModelAndView
其中model表示数据,而view表示视图,适用于需要返回数据和视图页面的情况。
5.2 String视图
该方法用于仅返回视图,不返回数据:
@RequestMapping(value = "/returnString.do")
public String doreturn @RequestParam(value = "rname") String name,
@RequestParam(value = "rage") Integer age){//service请求
System.out.println("名字是"+name+"age="+age);
return "show";
}
值得注意的是,此时show.jsp页面不能使用该方法的形参,因为不返回数据,那么如果想要返回视图,同时引用数据,还要在String方法的前提下,该怎么处理呢?
其实,前面已经讲到,我们的model的本质就是一句话:
mv.addObject("myname",stu.getName());
上述本质是框架将设置的key-value加入到request的作用域中,等价于:
request.setAttribute("myname",stu.getName());
因此,在不使用model的语法情况下,我们可以直接手动添加数据到request作用域:
request.setAttribute("myname",name); request.setAttribute("myage",age);
这样,在show.jsp即可引用:
<body>
<h3>show jsp</h3><br/>
<h3>myname数据:${myname}</h3>
<h3>myage数据:${myage}</h3>
</body>
5.3 String对象
如果没有加上@ResponseBody注解,默认是视图,否则是String数据。
@RequestMapping(value = "/returnString.do")
@ResponseBody
public String doreturn(HttpServletRequest request, @RequestParam(value = "rname") String name,
@RequestParam(value = "rage") Integer age){//service请求
System.out.println("名字是"+name+"age="+age);
request.setAttribute("myname",name);
request.setAttribute("myage",age);
return "你好,我是大白";
}
【注意】此时如果前端发来的ajax需求中dataType是json,而这里显然是String类型,无法转换,怎么处理矛盾呢?
首先明确一点,即如果dataType没有指明,那么默认String返回的是text/Content-Type: text/plain;charset=ISO-8859-1,为了使用utf-8解决乱码问题,方法如下:
@RequestMapping(value = "/returndataString.do",produces = "text/plain;charset=utf-8")
@ResponseBody
public String doreturndataString(String name, Integer age){//service请求
return "hellowfsadf发送到发送方三房";
}
即使用produces属性即可。
【问题】不是说可以用过滤器吗?不难证明,此时不经过过滤器,返回到ajax!
5.4void返回值
该返回值既无数据,也没有视图,那么有什么用呢?
一般多用于处理ajax的请求,通过response输出请求。
@RequestMapping(value = "/returnvoid-ajax.do")
public void doretunajax(HttpServletResponse response,String name,int age){
//service请求
System.out.println("名字是"+name+"age="+age);
//处理ajax,做json的数据格式
//service调用完成,使用student表示处理结果
Student stu = new Student("李白",99);
//转化为json
String json = "";
if(stu!=null){
ObjectMapper om = new ObjectMapper();
json = om.writeValueAsString(stu);
System.out.println("studnet的json为"+ json);
//输出数据,响应ajax请求:
//告诉浏览器,我发回来的正好是json格式。
response.setContentType("application/json;charset=utf-8");
PrintWriter pw = null;
pw = response.getWriter();
pw.println(json);
pw.flush();
pw.close();
}
}
总的来说,ajax请求后端可以总计为三步走:
调用service方法→包装java对象为json格式→输出json
不难发现,这三步第二步和第三步是重复的,因此可以让框架去实现,即下文的返回Object对象。
5.5 Object返回值
5.5.1 步骤讲述
不难发现,此时返回的是对象,即数据,而不是视图,因此非常适合ajax请求。
因此对应上面的ajax三步走,引入了springmvc框架后的ajax后台步骤如下:
第一步:加入处理json的依赖,即jackson
第二步:告诉springmvc配置文件,我要转换→引入mvc驱动,将java对象转换为json、xml、text等二进制数据;
<mvc:annotation-driven></mvc:annotation-driven>
【注意】等价于 json = om.writeValueAsString(stu);
【注意2】在引入驱动后,springmvc会自动创建HttpMessageConveter接口的七个实现类对象,例如MappingJackson2HttpMessageConveter,用jackson工具库的getmapper方法包装。
【注意3】其本质涉及canWrite()和Write()方法,canWrite()判断能否将java对象转化为所需的json等格式,后者用参数MediaType表示,如果转换,最后再使用Write方法转换,将其转化为json字符串!
第三步:处理器方法加上@ResponseBody注解
【注意】等价于如下代码:
response.setContentType("application/json;charset=utf-8"); PrintWriter pw = null; pw = response.getWriter(); pw.println(json); pw.flush(); pw.close();
【注意2】本质上,是通过HttpServletResponse输出数据,然后响应ajax请求。
其实,可以证明,加入注解驱动后,状态变化如下:
5.5.2 流程总结
第一步:框架会把返回值Student,调用框架中arrayList<httpmessageconverter>中每个类的canwwire方法,检查哪个实现类能够处理Student类型的数据,发现-MappingJackson2HttpMessageconverter可以将对象转化为json。
第二步:框架会调用实现类的write方法,MappingJackson2HttpMessageconverter的write方法,把王五同学的student对象转化为json,调用jackson的objectmapper实现转化为json
第三步:框架会调用@ResponseBody把第二步结果的输出到浏览器,ajax数据请求处理完毕;
由此可见,对于String对象的返回过程,大家应该也明白了,只需把对应的实现类改为StringHttpMessageConverter。