SpringMVC
特点:
高效,基于请求响应的MVC框架,与spring兼容性好,无缝结合…
HelloSpringMVC
1.新建模块,添加web的支持
2.确定导入了springmvc的依赖
3.配置web.xml,注册DispatcherServlet
遇到的error:
访问首页出现404:导入的jar包没有关联项目。
解决:在projectstructure–artifacts–在该项目的web-inf下建立lib目录,导入依赖
- springmvc执行原理:
用户提交请求,因为我们在web.xml中配置了dispatcherServlet(前端控制器),所有请求都会经过我们注册的映射路径,被前端控制器拦截。
springmvc帮我们做了?
一:根据请求找到对应的controller
处理器映射(HandlerMapping),前端控制器调用处理器映射器,处理器映射器根据url查找处理器(在spring配置文件中配置的bean,即controller),返回给前端控制器。
二:前端控制器调用适配器,适配器按照特定的规则去适配实现了controller接口的类,让controller去具体执行业务代码(调用业务层…封装对象…封装要跳转的视图),执行完后controller将具体的信息(ModelAndView…)返回给适配器,适配器将模型和视图传递给前端控制器。
三:前端控制器中的模型和视图都会经过视图解析器,视图解析器将解析的逻辑视图名返回给前端控制器,前端控制器响应给用户。
1.获取ModelAndView中的数据
2.解析ModelAndView中的视图名
3.拼接视图名(WEB-INF/jsp/xxx.jsp)
4.将数据渲染到视图上
流程图:
- 代码实现:
note:
如果在项目中修改了配置文件,则restart server(重启服务器),如果修改了java源代码,则redeploy(重新发布),如果修改了前端代码,则update class and resource(更新)。
1.配置前端控制器:
<!--配置DispatcherServlet(前端控制器,请求分发器),springmvc的核心-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--DispatcherServlet要绑定spring的配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!--设置启动级别-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
/ 和 /* 的区别:
/*会去匹配所有页面,包括jsp页面。 jsp中的数据已经渲染好了,如果被匹配到的话,会进入视图解析器中重新拼接,拼接结果为:/WEB-INT/jsp/xxx.jsp.jsp/,访问会出现错误。
2.在spring的配置文件中配置处理器映射器和适配器和视图解析器
<!--处理器映射器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!--处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!--视图解析器:DispatcherServlet给他的ModelAndView-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>
3.把controller交由ioc容器管理:
<bean id="/hello" class="com.qiufen.controller.HelloController"></bean>
4.编写Controller类:
public class HelloController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
ModelAndView mv = new ModelAndView();
// 业务代码
String result = "HelloSpringMVC";
mv.addObject("msg",result);
// 视图跳转
// 设置视图名后交由视图管理器拼接为/WEB-INF/jsp/test.jsp/
mv.setViewName("test");
return mv;
}
}
缺点:一个控制器只能跳转一个页面,如果存在多个控制器,则需要多个controller类,比较麻烦。
使用注解开发springMVC
使用注解开发springmvc,我们除了手动配置视图解析器外,只需在spring配置文件中导入
<context:component-scan base-package="com.qiufen.controller"/>
<!-- 让Spring MVC不处理静态资源:html,css... -->
<mvc:default-servlet-handler/>
<!--
支持mvc注解驱动
在spring中一般采用@RequestMapping注解来完成映射关系
要想使@RequestMapping注解生效
必须向上下文中注册DefaultAnnotationHandlerMapping
和一个AnnotationMethodHandlerAdapter实例
这两个实例分别在类级别和方法级别处理。
而annotation-driven配置帮助我们自动完成上述两个实例的注入。
-->
<mvc:annotation-driven/>
//@RequestMapping("/HelloController")
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(Model model){
// 使用model封装数据
model.addAttribute("msg","hello,springMVC!!!");
// 返回的结果会被视图解析器处理
return "hello";
}
@Controller:为了让SpringIoc容器初始化时,自动扫描到该处理器。
@RequstMapping:映射请求路径,可以在类或方法上,如果两者都存在则真实路径为:/项目发布名/类的映射路径/方法映射路径。
在方法中传入Model对象,把Action中的数据带到视图。
方法的返回结果是视图的名称,加上配置文件中的前后缀进行匹配。
解决了使用实现controller接口的弊端,在一个类里面可以实现跳转多个页面,即有多个方法。
- Restful风格
Restful就是一个资源定位及资源操作的风格。
一般访问:
@Controller
public class RestFulController {
@RequestMapping("add")
//方法中定义的参数,必须在前端中传参
public String test1(int a, int b, Model model){
model.addAttribute("msg","a+b结果为:"+(a+b));
return "test";
}
}
访问格式:http://localhost:8080/add?a=1&b=2
缺点:不安全,会暴露提交的参数
restful风格:
在springMVC中可以在方法变量上使用@PathVariable注解,让方法参数的值对应绑定到一个URL模板变量上。前端传递的参数会对应的赋值到由@PathVariable注解的变量上。
@RequestMapping("/add/{a}/{b}")
public String test2(@PathVariable int a, @PathVariable int b,Model model){
model.addAttribute("msg","a+b="+(a+b));
return "test";
}
访问格式:http://localhost:8080/add/2/3
使用method约束请求类型:
@RequestMapping(path = "/add/{a}/{b}",method = RequestMethod.GET)
public String test3(@PathVariable int a, @PathVariable int b,Model model){
model.addAttribute("msg","a+b="+(a+b));
return "test";
}
对应的存在:@GetMapping,@PostMapping,@DeleteMapping,@PutMapping,@PatchMapping
同一个路径不同方法提交的问题:
Get方式:
@GetMapping("/add/{a}/{b}")
public String test4(@PathVariable int a, @PathVariable int b, Model model) {
model.addAttribute("msg", "结果2:" + (a + b));
return "test";
}
Post方式:
@PostMapping("/add/{a}/{b}")
public String test3(@PathVariable int a, @PathVariable int b, Model model) {
model.addAttribute("msg", "结果2:" + (a + b));
return "test";
}
对应的表单:
<form action="/add/1/2" method="post">
<%-- <input type="text" name="a">--%>
<%-- <input type="text" name="b">--%>
<input type="submit">
</form>
可以看到访问相同的路径,对应的提交方式不同!!!
- SpringMVC:结果跳转方式
ModelAndView:根据view的名称,和视图解析器跳转到对应的页面
页面:{视图解析器前缀}–viewName–{视图解析器后缀}
通过SpringMVC来实现重定向和转发–无需视图解析器(其实是使用了servlet的请求和响应来实现)
方式一:
@RequestMapping("m1/t1")
public String test1(Model model){
model.addAttribute("msg","转发");
return "forward:/WEB-INF/jsp/test.jsp";
}
方式二:
@RequestMapping("m1/t2")
public String test2(Model model){
model.addAttribute("msg","重定向");
return "redirect:/index.jsp";
}
视图解析器存在的情况,则会进行拼接。
- springMVC:数据处理
基本数据类型:
@Controller
@RequestMapping("/user")
public class UserController {
@GetMapping("/t1")
// 访问格式:localhost:8080/user/t1?name=xxx;
public String test(@RequestParam("username") String name,Model model){
// 1.接收前端参数
System.out.println("接收到前端的参数为"+name);
// 2.将返回的结果传递给前端
model.addAttribute("msg",name);
return "test";
}
}
使用@RequestParam注解标记方法中的参数为前端的。
好处:
1.即只能通过localhost:8080/user/t1/username=xxx访问。避免了很多无效的访问:localhost:8080/user/t1/name=xxx…控制台输出为null。
引用数据类型:
// 前端接收的是一个对象,则逐一匹配user对象中的字段名
@GetMapping("/t2")
public String test2(User user){
System.out.println(user);
return "test";
}
- 乱码问题:提交方式,编码问题…
前端中表单提交后,数据回显会产生乱码。
如何解决?
我们手写过滤器解决:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("utf-8");
servletResponse.setCharacterEncoding("utf-8");
filterChain.doFilter(servletRequest,servletResponse);
}
使用springMVC提供给我们的过滤器:
<filter>
<filter-name>encodingFilter</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>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
过滤器中路径注意是/*,才能识别jsp页面
- json
是什么:一种轻量级的数据交换格式。
要求和语法格式:
- 对象表示为键值对,数据由逗号分割
- 花括号保存对象
- 方括号保存数组
@ResponseBody:增加该注解后,返回值不会经过视图解析器,只返回字符串。
@RestController:也不会经过视图解析器。
使用java中的toString返回对象:
@RequestMapping("j1")
@ResponseBody
public String json1(){
// 创建一个对象
User user = new User("秋分",20,"男");
return user.toString();
}
输出格式:
使用jackson中的ObjectMapper:
@RequestMapping("j1")
@ResponseBody
public String json1() throws JsonProcessingException {
User user = new User("秋分",20,"男");
ObjectMapper mapper = new ObjectMapper();
String str = mapper.writeValueAsString(user);
return str;
}
输出格式:
设置@RequestMapping的produces属性解决乱码问题
@RequestMapping(value = "j1",produces = "application/json;charset=utf-8")
显示为:
可以发现,如果使用以上方式解决乱码,则需要每个请求都去设置编码,很麻烦。我们可以通过spring配置统一指定,即在springMVC的配置文件添加一段StringHttpMessageConverter转换配置。
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
对象数组:
@RequestMapping("j2")
public String json2() throws JsonProcessingException {
ArrayList<User> users = new ArrayList<>();
User user1 = new User("秋分1",20,"男");
User user2 = new User("秋分2",20,"男");
User user3 = new User("秋分3",20,"男");
User user4 = new User("秋分4",20,"男");
users.add(user1);
users.add(user2);
users.add(user3);
users.add(user4);
return new ObjectMapper().writeValueAsString(users);
}
输出:
时间的转换:
@RequestMapping("j3")
public String json3() throws JsonProcessingException {
Date date = new Date();
// 自定义时间格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 经过objectMapper解析后的默认格式为:timestamp,即时间戳
return new ObjectMapper().writeValueAsString(sdf.format(date));
}
jackson默认会把时间转成时间戳形式,通过设置mapper的configure来取消
@RequestMapping("j3")
public String json3() throws JsonProcessingException {
Date date = new Date();
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.CLOSE_CLOSEABLE,false);
// 自定义时间格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
return mapper.writeValueAsString(date);
}
将字符串转换为json的方法提取工具类:
可以自定义时间的输出格式~~~
public static String getJson(Object object, String DateFormat) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.CLOSE_CLOSEABLE, false);
mapper.setDateFormat(new SimpleDateFormat(DateFormat));
try {
return mapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
如果对不是时间的转换也同理,我们覆写该方法,并自定义时间的输出格式就好了:
public static String getJson(Object object) throws JsonProcessingException {
return getJson(object,"yyyy-MM-dd HH:mm:ss");
}
ssm整合
- 创建模块后,添加web支持。导入所有依赖,并设置maven资源过滤
- 项目基本机构:
<!--ssm项目需要的依赖-->
<dependencies>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.24</version>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!--mybatis-spring整合的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!--spring的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
<!--静态资源导出-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
- MyBatis层:
1.编写数据库配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssmbuild?useSSL=false&userUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
jdbc.user=root
jdbc.password=123456
2.编写mybatis核心配置文件
使用日志输出打印sql语句
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<package name="com.qiufen.pojo"/>
</typeAliases>
3.编写数据库对应的实体类
尽量与数据库中对应表的字段名一致。如果不一致有如下方法:
- 在XML映射文件中使用的resultMap
- 让字段的别名与实体类的属性名相同
- 使用Map集合封装结果集
详细回顾Mybatis!
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Books {
private int bookID;
private String bookName;
private int bookCounts;
private String detail;
}
4.dao层的XxxMapper接口
public interface BooksMapper {
// 增加书籍
int addBook(Books book);
// 删除书籍
int deleteBook(@Param("bookID") int id);
// 修改书籍
int updateBook(Books book);
// 查找书籍
List<Books> queryBooks();
// 查询一本书
Books queryBook(@Param("bookID") int id);
}
编写对应的XxxMapper.xml配置文件
<insert id="addBook" parameterType="com.qiufen.pojo.Books">
insert into books(bookName,bookCounts,detail)
values (#{bookName},#{bookCounts},#{detail})
</insert>
<delete id="deleteBook">
delete from books
where bookID = #{bookID}
</delete>
<update id="updateBook" parameterType="com.qiufen.pojo.Books">
update books
set bookName = #{bookName},
bookCounts =#{bookCounts},
detail = #{detail}
where bookID = #{bookID}
</update>
<select id="queryBooks" resultType="com.qiufen.pojo.Books">
select * from books
</select>
<select id="queryBook" resultType="com.qiufen.pojo.Books">
select * from books
where bookID = #{bookID}
</select>
编写sql语句时一定要注意语法的正确性!
5.service层的接口和实现类
public interface BooksService {
// 增加书籍
int addBook(Books book);
// 删除书籍
int deleteBook(int id);
// 修改书籍
int updateBook(Books book);
// 查找书籍
List<Books> queryBooks();
// 查询一本书
Books queryBook(int id);
}
public class BooksServiceImpl implements BooksService {
//注意这里的booksMapper,由spring注入
private BooksMapper booksMapper;
public void setBooksMapper(BooksMapper booksMapper) {
this.booksMapper = booksMapper;
}
public int addBook(Books book) {
return booksMapper.addBook(book);
}
public int deleteBook(int id) {
return booksMapper.deleteBook(id);
}
public int updateBook(Books book) {
return booksMapper.updateBook(book);
}
public List<Books> queryBooks() {
return booksMapper.queryBooks();
}
public Books queryBook(int id) {
return booksMapper.queryBook(id);
}
}
- Spring层
1.spring整合mybatis,数据源使用c3p0连接池
2.编写spring整合mybatis的配置文件:spring-dao.xml
<context:property-placeholder location="classpath:database.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置sqlSessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
<!-- 自动扫描mybatis核心配置文件,不用再注册每个mapper -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!--
配置扫描dao接口,动态实现dao接口由spring注入
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--需要扫描的dao包-->
<property name="basePackage" value="com.qiufen.dao"/>
</bean>
</beans>
- spring整合service层
<!-- 扫描service相关的bean -->
<context:component-scan base-package="com.qiufen.service"/>
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
<!--BookServiceImpl注入到IOC容器中-->
<bean id="BooksServiceImpl" class="com.qiufen.service.BooksServiceImpl">
<property name="booksMapper" ref="booksMapper"/>
</bean>
遇到的error:
1.Failed to import bean definitions from URL location [classpath:spring-xxx.xml]
这类错误一般是spring配置文件中命名空间的错误。解决方法就是命名空间记得写全。
2.Error Creating bean with name 'xxx ’
导入依赖后记得要在模块中查看lib目录中是否有加入
3.
spring与mybatis整合流程图:
拦截器
过滤器与拦截器的区别:拦截器是AOP思想的具体应用。
拦截器只会拦截访问的控制器方法。访问jsp/html/…则不会进行拦截。
- 关于web目录:
在WEB-INF目录下建立jsp文件夹,存放一些核心页面(视图解析器也一般经过这里)。
在WEB-INF目录下的所有资源和文件都不能直接访问,只能通过控制器转发或servlet。 - 拦截器只需实现HandlerInterceptor接口即可
选择实现的三个方法只有第一个有返回值,如果返回true,则放行(或执行下一个拦截器),返回false的话即拦截。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("before the function~~~");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("after the function~~~");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("clean~~~");
}
在spring配置文件中配置拦截器:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.qiufen.config.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
判断是否要拦截(怎样拦截)的代码写在拦截器中。
遇到的一个error:如果在spring的配置文件中添加了配置,但是模块中没有导入相应的依赖,服务器正常启动后,访问页面后会出现500的错误。eg:我在一个模块中配置了json,但是打包的时候并没有导入相应的jar包,项目正常启动,到那时访问页面后就出现了java.lang.IllegalStateException Failed to introspect Class的error。还有我们写maven项目时,maven导入依赖后要添加到idea的lib库中。否则也会报错。