Spring、SpringMVC总结
> 小编最近二刷Spring,SpringMVC,之前第一次接触Spring,对Spring一知半解,总感觉差点意思,听了郝老师的讲解,真的是豁然开朗,黑马郝老师的功底是非常深厚的,对源码的认识也非常到位,听了之后绝对会有很大收获。开发知识浩如烟海,一起努力吧,加油!
Spring经典教程——B站空降:黑马程序员新版Spring零基础入门到精通,一套搞定spring全套视频教程(含实战源码)_哔哩哔哩_bilibili
0. Spring 容器Bean基础知识
0.1 创建Bean的三种方式(Bean的实例化方式)
通过构造方法创建(无参/有参)有参需要添加参数<contrutor_arg name=“” vlaue = “”(如果参数未引入数据类型ref=“”)>
通过实例工厂创建对象
通过静态工厂创建对象
0.2 依赖注入的种类
自动装配 antowire = “byType(该类型bean实例只能有一个,否则将会报错)/byName/no(默认)” bean的no == name(后期no属性会转为name)
注入基本数据类型
注入引入数据类型
0.3 BeanFactory和Application Context的关系
前者是后者的父类,前者延迟加载,后者立即加载。其中Appltication Context又称为Spring容器
1. Spring 容器Bean加载执行顺序:
2. spring后处理器(spring注解开发原理/spring功能扩展原理)
视频来源: 45-Spring的Bean后处理器-再次完善实例化基本流程图哔哩哔哩bilibili
作用:修改或添加BeanFactory对象中的BeanDefinitionMap中的内容,使得单例池中的对象发生变化
BeanFactoryPostProcessor: 也叫Bean工厂后处理器,在BeanDefinitionMap初始化后调用(对Bean定义的操作) ---->spring注解开发原理
BeanPostPorcessor: 也叫Bean后处理器,在Bean对象创建之后,存入SingletonObjets之前调用(对Bean对象的操作)----->Spring功能扩展原理,通过动态代理得到Bean增强对象
3. Bean的生命周期
实例化阶段:创建半成品的Bean对象(不完整,缺少依赖注入、AOP等拓展)
初始化阶段:Bean功能实现的核心阶段,包括依赖注入、Spring后处理器、AOP等操作实现Bean对象功能的拓展。详细的初始化阶段步骤见下图:
对象的属性列表也会以Map集合的形式存储在BeanDefintion中的propertyValues,Spring先检查propertyValues是否有内容,然后才会进行依赖注入。
Spring依赖注入时的循环引用问题(Spring三级缓存)
SpringIOC整体流程总结:
Bean定义----> Bean实例化阶段----->Bean初始化阶段(依赖注入、增强)----->Bean的存储阶段
4. 注解开发
Component注解:相当与xml的<bean>标签,用于标注Bean,其有3个子标签,分别对应三层架构的不同层
① Controller:表示层、表现层、web层
② Service:业务逻辑层
③ Repository: 持久化层DAO
依赖注入
@Autowired注解:默认通过类型注入,如果该类型有不止一个实例,则默认查找同名对象注入,如果无同名对象则会报错。
@Qualifier注解:与Autowired注解一起使用,根据名称注入相应的Bean。
@Resource注解:是javax的注解,不是spring的注解,Spring对其进行了解析。不指定名称参数时,按照类型注入,指定名称时,按照名称注入,(综合了Qualifier注解和@Autowired注解)一般不用。
Bean注解->非自定义Bean(第三方类):对于一些非自定以的第三方类,我们无法在源码上添加注解,需要使用如下方法。原理:以工厂方式配置Bean
① 提供一个方法,返回值设为第三方类的对象。
② 在方法上添加@Bean(“beanName”)注解标注。
③ ①中方法所在的类必须交给Spring管理(必须使用Component注解)
④ 如果方法需要提供形参,则注入的格式如下:
@Component public class OtherBean{ @Bean("dataSource")//如果设置名字,就是所设的名字,否则默认为方法的名字 public DataSource dataSource( /*方法形参*/ @Value("${jdbc.driver}") String dirverClassName, @Autowired @Qualifier("userDao2") UserDao userDao, UserService userService ){ //... return new DataSource(); } } //注:@Autowired @Qualifier("userDao2") UserDao userDao当为形参自动注入时,@Autowired可以省略。@Qualifier("userDao2") UserDao userDao即可。形参按照类型注入时,@Autowired可以省略,如果按照名字注入时,需要用Qualifier(“xxx”)根据名称匹配
使用配置类替代xml,用注解标注xml中的非Bean内容
@Configuration //标注当前类是一个配置类,并交给Spring容器管理 @ComponentScan({"com.itheima","xxx"}) //需要扫描的包(扫描带Component的包) @PropertySource({"classpath:jdbc.properties"}) //属性资源位置 @Import(OtherBean.class) //导入其他配置类 public class SpringConfig{ }
Spring整合Mybatis
@Configuration //声明该类是核心配置类 @ComponentScan("com.itheima") //开启spring注解扫描 @PropertySource("classpath:db.properties") //引入properties文件 @MapperScan("com.itheima.dao") //MyBatis扫描dao接口 public class Application { //定义属性 为属性注入数据(数据的来源上面引入的db.properties文件) @Value("${db.driverClass}") private String driverClass; @Value("${db.url}") private String url; @Value("${db.username}") private String username; @Value("${db.password}") private String password; //创建数据源返回数据源,Spring会自动调用该方法,并将该对象交给IOC容器管理 @Bean public DataSource dataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(driverClass); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); return druidDataSource; } //创建SqlSessionFactoryBean对象,设置形参,Spring会自动去调用IOC容器中已有的数据源 @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){ SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); retun sqlSessionFactoryBean; } }
5.Spring整合web环境
Listener在框架中主要监听域的创建与销毁。
在web层(Controller)需要调用Spring容器中的Service和Dao层代码,如果每个Servlet都在运行时创建一个Spring容器,那么会导致内存消耗太大。正确的做法是将Spring容器放在web三大作用域(application域、session域、request域)的application中(也叫ServletContext域),使得所有servlet都能访问到同一个Spring容器。
现在的目标如下:
ApplicationContext(Spring容器)创建一次,配置类加载一次
在web服务器启动时,就执行Spring容器的创建,后续直接从容器中获取Bean使用(监听ServletContextListener创建Spring容器)
ApplicationContext的应用需要在web层任何位置都可以获取到(application域)
Web层不使用框架开发Servlet(Web)程序的缺点(Servlet充当Controller的问题):
将每个Servlet的共有行为和特有行为进行拆分:
共有:解析请求参数、封装数据、放到域中、指派视图 --->前端控制器
特有行为放到后端控制器中
使用框架实现前端控制器,封装共有行为
向web层中注入业务层的Bean
SpringMVC框架
见 6.
6.SpringMVC
SpringMVC概述
SpringMVC环境搭建步骤
编写的Controller层示例代码:
@Controller public class QuickController{ @RequestMapping("/show")//请求路径(前缀) public void show(){ System.out.println("hello...");//控制台打印 } } //报500错误分析:ViewResolver.... //默认后端控制器Controller返回视图名字,由ViewResolver视图解析器解析,现在返回的是空,会报错,可以创建一个界面,并将界面名字作为方法的返回内容即可。注解分析:
@Controller注解:将该Controller交给Spring容器管理
@RequestMapping:请求路径,可以放在方法上,默认为get请求路径,Request.GET,也可以放在类上,作为请求路径的默认前缀。
Controller中如何直接注入Service
SpringMVC是Spring的子容器,当程序中配置了SpringMVC和Spring容器后,SpringMVC中的Controller就可以调用Spring容器中的对象,从而实现了Contoller中注入Service对象。
SpringMVC相关组件
① HandlerMapping(处理器映射器):匹配映射路径对应的Handler,
② HandlerAdapter(处理器适配器) :执行①中匹配的方法
③ ViewResolver(视图解析器) :处理视图模型对象
SpringMVC请求处理:
请求映射路径的配置
① @RequestMapping(“/quick”)请求路径,可以放在方法上,默认为get请求路径,Request.GET,也可以放在类上,作为请求路径的默认前缀。
② @GetMapping(“/xxx”)
③ @PostMapping(“/xxx”)
接收客户端请求数据
① Get请求。
当请求路径中key名字与形参名字一致时:
SpringMVC会自动解析请求路径,当请求参数key名字与形参名字一致时,会根据名字自动匹配,将对应vlaue注入给同名的形参中。
当请求路径中key名字与形参名字不一致时:
使用@RequestParam(“请求参数名”)注解,在形参类型前面使用,使得请求参数与形参对应
当请求路径中的参数有多个同名key时(比如复选框发送给后端的数据)
“http://localhost/yu/param1?hobby=football&hobby=basketball&hobby=pingpong”
后端可以使用String[]数组接收,也可以使用List集合接收,当使用List集合接收时,必须加@RequestParam注解,表示只需要将内容加入结合即可,不需要创建对象。应为当形参为Bean时,SpringMVC会尝试创建对象,而List是个接口,不能创建对象,所以不加@RequestParam会报错。Map集合同理,也要加RequestParam
所以,数组不需要加,集合需要加@RequestParam
@RequestParam注解解析: @RequestParam(vlaue="请求路径中的名字",request=true,defaultVlaue:"hhh") vlaue:请求路径中的名字 request:是否为必须使用的形参, 默认为faluse,如果路径中未匹配到参数,如果为引用数据类型,则为null,如果为基本数据类型,则报错。因为基本数据类型不能赋值为null 如果设为true,则必须再路径中声明参数,否则会报错 defaultVlaue:默认值
接收客户端请求数据如何自动封装为实体类对象
只需要在形参中添加想要封装的对象,实体类需要是一个javaBean(无参构造和set方法)
SpringMVC会解析请求路径,通过反射机制创建对象。
Restful风格请求参数的获取:请求路径格式http://localhost/yu/user/zhangsan/10
使用注解@PathVariable(“请求路径参数名”)
例如:
如果GetMapping(“/user/{name}/{id}”)
这里可以在形参前使用@PathVariable(“name”)、@PathVariable(“age”)
实例:
@Controller @RequestMapping("/yu") public class ParamController{ //请求路径:http://localhost/yu/param1?username=zhangsan&age=18 @GetMapping("/param1") public String param1(String username,int age){ System.out.println(username); System.out.println(age); return "index.jsp"; } //请求路径:http://localhost/yu/param2?username=zhangsan&age=18 @GetMapping("/param2") public String param1(@RequsetParam("username")String name,int age){ System.out.println(username); System.out.println(age); return "index.jsp"; } //请求路径:http://localhost/yu/param3?hobby=lq&hobby=zq&hobby=pp @GetMapping("/param3") public String param1(String[] hobby){ System.out.println(username); System.out.println(age); return "index.jsp"; } //请求路径:http://localhost/yu/param3?hobby=lq&hobby=zq&hobby=pp @GetMapping("/param3") public String param1(@RequestParam List<String> hobby){ System.out.println(username); System.out.println(age); return "index.jsp"; } //------前端数据自动封装为实体(反射,调用set方法) //请求路径:http://localhost/yu/param4?username=zhangsan&age=18&address.province=bj&address.country=china @GetMapping("/param4") public String param1(User user){ System.out.println(username); System.out.println(age); return "index.jsp"; } } //其中User类的定义如下: public class User{ private String username; private int age; private Address address; public void setUsername(String username){} //... } //Address类的定义如下 public class Address{ private String country; private String province; public void setCountry(String country){} public void setProvince(String province){} //... -------------------------------------------------------- ---------Restful风格中请求参数的获取------------- -------------------------------------------------------- //请求路径:http://localhost/yu/user/zhangsan/10 @GetMapping("/user/{name}/{idxxx}") public String param1(@PathVariale("name") String username,@PathVariale("idxxx") int age){ System.out.println(username); System.out.println(age); return "index.jsp"; } }② Post请求。
Post请求客户端的请求内容存储在请求体中,想要将请求体中的数据传给形参,则需要在形参前使用@RequestBody注解。
实例:
@Controller @RequestMapping("/yu") public class ParamController{ //请求路径:http://localhost/yu/requestbody @GetMapping("/requestbody") public String param1(@RequestBody String jsonStr){ System.out.println(jsonStr); return "index.jsp"; } } //通常为前端在请求体中传入json格式字符串,发送给后端,后端通过@RequestBody注解接收请求体中的内容。 SpringMVC响应处理
分类:同步方式(转发、重定向) 异步方式(ajax+Restful风格+json)
区别:
① 同步方式回写数据,是将数据响应给浏览器;而异步方式一般是回写给AJAX引擎(ajax通过监听的方式,得到服务器的数据)。即谁访问服务器端,服务器端就就回写给谁。
② 同步方式一般响应的是无特定格式的字符串,而异步方式通常为JSON格式字符串。
同步方式:
同步方式:
转发和重定向
响应数据模型(ModelAndView)
直接回写数据给客户端
@ResponseBody注解:由于SpringMVC默认返回的字符串为视图名,在方法上加上或者也可以直接在类上添加,如果在类上添加,则就等于给所有方法都添加了@ResponseBody注解。@RespinseBody注解,表示返回的字符串是响应体内容而非视图名。
如果方法返回值为引用数据类型,则改注解还会自动将返回的对象转为JSON格式字符串。发生给请求方
由于目前项目大都采用前后端分离的开放方式,所有方法返回的都是JSON格式字符串,所以可以将@ResponseBody提到类的头部声明,这样,类的头部就有了两个注解,@Controller和@ResponseBody注解,这两个注解可以合并为@RestController注解。
//@Controller //@ResponseBody @RestController @RequestMapping("/yu") public class ParamController{ //请求路径:http://localhost/yu/responsebody @GetMapping("/responsebody") // @ResponseBody //告诉SpringMVC返回的字符串不是视图名,而是响应体的数据 public String param1(@RequestBody String jsonStr){ System.out.println(jsonStr); return "hello world"; } }@Controller @RequestMapping("/yu") public class ParamController{ //请求路径:http://localhost/yu/responsebody @GetMapping("/responsebody") @ResponseBody //告诉SpringMVC返回的字符串不是视图名,而是响应体的数据 public String param1(@RequestBody String jsonStr){ System.out.println(jsonStr); return "hello world"; } }异步方式:
异步方式:见直接回写数据给客户端
//@Controller //@ResponseBody @RestController @RequestMapping("/yu") public class ParamController{ //请求路径:http://localhost/yu/responsebody @GetMapping("/responsebody") // @ResponseBody //告诉SpringMVC返回的字符串不是视图名,而是响应体的数据 public String param1(@RequestBody String jsonStr){ System.out.println(jsonStr); return "hello world"; } }
7.Restful风格数据提交
什么是restful风格。
每个模块都是一个名词,用UR地址表示模块资源。
请求:用请求方式表示当前模块下业务的动作(GET表示查询、POST表示插入、PUT表示更新、Delete表示删除)
响应:用HTTP响应状态码表示结果,通常封装在Result实体中(状态码,状态信息,响应数据)
Restful风格的请求,常见的模块规则有以下三种
模块 URI资源 用户模块 user http://localhost/user 商品模块 product http://localhost/product 账户模块 account http://localhost/account 日志模块 log http://localhost/log
常见请求动作
8.SpringMVC访问静态资源的三种方法
SpringMVC无法访问静态资源的原因:原有的javaWeb程序中,存在一个DefaultServlet(Tomcat的config的web.xml可以找到),按照访问的文件名查找相应的Servlet,如果没有匹配到Servlet,则会用资源名找DefaultServlet,DefaultServlet具备查找静态资源的能力。而Tomcat中,DefaultServlet的匹配路径是“/”,在SpringMVC中,前端控制器DispatherServlet的匹配路径也是“/”,也就是说DispatherServlet将Tomcat中的DefaultServet覆盖掉了。所以无法访问静态资源。
解决方法:
再次激活DefalutServlet url-pattern配置更加精确一点
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> 或者: <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/img/*</url-pattern> </servlet-mapping>
配置资源的映射路径
请求路径为"/image/*"到"/img"下寻找资源 <mvc:resources mapping="/image/*" location="/img/"></mvc:resources>
在spring-mvc.xml中配置
<mvc:default-servlet-handler>
,原理:该方式是在SpringMVC容器中注册了DefaultServletHttpRequestHandler处理器,改处理器会自动寻找静态资源。
采用方法2和方法3导致的问题:127-SpringMVC框架-annotation-driven的使用机器原理_哔哩哔哩_bilibili:
SpringMVC加载组件时,如果容器中没有处理器映射器时会默认加载处理器映射器(HanderMapping)的组件其中重要的是RequestMappingHanderMapping,用于解析请求路径的组件,但如果手动加载了其他的HanderMapping,则默认加载策略会失效。而配置2或3标签时,涉及到自定义命名空间解析,底层会注入SimplyUrlHanderMapping组件,此时SpringMVC将不会加载默认组件,也就没有了RequestMappingHanderMapping(解析注解),需要在spring-mvc.xml中向容器中注入一个RequestMappingHanderMapping。这样就实现了既能加载静态资源,又能解析注解,程序能正常运行。
由于上述配置繁琐的原因,Spring提供了一个标签--->MVC注解驱动
9.mvc注解驱动
mvc注解驱动内部会
注册RequestMappingHandlerMapping
注册RequestMappingHandlerAdapter
注入Json消息转换器等(默认为MappingJackson2HttpMessageConverter)
<!--mvc注解驱动--> <mvc:annotation-driven/> <!--配置DefalutServletHttpRequestHandler,用于扫描静态资源--> <mvc:default-servlet-handler>
*10.Spring容器 SpringMVC容器 web容器的关系
出处声明:Spring容器 SpringMVC容器 web容器的关系 - 知乎 (zhihu.com)
首先: springmvc和spring它俩都是容器,容器就是管理对象的地方,例如Tomcat,就是管理servlet对象的,而springMVC容器和spring容器,就是管理bean对象的地方,再说的直白点,springmvc就是管理controller对象的容器,spring就是管理service和dao的容器,这下你明白了吧。所以我们在springmvc的配置文件里配置的扫描路径就是controller的路径,而spring的配置文件里自然配的就是service和dao的路径。
至于他是怎么管理起来的,又是怎么注入属性的,这就涉及到他们底层的实现技术了
其次, spring容器和springmvc容器的关系是父子容器的关系。spring容器是父容器,springmvc是子容器。在子容器里可以访问父容器里的对象,但是在父容器里不可以访问子容器的对象,说的通俗点就是,在controller里可以访问service对象,但是在service里不可以访问controller对象
所以这么看的话,所有的bean,都是被spring或者springmvc容器管理的,他们可以直接注入。然后springMVC的拦截器也是springmvc容器管理的,所以在springmvc的拦截器里,可以直接注入bean对象。
而web容器又是什么鬼,
web容器是管理servlet,以及监听器(Listener)和过滤器(Filter)的。这些都是在web容器的掌控范围里。但他们不在spring和springmvc的掌控范围里。因此,我们无法在这些类中直接使用Spring注解的方式来注入我们需要的对象,是无效的,
web容器是无法识别的。
但我们有时候又确实会有这样的需求,比如在容器启动的时候,做一些验证或者初始化操作,这时可能会在监听器里用到bean对象;又或者需要定义一个过滤器做一些拦截操作,也可能会用到bean对象。
web容器中有servlet容器,spring项目部署后存在spring容器和springmvc容器。其中spring控制service层和dao层的bean对象。springmvc容器控制controller层bean对象。servlet容器控制servlet对象。项目启动是,首先 servlet初始化,初始化过程中通过web.xml中spring的配置加载spring配置,初始化spring容器和springmvc容器。待容器加载完成。servlet初始化完成,则完成启动。 HTTP请求到达web容器后,会到达Servlet容器,容器通过分发器分发到具体的spring的Controller层。执行业务操作后返回结果。
总结:
Tomcat在启动时给每个Web应用创建一个全局的上下文环境,这个上下文就是ServletContext,其为后面的Spring容器提供宿主环境。
Tomcat在启动过程中触发容器初始化事件,Spring的ContextLoaderListener会监听到这个事件,它的contextInitialized方法会被调用,在这个方法中,Spring会初始化全局的Spring根容器,这个就是Spring的IoC容器,IoC容器初始化完毕后,Spring将其存储到ServletContext中,便于以后来获取。
Tomcat在启动过程中还会扫描Servlet,一个Web应用中的Servlet可以有多个,以SpringMVC中的DispatcherServlet为例,这个Servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个Servlet请求。
Servlet一般会延迟加载,当第一个请求达到时,Tomcat&Jetty发现DispatcherServlet还没有被实例化,就调用DispatcherServlet的init方法,DispatcherServlet在初始化的时候会建立自己的容器,叫做SpringMVC 容器,用来持有Spring MVC相关的Bean。同时,Spring MVC还会通过ServletContext拿到Spring根容器,并将Spring根容器设为SpringMVC容器的父容器,请注意,Spring MVC容器可以访问父容器中的Bean,但是父容器不能访问子容器的Bean, 也就是说Spring根容器不能访问SpringMVC容器里的Bean。说的通俗点就是,在Controller里可以访问Service对象,但是在Service里不可以访问Controller对象。