使用SpringMVC需要先导入SpringMVC坐标与Servlet坐标
<dependencies> <!--1.导入坐标--> <!--Servlet--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <!--由于可能会和tomcat插件冲突,添加范围provided--> <scope>provided</scope> </dependency> <!--SpringMVC--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies>
创建SpringMVC控制器类(等同于Servlet功能)
//2.定义controller //2.1 使用@Controller定义bean @Controller public class UserController { //2.2 设置当前操作的访问路径 @RequestMapping("/save") //2.3 设置当前操作的返回值类型 @ResponseBody public String save(){ System.out.println("user save.."); return "{'module':'Spring MVC'}"; } }
初始化SpringMVC环境(同Spring环境),设定SpringMVC加载对应Bean
//3.创建SpringMVC的配置文件,加载Controller对应的bean @Configuration @ComponentScan("com.mark.controller") public class SpringMVC { }
初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理的请求
//4.定义一个Servlet容器启动的配置类,在里面加载Spring的配置,告知服务器使用SpringMVC public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer { //加载SpringMVC容器配置 @Override protected WebApplicationContext createServletApplicationContext() { //原来Spring获取容器:ApplicationContext ctx= new AnnotationConfigApplicationContext() //获取容器 AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); //注册配置 ctx.register(SpringMVCConfig.class); return ctx; } //设置哪些请求归属SpringMVC处理 @Override protected String[] getServletMappings() { //所有请求都归SpringMVC处理 return new String[]{"/"}; } //加载Spring容器配置 @Override protected WebApplicationContext createRootApplicationContext() { return null; } }
注意:此时web.xml可以删除,之后配置tomcat服务器
<build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.1</version> <configuration> <port>80</port> <path>/</path> </configuration> </plugin> </plugins> </build>
这时浏览器访问http://localhost/save 即可看到结果 {'module':'Spring MVC'}
@Controller
类型:类注解
位置:SpringMVC控制器类定义上方
作用:设定SpringMVC的核心控制器bean
范例:
@Controller public class UserController { }
@RequestMapping
类型:方法注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器方法请求访问路径
范例:
@RequestMapping("/save") public void save(){ System.out.println("user save ..."); }
相关属性
@ResponseBody
类型:方法注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器方法响应内容为当前返回值,无需解析
范例
@RequestMapping("/save") @ResponseBody public String save(){ System.out.println("user save ..."); return "{'info':'springmvc'}"; }
AbstractDispatcherServletInitializer
类是SpringMVC提供的快速初始化Web3.0容器的抽象类
AbstractDispatcherServletInitializer提供三个接口方法供用户实现
createServletApplicationContext()
方法:
创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围
@Override protected WebApplicationContext createServletApplicationContext() { //原来Spring获取容器:ApplicationContext ctx= new AnnotationConfigApplicationContext() //获取容器 AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); //注册配置 ctx.register(SpringMVCConfig.class); return ctx; }
getServletMappings()
方法:
设定SpringMVC对应的请求映射路径,设置为/表示拦截所有请求,任意请求都将转入到SpringMVC进行处理
@Override protected String[] getServletMappings() { //所有请求都归SpringMVC处理 return new String[]{"/"}; }
createRootApplicationContext()
方法:
如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式同createServletApplicationContext()。如果没有返回null即可
@Override protected WebApplicationContext createRootApplicationContext() { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(SpringConfig.class); return ctx; }
启动服务器初始化过程
单次请求过程
不同的bean由不同的容器管理
因为功能不同,如何避免Spring错误的加载到SpringMVC的bean?
不同bean的加载控制:
SpringMVC相关bean加载控制
Spring相关bean加载控制
方式一:Spring加载的bean设定扫描范围为com.mark,排除掉controller包内的bean
@Configuration //@ComponentScan({"com.mark.service","com.mark.dao"}) //扫描com.mark下的所有,但是按照注解排除掉使用了Controller注解的bean @ComponentScan(value = "com.mark", excludeFilters = @ComponentScan.Filter( type = FilterType.ANNOTATION, classes = Controller.class ) ) public class SpringConfig { }
方式二:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包
@Configuration @ComponentScan({"com.mark.service","com.mark.dao"}) public class SpringConfig { }
方式三:不区分Spring与SpringMVC的环境,加载到同一个环境
相关注解介绍
@ComponentScan
类型:类注解
范例:
@Configuration @ComponentScan(value = "com.mark", excludeFilters = @ComponentScan.Filter( type = FilterType.ANNOTATION, classes = Controller.class ) ) public class SpringConfig { }
属性
⭐配置Web容器启动类改进、简化:继承AbstractAnnotationConfigDispatcherServletInitializer
类
public class ServletContainerInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMVCConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
思考:
团队多人开发,每个人设置不同的请求路径,冲突如何解决?
设置模块名作为请求路径前缀
@Controller public class BookController { @RequestMapping("/book/save") @ResponseBody public String save(){ System.out.println("book save ..."); return "{'module':'book save'}"; } }
@Controller //请求路径前缀 @RequestMapping("/user") public class UserController { @RequestMapping("/save") @ResponseBody public String save() { System.out.println("user save ..."); return "{'module':'user save'}"; } @RequestMapping("/delete") @ResponseBody public String delete() { System.out.println("user delete ..."); return "{'module':'user delete'}"; } }
注解介绍
名称:@RequestMapping
类型:方法注解 类注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器方法请求访问路径,如果设置在类上统一设置当前控制器方法请求访问路径前缀
范例:
@Controller @RequestMapping("/book") public class BookController { @RequestMapping("/save") @ResponseBody public String save() { System.out.println("book save ..."); return "{'module':'book save'}"; } }
属性
请求方式
Get请求参数
普通参数:url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数
@Controller @RequestMapping("/user") public class UserController { //普通参数 @RequestMapping("/commonParam") @ResponseBody public String commonParam(String name,int age){ System.out.println("普通参数传递 name ==>"+name); System.out.println("普通参数传递 age ==>"+age); return "{'module':'common param'}"; } }
请求参数名与形参变量名不同,使用@RequestParam
绑定参数关系
@Controller @RequestMapping("/user") public class UserController { //普通参数 @RequestMapping("/commonParam") @ResponseBody public String commonParam(@RequestParam("name") String username,int age){ System.out.println("普通参数传递 name ==>"+username); System.out.println("普通参数传递 age ==>"+age); return "{'module':'common param'}"; } }
这时发送请求的名字为name,username可以接收到name的值
POJO参数:请求参数名与形参对象属性名相同,定义POJO类型形参即可接收参数
//POJO参数 @RequestMapping("/pojoParam") @ResponseBody public String pojoParam(User user){ System.out.println("POJO参数传递 user ==>"+user); return "{'module':'pojo param'}"; }
嵌套POJO参数:POJO对象中包含POJO对象
请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数
public class User { private String name; private int age; private Address address; }
public class Address { private String province; private String city; }
数组参数:请求参数名与形参数组名相同且请求参数为多个,定义数组类型形参即可接收参数
@RequestMapping("arrayParam") @ResponseBody public String arrayParam(String[] hobby){ System.out.println(("数组参数传递 hobby ==> "+ Arrays.toString(hobby))); return "{'module':'array param'}"; }
集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系
@RequestMapping("listParam") @ResponseBody public String listParam(@RequestParam List<String> hobby){ System.out.println("集合参数传递 hobby ==> "+ hobby); return "{'module':'list param'}"; }
Post请求参数
普通参数:form表单post请求传参,表单参数名与形参变量名相同,定义形参即可接收参数,代码与Get相同。
Post请求中文乱码处理
在Servlet启动类配置中添加过滤器
//乱码处理 @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); return new Filter[]{filter}; }
其他类型与Get方式规则一样
添加json数据转换相关坐标
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency>
设置发送json数据(请求body中添加json数据)
开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
@Configuration @ComponentScan("com.mark.controller") @EnableWebMvc public class SpringMvcConfig { }
使用@RequestBody
注解将外部传递的json数组数据映射到形参的集合对象中作为数据
集合类数据:
@RequestMapping("/listParamForJson") @ResponseBody public String listParamForJson(@RequestBody List<String> hobby){ System.out.println("list common(json)参数传递 list ==> "+hobby); return "{'module':'list common for json param'}"; }
POJO类数据:json数据与形参对象属性名相同,定义POJO类型形参即可接收参数
@RequestMapping("/pojoParamForJson") @ResponseBody public String pojoParamForJson(@RequestBody User user){ System.out.println("pojo(json)参数传递 user ==> "+user); return "{'module':'pojo for json param'}"; }
POJO集合参数
@RequestMapping("/listPojoParamForJson") @ResponseBody public String listPojoParamForJson(@RequestBody List<User> list){ System.out.println("list pojo(json)参数传递 list ==> "+list); return "{'module':'list pojo for json param'}"; }
@DateTimeFormat
设定日期时间型数据格式,属性:pattern = 日期时间格式字符串@RequestMapping("/dataParam") @ResponseBody public String dataParam(Date date, @DateTimeFormat(pattern="yyyy-MM-dd") Date date1, @DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2){ System.out.println("参数传递 date ==> "+date); System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1); System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2); return "{'module':'data param'}"; } /* 参数传递 date ==> Tue Apr 18 00:00:00 CST 2000 参数传递 date1(yyyy-MM-dd) ==> Tue Apr 18 00:00:00 CST 2000 参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> Tue Apr 18 00:05:20 CST 2000 */
响应页面(了解)
返回值为String类型,设置返回值为页面名称,即可实现页面跳转
@RequestMapping("/toJumpPage") public String toJumpPage(){ System.out.println("跳转页面"); return "page.jsp"; }
响应数据
文本数据(了解)
返回值为String类型,设置返回值为任意字符串信息,即可实现返回指定字符串信息,需要依赖@ResponseBody注解
@RequestMapping("/toText") @ResponseBody public String toText(){ System.out.println("返回纯文本数据"); return "response text"; }
json数据(重点)
响应POJO对象
返回值为实体类对象,设置返回值为实体类类型,即可实现返回对应对象的json数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
@RequestMapping("/toJsonPOJO") @ResponseBody public User toJsonPOJO(){ System.out.println("返回json对象数据"); User user = new User(); user.setName("itcast"); user.setAge(15); return user; }
响应POJO集合对象
返回值为集合对象,设置返回值为集合类型,即可实现返回对应集合的json数组数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
@RequestMapping("/toJsonList") @ResponseBody public List<User> toJsonList(){ System.out.println("返回json集合数据"); User user1 = new User(); user1.setName("传智播客"); user1.setAge(15); User user2 = new User(); user2.setName("黑马程序员"); user2.setAge(12); List<User> userList = new ArrayList<User>(); userList.add(user1); userList.add(user2); return userList; }
步骤:
修改@RequestMapping路径为模块名称复数
@RequestMapping("/users")
设置请求行为(http请求动作)
@RequestMapping(value = "/users",method = RequestMethod.POST)
设定请求参数(路径变量)
@RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE) @ResponseBody public String delete(@PathVariable Integer id) { System.out.println("user delete..." + id); return "{'module':'user delete'}"; }
实现:
@Controller public class UserController { //设置当前请求方法为POST,表示REST风格中的添加操作 @RequestMapping(value = "/users",method = RequestMethod.POST) @ResponseBody public String save(@RequestBody User user) { System.out.println("user save..."+user); return "{'module':'user save'}"; } //设置当前请求方法为DELETE,表示REST风格中的删除操作 //@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同 @RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE) @ResponseBody public String delete(@PathVariable Integer id) { System.out.println("user delete..." + id); return "{'module':'user delete'}"; } //设置当前请求方法为PUT,表示REST风格中的修改操作 @RequestMapping(value = "/users",method = RequestMethod.PUT) @ResponseBody public String update(@RequestBody User user) { System.out.println("user update..." + user); return "{'module':'user update'}"; } //设置当前请求方法为GET,表示REST风格中的查询操作 //@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同 @RequestMapping(value = "/users/{id}",method = RequestMethod.GET) @ResponseBody public String getById(@PathVariable Integer id) { System.out.println("user getById..." + id); return "{'module':'user getById'}"; } //设置当前请求方法为GET,表示REST风格中的查询操作 @RequestMapping( value = "/users",method = RequestMethod.GET) @ResponseBody public String getAll() { System.out.println("user getAll..."); return "{'module':'user getAll'}"; } }
POST、DELETE、PUT、GET分别对应增删改查
@PathVariable
注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
截至目前,见到过的接收参数注解有三种
应用
在上面的案例中,有很多重复编写的代码
将每个方法的value中的模块名提取到类前
将每个方法的@ResponseBody提取到类前
而类的@Controller和@ResponseBody可以合并成@RestController
每个方法@PostMapping
中的请求行为设置可以用对应的@xxxMapping
注解替换
//@Controller //@ResponseBody配置在类上可以简化配置,表示设置当前每个方法的返回值都作为响应体 //@ResponseBody @RestController//使用@RestController注解替换@Controller与@ResponseBody注解,简化书写 @RequestMapping("/books") public class BookController { //@RequestMapping( method = RequestMethod.POST) @PostMapping//使用@PostMapping简化Post请求方法对应的映射配置 public String save(@RequestBody Book book) { System.out.println("book save..." + book); return "{'module':'book save'}"; } //@RequestMapping(value = "/{id}" ,method = RequestMethod.DELETE) @DeleteMapping("/{id}")//使用@DeleteMapping简化DELETE请求方法对应的映射配置 public String delete(@PathVariable Integer id) { System.out.println("book delete..." + id); return "{'module':'book delete'}"; } //@RequestMapping(method = RequestMethod.PUT) @PutMapping//使用@PutMapping简化Put请求方法对应的映射配置 public String update(@RequestBody Book book) { System.out.println("book update..." + book); return "{'module':'book update'}"; } //@RequestMapping(value = "/{id}" ,method = RequestMethod.GET) @GetMapping("/{id}")//使用@GetMapping简化GET请求方法对应的映射配置 public String getById(@PathVariable Integer id) { System.out.println("book getById..." + id); return "{'module':'book getById'}"; } //@RequestMapping(method = RequestMethod.GET) @GetMapping//使用@GetMapping简化GET请求方法对应的映射配置 public String getAll() { System.out.println("book getAll..."); return "{'module':'book getAll'}"; } }
POSTMan实现后台接口开发
@RestController @RequestMapping("/books") public class BookController { /** * 保存新增数据 * @param book * @return */ @PostMapping public String save(@RequestBody Book book) { System.out.println("book save" + book); return "{'module':'book save success'}"; } /** * 获取全部 * @return */ @GetMapping public List<Book> getAll() { Book book1 = new Book(); book1.setType("计算机"); book1.setName("SpringMVC入门"); book1.setDescription("我是小白"); Book book2 = new Book(); book2.setType("计算机"); book2.setName("SpringMVC实战"); book2.setDescription("我是大佬"); List<Book> list = new ArrayList<Book>(); list.add(book1); list.add(book2); return list; } }
实现页面数据交互:
由于在访问静态资源页面时,SpringMVC的Sevlet容器配置中设置了protected String[] getServletMappings() { return new String[]{"/"};}
会拦截所有请求,导致无法打开页面
因此创建SpringMvcSupport配置类,设置对静态资源的访问放行
@Configuration public class SpringMVCSupport extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { //当访问/pages/???的时候,不要走MVC,走/pages目录下的内容 registry.addResourceHandler("/pages/**").addResourceLocations("/pages/"); registry.addResourceHandler("/css/**").addResourceLocations("/css/"); registry.addResourceHandler("/js/**").addResourceLocations("/js/"); registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/"); } }
前端页面通过异步提交访问后台控制器
//添加 saveBook() { axios.post("/books", this.formData).then((res) => { }); }, //主页列表查询 getAll() { axios.get("/books").then((res) => { this.dataList = res.data; }); },
创建工程
SSM整合
Spring
SpringConfig
@Configuration @ComponentScan({"com.mark.service"}) @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) public class SpringConfig {}
MyBatis
JdbcConfig
public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } }
MyBatisConfig
public class MybatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setTypeAliasesPackage("com.mark.domain"); return factoryBean; } @Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("com.mark.dao"); return msc; } }
jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm_db jdbc.username=root jdbc.password=123
SpringMVC
SpringMVCConfig
@Configuration @ComponentScan({"com.mark.controller","com.mark.config"}) @EnableWebMvc public class SpringMvcConfig {}
ServletConfig
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter characterEncodingFilter =new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); return new Filter[]{characterEncodingFilter}; } }
SpringMVCSupport
@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/pages/**").addResourceLocations("/pages/"); registry.addResourceHandler("/js/**").addResourceLocations("/js/"); registry.addResourceHandler("/css/**").addResourceLocations("/css/"); registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/"); } }
表与实体类
创建表:
-- ---------------------------- -- Table structure for tbl_book -- ---------------------------- DROP TABLE IF EXISTS `tbl_book`; CREATE TABLE `tbl_book` ( `id` int(11) NOT NULL AUTO_INCREMENT, `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of tbl_book -- ---------------------------- INSERT INTO `tbl_book` VALUES (1, '计算机理论', 'Spring实战 第5版', 'Spring入门经典教程,深入理解Spring原理技术内幕'); INSERT INTO `tbl_book` VALUES (2, '计算机理论', 'Spring 5核心原理与30个类手写实战', '十年沉淀之作,手写Spring精华思想'); INSERT INTO `tbl_book` VALUES (3, '计算机理论', 'Spring 5 设计模式', '深入Spring源码剖析Spring源码中蕴含的10大设计模式'); INSERT INTO `tbl_book` VALUES (4, '计算机理论', 'Spring MVC+MyBatis开发从入门到项目实战', '全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手'); INSERT INTO `tbl_book` VALUES (5, '计算机理论', '轻量级Java Web企业应用实战', '源码级剖析Spring框架,适合已掌握Java基础的读者'); INSERT INTO `tbl_book` VALUES (6, '计算机理论', 'Java核心技术 卷I 基础知识(原书第11版)', 'Core Java 第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新'); INSERT INTO `tbl_book` VALUES (7, '计算机理论', '深入理解Java虚拟机', '5个维度全面剖析JVM,大厂面试知识点全覆盖'); INSERT INTO `tbl_book` VALUES (8, '计算机理论', 'Java编程思想(第4版)', 'Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉'); INSERT INTO `tbl_book` VALUES (9, '计算机理论', '零基础学Java(全彩版)', '零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术'); INSERT INTO `tbl_book` VALUES (10, '市场营销', '直播就该这么做:主播高效沟通实战指南', '李子柒、李佳琦、薇娅成长为网红的秘密都在书中'); INSERT INTO `tbl_book` VALUES (11, '市场营销', '直播销讲实战一本通', '和秋叶一起学系列网络营销书籍'); INSERT INTO `tbl_book` VALUES (12, '市场营销', '直播带货:淘宝、天猫直播从新手到高手', '一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');
实体类:
@Getter @Setter @ToString public class Book { private Integer id; private String type; private String name; private String description; }
创建好所有的接口和实现类
dao(接口+自动代理实现)
public interface BookDao { //@Insert("insert into tbl_book values (null,#{type},#{name},#{description})") //上面语句中type指的是Book类的属性名,id需要为null //下面语句中第一个type指的是表里的属性名 第二个type指的是Book类的属性名,不需要为id赋值 /** * 保存 * @param book */ @Insert("insert into tbl_book (type, name, description) values (#{type}, #{name}, #{description})") public void save(Book book); /** * 修改/更新 * @param book */ @Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}") public void update(Book book); /** * 删除 * @param id */ @Delete("delete from tbl_book where id = #{id}") public void delete(Integer id); /** * 根据id获取book * @param id * @return */ @Select("select id, type, name, description from tbl_book where id = #{id}") public Book getById(Integer id); /** * 获取所有book * @return */ @Select("select id, type, name, description from tbl_book") public List<Book> getAll(); }
service(接口+实体类)
public interface BookService { /** * 保存 * @param book */ public boolean save(Book book); /** * 修改/更新 * @param book */ public boolean update(Book book); /** * 根据id删除 * @param id */ public boolean delete(Integer id); /** * 根据id查询 * @param id * @return */ public Book getById(Integer id); /** * 获取所有 * @return */ public List<Book> getAll(); }
@Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; @Override public boolean save(Book book) { bookDao.save(book); return true; } @Override public boolean update(Book book) { bookDao.update(book); return true; } @Override public boolean delete(Integer id) { bookDao.delete(id); return true; } @Override public Book getById(Integer id) { //Book book = bookDao.getById(id); //return book; return bookDao.getById(id); } @Override public List<Book> getAll() { return bookDao.getAll(); } }
controller
@RestController @RequestMapping("/books") public class BookController { @Autowired private BookService bookService; /** * 保存 * @param book */ @PostMapping public boolean save(@RequestBody Book book) { return bookService.save(book); } /** * 修改/更新 * @param book */ @PutMapping public boolean update(@RequestBody Book book) { return bookService.update(book); } /** * 根据id删除 * @param id */ @DeleteMapping("/{id}") public boolean delete(@PathVariable Integer id) { return bookService.delete(id); } /** * 根据id查询 * @param id * @return */ @GetMapping("/{id}") public Book getById(@PathVariable Integer id) { return bookService.getById(id); } /** * 获取所有 * @return */ @GetMapping public List<Book> getAll() { return bookService.getAll(); } }
业务层接口测试(整合Junit)
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class BookServiceTest { @Autowired private BookService bookService; @Test public void testGetById() { Book book = bookService.getById(2); System.out.println(book); } @Test public void testGetAll() { List<Book> list = bookService.getAll(); System.out.println(list); } }
表现层接口测试(PostMan)
在Spring配置类中打开使用注解式事务驱动@EnableTransactionManagement
在JDBC配置类中添加事务管理器配置PlatformTransactionManager
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager ptm = new DataSourceTransactionManager(); ptm.setDataSource(dataSource); return ptm; }
给要添加事务的实现类的接口添加注解@Transactional
前端接收数据格式
增删改:true
查单条:
{ "id": 1, "type": "计算机理论", "name": "Spring实战 第5版", "description": "Spring入门经典教程,深入理解Spring原理技术内幕" }
查全部:
[ { "id": 1, "type": "计算机理论", "name": "Spring实战 第5版", "description": "Spring入门经典教程,深入理解Spring原理技术内幕" }, { "id": 2, "type": "计算机理论", "name": "Spring 5核心原理与30个类手写实战", "description": "十年沉淀之作,手写Spring精华思想" } ]
统一格式,前端接收数据格式:封装数据到data属性中,封装操作到code属性中,封装特殊消息到message(msg)属性中
增删改:
{ "code":20031 "data":true }
查单条:
{ "code":20041 "data":{ "id": 1, "type": "计算机理论", "name": "Spring实战 第5版", "description": "Spring入门经典教程,深入理解Spring原理技术内幕" } }
查单条数据为空
{ "code":20040 "data":null "msg":"数据查询失败,请重试!" }
查全部:
{ "code":20041 "data":[ { "id": 1, "type": "计算机理论", "name": "Spring实战 第5版", "description": "Spring入门经典教程,深入理解Spring原理技术内幕" }, { "id": 2, "type": "计算机理论", "name": "Spring 5核心原理与30个类手写实战", "description": "十年沉淀之作,手写Spring精华思想" } ] }
设置统一数据返回结果类
public class Result { private Object data; private Integer code; private String msg; }
添加Result结果数据
@Getter @Setter public class Result { private Object data; private Integer code; private String msg; public Result(Integer code, Object data, String msg) { this.data = data; this.code = code; this.msg = msg; } public Result(Integer code, Object data) { this.data = data; this.code = code; } public Result() { } }
修改Controller的return数据
@RestController @RequestMapping("/books") public class BookController { @Autowired private BookService bookService; /** * 保存 * @param book */ @PostMapping public Result save(@RequestBody Book book) { boolean flag = bookService.save(book); return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag); } /** * 修改/更新 * @param book */ @PutMapping public Result update(@RequestBody Book book) { boolean flag = bookService.update(book); return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag); } /** * 根据id删除 * @param id */ @DeleteMapping("/{id}") public Result delete(@PathVariable Integer id) { boolean flag = bookService.delete(id); return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag); } /** * 根据id查询 * @param id * @return */ @GetMapping("/{id}") public Result getById(@PathVariable Integer id) { Book book = bookService.getById(id); Integer code = book != null ? Code.GET_OK : Code.GET_ERR; String msg = book != null ? "" : "数据查询失败,请重试"; return new Result(code, book, msg); } /** * 获取所有 * @return */ @GetMapping public Result getAll() { List<Book> books = bookService.getAll(); Integer code = books.isEmpty() ? Code.GET_OK : Code.GET_ERR; String msg = books.isEmpty() ? "" : "数据查询失败,请重试"; return new Result(code, books, msg); } }
程序开发过程中不可避免的会遇到异常现象
出现异常现象的常见位置与常见诱因如下
各个层级都会出现异常,异常处理代码书写在哪一层?
表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?
异常处理器
@RestControllerAdvice public class ProjectExceptionAdvice { @ExceptionHandler(Exception.class) public Result doException(Exception ex){ System.out.println("异常别跑"); return new Result(666,null,"异常别跑"); } }
@RestControllerAdvice:声明一个类作为异常处理器
@ExceptionHandler:定义当前方法处理哪一种异常
项目异常分类
项目异常处理方案
实现:
新建exception包
创建Exception类,自定义系统、业务级异常
@Getter @Setter public class SystemException extends RuntimeException { private Integer code; public SystemException(Integer code, String message) { super(message); this.code = code; } public SystemException(Integer code, String message, Throwable cause) { super(message, cause); this.code = code; } }
@Getter @Setter public class BusinessException extends RuntimeException{ private Integer code; public BusinessException(Integer code, String message) { super(message); this.code = code; } public BusinessException(Integer code, String message, Throwable cause) { super(message, cause); this.code = code; } }
自定义异常编码
public class Code { public static final Integer SYS_ERR = 50001; public static final Integer SYS_TIMEOUT_ERR = 50002; public static final Integer SYS_UNKNOW_ERR = 59999; public static final Integer Business_ERR = 60002; }
将可能出现的异常进行包装,转换成自定义异常,触发自定义异常
@Override public Book getById(Integer id) { if (id == 1){ throw new BusinessException(Code.Business_ERR,"请不要乱来!"); } //将可能出现的异常进行包装,转换成自定义异常 try { int i = 1 / 0; }catch (Exception e){ throw new SystemException(Code.SYS_TIMEOUT_ERR,"服务器访问超时,请重试",e); } return bookDao.getById(id); }
在异常处理器中分别进行处理
@RestControllerAdvice public class ProjectExceptionAdvice { @ExceptionHandler(SystemException.class) public Result doSystemException(SystemException ex){ //记录日志 //发送消息给运维 //发送邮件给开发文件,ex对象发送给开发人员 return new Result(ex.getCode(),null,ex.getMessage()); } @ExceptionHandler(BusinessException.class) public Result doBusinessException(BusinessException ex){ return new Result(ex.getCode(),null,ex.getMessage()); } @ExceptionHandler(Exception.class) public Result doException(Exception ex){ //记录日志 //发送消息给运维 //发送邮件给开发文件,ex对象发送给开发人员 return new Result(Code.SYS_UNKNOW_ERR,null,"系统繁忙,请稍后再试"); } }
列表功能
getAll() { //发送ajax请求 axios.get("/books").then((res)=>{ this.dataList = res.data.data; }) },
添加功能
//弹出添加窗口 handleCreate() { this.dialogFormVisible = true; },
//添加 handleAdd() { //发送ajax请求 axios.post("/books", this.formData).then((res) => { //console.log(res.data) if (res.data.code == 20011){ //如果操作成功,关闭弹窗,显示数据 this.dialogFormVisible = false; this.$message.success("添加成功") }else if (res.data.code ==20010){ this.$message.error("添加失败") }else { this.$message.error(res.data.msg) } }).finally(()=>{ this.getAll(); }) },
//重置表单 resetForm() { this.formData = {} },
//弹出添加窗口 handleCreate() { this.dialogFormVisible = true; this.resetForm(); },
修改功能
//弹出编辑窗口 handleUpdate(row) { //查询数据,根据id查询 axios.get("/books/"+row.id).then((res)=>{ if (res.data.code== 20041){ //展示弹层,加载数据 this.formData = res.data.data; this.dialogFormVisible4Edit = true; }else{ this.$message.error(res.data.msg) } }) },
//编辑 handleEdit() { //发送ajax请求 axios.put("/books", this.formData).then((res) => { //如果操作成功,关闭弹层,显示数据 if (res.data.code == 20031){ this.dialogFormVisible4Edit = false; this.$message.success("修改成功") }else if (res.data.code ==20030){ this.$message.error("修改失败") }else { this.$message.error(res.data.msg) } }).finally(()=>{ this.getAll(); }) },
删除功能
// 删除 handleDelete(row) { //弹出提示框 this.$confirm("此操作不可逆,永久删除数据,是否继续", "提示", { type: 'info' }).then(() => { //删除业务 //查询数据,根据id查询 axios.delete("/books/" + row.id).then((res) => { if (res.data.code == 20021) { this.$message.success("删除成功") } else { this.$message.error("删除失败") } }).finally(() => { this.getAll(); }); }).catch(() => { //取消删除 this.$message.info("取消删除操作") }); }
制作拦截器功能类:声明拦截器的bean,并实现HanderInterceptor接口(注意:扫描加载bean)
@Component public class ProjectInterceptor implements HandlerInterceptor { //在原始被拦截之前运行的代码 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle"); //false时,只执行preHandle,终止原始操作的运行 return true; } //在原始被拦截之后运行的代码 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle"); } //在原始被拦截之后运行的代码并且在post之后 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion"); } }
配置拦截器的执行位置:定义配置类,继承WebMvcConfigurationSupport,实现addInterceptor方法设定拦截的访问路径(注意:扫描加载配置)
@Configuration public class SpringMVCSupport extends WebMvcConfigurationSupport { @Autowired private ProjectInterceptor projectInterceptor; @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/pages/**").addResourceLocations("/pages"); } //可以配置多个拦截路径 @Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*"); } }
在访问books时输出:
preHandle book save...Book{书名='haha', 价格=200.0} postHandle afterCompletion
简化开发:
不需要SpringMVCSupport类,直接在SpringMvcConfig中实现(侵入式较强)
@Configuration @ComponentScan({"com.mark.controller"}) @EnableWebMvc public class SpringMvcConfig implements WebMvcConfigurer { @Autowired private ProjectInterceptor projectInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*"); } }
前置处理
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String contentType = request.getHeader("Content-Type"); HandlerMethod hm=(HandlerMethod) handler; hm.getMethod(); System.out.println("preHandle..."+contentType); return true; }
后置处理
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle..."); }
完成后处理
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion"); }
当配置多个拦截器时,形成拦截器链
配置两个拦截器后运行顺序:
preHandle... preHandle...222 book getById...1 postHandle...222 postHandle... afterCompletion...222 afterCompletion
先进后出
拦截器链的运行顺序参照拦截器添加顺序为准
拦截器运行中断,post都不会执行
分模块开发意义
分模块开发步骤
书写模块代码
分模块开发需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕,然后进行拆分
通过maven指令安装模块到本地仓库(install命令)
团队内部开发需要发布模块到团队内部可共享的仓库中(私服)
在主项目pom中引入各个模块坐标
依赖指当前项目运行所需的jar,一个项目可以设置多个依赖
格式:
<!--设置当前项目所依赖的所有jar--> <dependencies> <!--设置具体的依赖--> <dependency> <!--依赖所属群组的id--> <groupId>com.mark</groupId> <!--依赖所属项目的id--> <artifactId>Maven_02_Pojo</artifactId> <!--依赖版本号--> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies>
依赖传递
依赖传递冲突问题
可选依赖:optional
标签。对外隐藏当前资源。——不透明
可选依赖是隐藏当前工程所依赖的资源,隐藏后对应资源将不具有依赖传递性
<dependency> <groupId>com.mark</groupId> <artifactId>Maven_02_Pojo</artifactId> <version>1.0-SNAPSHOT</version> <!--可选依赖是隐藏当前工程所依赖的资源,隐藏后对应资源将不具有依赖传递性--> <optional>true</optional> </dependency>
排除依赖:主动端来依赖的资源。——不需要
在引入其他模块坐标时,该有不需要的依赖可使用排除依赖标签exclusions
,被排除的资源无需指定版本
<dependency> <groupId>com.mark</groupId> <artifactId>Maven_03_Dao</artifactId> <version>1.0-SNAPSHOT</version> <!--隐藏当前资源对应的依赖关系--> <exclusions> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> </exclusions> </dependency>
可选依赖和排除依赖区别:
聚合
聚合就是将多个模块组织成一个整体,同时进行项目构建的过程称为聚合
聚合工程:通常是一个不具有业务功能的“空”工程(有且仅有一个pom文件)
作用:使用聚合工程可以将多个工程编组,通过对聚合工程进行构建,实现对所包含的模块进行同步构建
步骤:
创建新的模块
修改打包方式为pom
<groupId>com.mark</groupId> <artifactId>Maven_00_parent</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging>
设置管理的模块名称
<!--设置管理的模块名称--> <modules> <module>../Maven_01_SSM</module> <module>../Maven_02_Pojo</module> <module>../Maven_03_Dao</module> </modules>
继承
概念:继承描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承。简单来说,父工程的依赖子工程可以使用。
作用:
实现:
在父工程的pom中设置打包类型为pom
在父工程的pom文件中配置依赖关系(子工程将沿用父工程的依赖关系)
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency> ...... </dependencies>
在父工程中配置子工程可以选择的依赖
<!--定义依赖管理--> <dependencyManagement> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement>
在子工程中配置当前工程所继承的父工程
<parent> <groupId>com.mark</groupId> <artifactId>Maven_00_parent</artifactId> <version>1.0-SNAPSHOT</version> <!--相对路径,可以快速地找到继承的工程。可以不写--> <relativePath>../Maven_00_parent/pom.xml</relativePath> </parent>
子工程这时就可以使用父工程的依赖,同时还可以配置父工程中可选的依赖坐标
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies>
子工程中使用父工程中的可选依赖时,仅需要提供群组id和项目id,无需提供版本,版本由父工程统一提供,避免版本冲突
子工程中还可以定义父工程中没有定义的依赖关系
聚合与继承的区别
属性的配置与使用
<properties> <spring.version>5.2.10.RELEASE</spring.version> <junit.version>4.12</junit.version> </properties>
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> </dependencies> <!--定义依赖管理--> <dependencyManagement> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement>
配置文件加载属性
定义属性
<properties> <spring.version>5.2.10.RELEASE</spring.version> <junit.version>4.12</junit.version> <jdbc.url>jdbc:mysql://127.0.0.1:3306/ssm_db</jdbc.url> </properties>
配置文件中引用属性
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=${jdbc.url} jdbc.username=root jdbc.password=123
开启资源文件目录加载属性的过滤器
<build> <!--扩大maven构建范围--> <resources> <resource> <!--使指定的目录里的文件可以解析${}的格式--> <directory>${project.basedir}/src/main/resources</directory> <filtering>true</filtering> </resource> </resources> </build>
配置maven打war包时,可以忽略web.xml的检查
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.2.3</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins>
其他属性(了解)
版本管理
多环境开发
<!--配置多环境开发--> <profiles> <!--定义开发环境:生产环境--> <profile> <!--定义环境对应的唯一名称--> <id>env_dep</id> <!--定义环境中专属的属性值--> <properties> <jdbc.url>jdbc:mysql://127.1.1.1:3306/ssm_db</jdbc.url> </properties> <!--设定是否为默认启动环境--> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <!--定义开发环境:开发环境--> <profile> <id>env_pro</id> <properties> <jdbc.url>jdbc:mysql://127.2.2.2:3306/ssm_db</jdbc.url> </properties> </profile> <!--定义开发环境:测试环境--> <profile> <id>env_test</id> <properties> <jdbc.url>jdbc:mysql://127.3.3.3:3306/ssm_db</jdbc.url> </properties> </profile> </profiles>
使用多环境(构建过程)
mvn 指令 -P 环境定义id
范例:
mvn install -P env_pro
跳过测试
应用场景
方式一:快速跳过
方式二:配置跳过
<plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.12.4</version> <configuration> <!--false:不跳过所有test--> <skipTests>false</skipTests> <!--排除掉不参与测试的内容--> <excludes>**/BookServiceTest.java</excludes> </configuration> </plugin> </plugins>
方式三:命令跳过
mvn package -D skipTests
私服简介
Nexus
使用:
启动服务器(命令行启动)
nexus.exe /run nexus
访问服务器(默认端口8081)
http://localhost:8081
修改基础配置信息
修改服务器运行配置信息
私服仓库分类
仓库组是小组内共享资源用的,宿主仓库是小组内自己用的,代理仓库是所有项目组公用的
资源上传与下载
本地仓库访问私服配置
创建自己的两个仓库
配置本地仓库对私服的访问权限:打开maven的settings.xml文件,找到servers
<!-- 配置访问私服的权限 --> <server> <!-- 私服中的服务器id名称 --> <id>mark-snapshot</id> <!-- admin --> <username>admin</username> <!-- 123 --> <password>123</password> </server> <server> <!-- 私服中的服务器id名称 --> <id>mark-release</id> <!-- admin --> <username>admin</username> <!-- 123 --> <password>123</password> </server>
设置仓库组管理范围
配置映射关系
<!-- 私服的访问路径 --> <mirror> <!-- 仓库组的id --> <id>maven-public</id> <mirrorOf>*</mirrorOf> <url>http://localhost:8081/repository/maven-public/h</url> </mirror>
私服资源上传与下载
配置工程保存在私服中的位置
<!--配置当前工程保存在私服中的具体位置--> <distributionManagement> <repository> <id>mark-release</id> <url>http://localhost:8081/repository/mark-release/</url> </repository> <snapshotRepository> <id>mark-snapshot</id> <url>http://localhost:8081/repository/mark-snapshot/</url> </snapshotRepository> </distributionManagement>
上传指令:deploy(在上传前要为所有的模块配置继承关系)
然后接可以在仓库中看到上传的资源了
因为pom.xml中设置了版本为SNAPSHOT,因此只上传到了snapshot仓库,当修改为RELEASE时便可上传到mark-release仓库中
<groupId>com.mark</groupId> <artifactId>Maven_00_parent</artifactId> <version>1.0-RELEASE</version> <packaging>pom</packaging>
可以更换中央仓库: