Spring结构
Spring结构
分层
- Web 层:SpringMVC
- Dao 层:Spring JDBCTemplate
- 业务层:Spring 提供的声明式事务控制
优势
- 方便解耦,简化开发
- AOP 编程的支持
- 声明式事务控制
- 方便程序的测试
- 方便集成各种优秀的框架
- 降低 JavaEE API 的使用难度
- Java 源码是经典学习范例
Spring 程序开发步骤
- 导入 Spring 开发的基本包坐标
- 创建 Bean
- 创建 applicationContext.xml
- 在配置文件中进行配置
- 创建 ApplicationContext 对象 getBean
依赖注入
Spring 框架核心 IOC 的具体实现。
数据源(连接池)作用
- 数据源是提高程序性能出现的
- 事先实例化数据源,初始化部分连接资源
- 使用连接资源时从数据源中获取
- 使用完毕后将连接资源归还给数据源
常见的数据源:DBCP、C3P0、BoneCP、Druid
数据源开发步骤
- 导入数据源的坐标和数据库驱动
- 创建数据源对象
- 设置数据源的基本连接数据
- 使用数据源获取连接资源和归还连接资源
Spring 集成 Junit 步骤
- 导入 spring 集成 Junit 的坐标
- 使用 @Runwith 注解替换原来的运行期
- 使用 @ContextConfiguration 指定配置文件或配置类
- 使用 @Autowired 注入需要测试的对象
- 创建测试方法进行测试
Spring 集成 Web 环境
- 在 web.xml 中配置 ContextLoaderListener 监听器(导入 spring-web 坐标)
- 使用 WebApplicationContextUtils 获得应用上下文 ApplicationContext
SpringMVC 开发步骤
- 导入 SpringMVC 包
- 配置 SpringMVC 核心控制器 DispathcerServlet
- 创建 Controller 类和视图页面
- 使用注解配置 Controller 类中业务方法的映射地址
- 配置 SpringMVC 核心文件 spring-mvc.xml (配置组件扫描)
- 执行访问测试
SpringMVC 的执行流程
- 用户发送请求至前端控制器 DispatcherServlet
- DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器
- 处理器映射器找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet
- DispatcherServlet 调用 HandlerAdapter 处理器适配器
- HandlerAdapter 经过适配调用具体的处理器( Controller,也叫后端控制器)
- Controller 执行完成返回 ModelAndView
- HandlerAdapter 将 Controller 执行结果 ModelAndView 返回给 DispatcherServlet
- DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器
- ViewReslover 解析后返回具体 View
- DispatcherServlet 根据 View 进行进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet 响应用户
SpringMVC 的相关组件
- 前端控制器:DispatcherServlet
- 处理器映射器:HandlerMapping
- 处理器适配器:HandlerAdapter
- 处理器:Handler
- 视图解析器:ViewReslover
- 视图:View
SpringMVC 的注解和配置
- 请求映射注解:@RequestMapping
视图解析器配置
REDIRECT_URL_PREFIX = "redirect:" FORWARD_URL_PREFIX = "forward:" prefix=""; suffix="";
SpringMVC 的数据响应方式
页面跳转:
直接返回字符串
此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转
- 通过 ModelAndView 对象返回
回写数据
直接返回字符串
将需要回写的字符串直接返回,但此时需要通过 @ResponseBody 注解告知 SpringMVC 框架,方法返回的字符串不是跳转是直接在 http 响应体中返回。
@ResponseBody // 告知 SpringMVC 框架,该方法不进行视图跳转 直接进行数据响应
返回对象或集合
SpringMVC 获取请求数据
获得基本参数
Controller 中的业务方法的参数名称要与请求参数的 name 一致,参数值会自动映射匹配。
获得 POJO 类型参数
Controller 中的业务方法的 POJO 参数的属性名与参数的 name 一致,参数会自动映射匹配。
获得数组类型参数
Controller 中的业务方法数组名称与请求参数的 name 一致,参数会自动映射匹配。
获取集合类型参数
- 获取集合参数时,要将集合参数包装到一个 POJO 中才可以。
- 当时用 ajax 提交时,可以指定 contentType 为 json 形式,那么在方法参数位置使用 @RequestBody 可以直接接收集合数据而无需使用 POJO 进行包装。
开放静态资源访问
<mvc:default-servlet-handler/>
请求数据乱码问题
当 post 请求时,数据会出现乱码,我们可以设置一个过滤器来进行编码的过滤。
<!-- 配置全局过滤的 filter --> <filter> <filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
参数绑定注解 @RequestParam
当请求的参数名称与 Controller 的业务方法参数名称不一致时,就需要通过 @RequestParam 注解显示的绑定。
@RequestParam 的使用
- value: 与请求参数名称
- required: 此在指定的请求参数是否必须包括,默认是 true,提交时如果没有此参数则报错。
- defaultValue: 当没有指定请求参数时,则使用指定的默认值赋值。
获得 Restful 风格的参数
Restful 风格的请求是使用 “url + 请求方式” 表示一次请求目的,HTTP 协议里面四个表示操作方式的动词如下:
- GET:用于获取资源
- POST:用于新建资源
- PUT:用于更新资源
- DELETE:用于删除资源
例如:
- /user/1 GET: 得到 id = 1 的 user
- /user/1 DELETE: 删除 id = 1 的 user
- /user/1 PUT: 更新 id = 1 的 user
- /user POST: 新增 user
上述 url 地址 /user/1 中的就是要获得的请求参数,在 SpringMVC 中可以使用占位符进行参数绑定。地址 /user/1 可以写成 /user/{id},占位符 {id} 对应的值就是1,。在业务方法中我们可以使用 @PathVariable 注解进行占位符的匹配获取工作。
自定义类型转换器
- SpringMVC 默认提供了一些常用的类型转换器,例如客户端提交的字符串转换成 int 型进行参数设置。
- 但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。
步骤:
定义转换器类实现 Converter 接口
public class DateConverter implements Converter<String, Date> { @Override public Date convert(String dateStr) { // 将日期的字符串转换成日期对象 返回 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; try { date = format.parse(dateStr); } catch (ParseException e) { e.printStackTrace(); } return date; } }
在配置文件中声明转换器
<!-- 声明转换器 --> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="com.converter.DateConverter"/> </list> </property> </bean>
在 <annotation-driven> 中引用转换器
<!-- mvc 的注解驱动 --> <mvc:annotation-driven conversion-service="conversionService"/>
获得 Servlet 相关 API
SpringMVC 支持使用原始 ServletAPI 对象作为控制器方法的参数进行注入,常用对象如下:
- HttpServletRequest
- HttpServletResponse
- HttpSession
获得请求头
@RequestHeader
使用 @RequestHeader 可以获得请求头信息,相当于 web阶段学习的 request.getHeader(name)
@RequestHeader 注解的属性如下:
- value:请求头的名称
- required:是否必须携带此请求头
@CookieValue
使用 @CookieValue 可以获得指定 Cookie 的值
@CookieValue 注解的属性如下:
- value:指定 cookie 的名称
- required:是否必须携带此 cookie
文件上传
文件上传客户端三要素
- 表单项 type = "file"
- 表单提交方式是 post
- 表单的 enctype 属性是多部分表单形式,及 enctype = "multiypart/form-data"
文件上传原理
- 当 form 表单修改为多部分表单时,request.getParameter() 将失效。
- enctype = "application/x-www-form-urlencoded" 时,form 表单的正文内容格式是:key = value & key = value & key = value
当 from 表单的 enctype 取值为 multipart/form-data 时,请求正文内容就变成多部分形式:
单文件上传的步骤
导入 fileupload 和 io 坐标
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>
配置文件上传解析器
<!-- 配置文件上传解析器 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 上传文件总大小 --> <property name="maxUploadSize" value="500000"/> <!-- 上传单个文件的大小 --> <property name="maxUploadSizePerFile" value="500000"/> <!-- 上传文件的编码类型 --> <property name="defaultEncoding" value="UTF-8"/> </bean>
编写文件上传代码
public void save22(String username, MultipartFile upload) throws IOException { System.out.println(username); // 获得上传文件的名称 String originalFilename = upload.getOriginalFilename(); upload.transferTo(new File("D:\\SSM_2022\\Spring\\spring_mvc\\src\\main\\webapp\\docx\\" + originalFilename)); }
多文件上传
JDBCTemplate 基本使用
JDBCTemplate 开发步骤
导入 spring-jdbc 和 spring-tx 坐标
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.20</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.20</version> </dependency>
- 创建数据库表和实体
创建 JDBCTemplate 对象
JdbcTemplate jdbcTemplate = new JdbcTemplate(); // 设置数据源对象 要知道数据库在哪 jdbcTemplate.setDataSource(dataSource);
执行数据库操作
更新操作:
jdbcTemplate.update(sql, params);
查询操作:
jdbcTemplate.query(sql, Mapper, params); jdbcTemplate.queryForObject(sql, Mapper, params);
Spring 环境搭建步骤
- 创建工程
- 导入静态页面
- 导入需要坐标 pom.xml
创建包结构
- controller
- service
- dao
- domain
- utils
- 导入数据库脚本
- 创建 POJO 类
创建配置文件
- applicationContext.xml
- spring-mvc.xml
- jdbc.properties
- log4j.properties
SpringMVC 拦截器 interceptor
interceptor 的作用
相当于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
自定义 Interceptor 步骤
- 创建拦截器实现 HandlerInterceptor 接口
配置拦截器
<!-- 配置拦截器 --> <mvc:interceptors> <mvc:interceptor> <!-- 对哪些资源进行拦截操作 --> <mvc:mapping path="/**"/> <bean class="com.interceptor.MyInterceptor"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.interceptor.MyInterceptor2"/> </mvc:interceptor> </mvc:interceptors>
- 测试拦截器的拦截效果
SpringMVC 异常处理
异常处理思路
系统中的异常包括两大类:预期异常和运行时异常( RuntimeException ),前者通过捕获异常从何获取异常信息,后者只要通过规范代码开发、测试等手段减少运行时异常的发生。
两种方式
使用 SpringMVC 提供的简单异常处理器 SimpleMappingExceptionResolver
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="defaultErrorView" value="error"/> <property name="exceptionMappings"> <map> <entry key="java.lang.ClassCastException" value="error1"/> <entry key="java.lang.ArithmeticException" value="error2"/> <entry key="java.io.FileNotFoundException" value="error3"/> <entry key="java.lang.NullPointerException" value="error4"/> <entry key="com.exception.MyException" value="error5"/> </map> </property> </bean>
- 实现 Spring 的异常处理器接口 HandlerExceptionResolver 自定义自己的异常处理器。
自定义异常处理步骤
创建异常处理器类实现 HandlerExceptionResolver
public class MyExceptionResolver implements HandlerExceptionResolver { /* 参数 Exception:异常对象 返回值 ModelAndView:跳转到错误视图 */ @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { ModelAndView modelAndView = new ModelAndView(); if (ex instanceof MyException) { modelAndView.addObject("info", "自定义异常"); } else if (ex instanceof ClassCastException) { modelAndView.addObject("info", "类转换异常"); } else if (ex instanceof ArithmeticException) { modelAndView.addObject("info", "零异常"); } else if (ex instanceof FileNotFoundException) { modelAndView.addObject("info", "文件找不到异常"); } else if (ex instanceof NullPointerException) { modelAndView.addObject("info", "空指针异常"); } else { modelAndView.addObject("info", "通用错误异常"); } modelAndView.setViewName("error"); return modelAndView; } }
配置异常处理器
<bean class="com.resolver.MyExceptionResolver"/>
- 编写异常页面
- 测试异常跳转
AOP 面向切面编程
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
动态代理的优点
在不修改源码的情况下,对目标方法进行相应的增强。
作用:完成程序之间的松耦合。
AOP 作用及其优势
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。
优势:减少重复代码,提高开发优势,并且便于维护。
常用的动态代理技术
- JDK 代理:基于接口的动态代理技术
- cglib 代理:基于父类的动态代理技术
AOP 相关概念 (术语)
- Target(目标对象):代理的目标对象
- Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类
- Joinpoint(连接点):所谓连接点是指哪些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。可以被增强的方法叫做连接点
- Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpiont 进行拦截的定义。也叫切点,被增强的方法叫做切入点
- Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。对目标方法进行增强的那个增强方法叫做增强
- Aspect(切面):是切入点和通知的结合。目标方法+增强
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。切点和增强结合的过程叫做织入。
AOP 开发明确的事项
需要编写的内容
- 编写核心业务代码(目标类的目标方法)
- 编写切面类,切面类中有通知(增强功能方法)
- 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
AOP 技术实现的内容
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
AOP 底层使用哪种代理方式
在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
基于 XML 的 AOP 开发步骤
导入 AOP 相关坐标
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.20</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency>
- 创建目标接口和目标类(内部有切点)
- 创建切面类(内部有增强方法)
- 将目标类和切面类的对象创建权交给 spring
在 applicationContext.xml 中配置织入关系
<!-- 目标对象 --> <bean id="target" class="com.aop.Target"/> <!-- 切面对象 --> <bean id="myAspect" class="com.aop.MyAspect"/> <!-- 配置织入 告诉 spring 框架 哪些方法(切点)需要进行哪些增强(前置、后置...) --> <aop:config> <!-- 声明切面 --> <aop:aspect ref="myAspect"> <!-- 切面:切点 + 通知 --> <aop:before method="before" pointcut="execution(public void com.aop.Target.save())"/> </aop:aspect> </aop:config>
切点表达式的写法:
切点表达式:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用 * 代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
- 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表
通知的类型:
通知的配置语法:
<aop:通知类型 method="切面类中的方法名" pointcut="切点表达式"></aop:通知类型>
前置通知
<aop:before> 用于配置前置通知,指定增强方法在切点方法之前执行
后置通知
<aop:after-returning> 用于配置后置通知,指定增强方法在切点方法之后执行
环绕通知
<aop:around> 用于配置环绕通知,指定增强方法在切点方法之前和之后都执行
异常抛出通知
<aop:after-throwing> 用于配置异常抛出通知,指定增强方的法在出现异常时执行
最终通知
<aop:after> 用于配置最终通知,无论增强方式执行是否有异常都会执行
切点表达式的抽取:
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。
<aop:pointcut id="myPointcut" expression="execution(* com.aop.*.*(..))"/> <aop:around method="around" pointcut-ref="myPointcut"/> <aop:after method="after" pointcut-ref="myPointcut"/>
- 测试代码
基于注解的 AOP 开发步骤
- 创建目标接口和目标类(内部有切点)
- 创建切面类(内部有增强方法)
- 将目标类和切面类的对象创建权交给 spring
- 在切面类中使用注解配置织入关系
在配置文件中开启组件扫描和 AOP 的自动代理
<!-- Aop 自动代理 --> <aop:aspectj-autoproxy/>
- 测试
切点表达式的抽取:
// 抽取 @Pointcut("execution(* com.anno.*.*(..))") public void pointcut() {} // 两种方式使用: // 1. @Before("pointcut()") public void before() { System.out.println("前置增强。。。"); } // 2. @AfterReturning("MyAspect.pointcut()") public void afterReturning() { System.out.println("后置增强。。。"); }
Spring 事务控制
编程式事务控制三大对象
PlatformTransactionManager
一个接口 根据不同的 Dao 层的实现来使用改接口对应的实现类
TransactionDefinition
事务的定义对象
事务的隔离级别
设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。
- ISOLATION_DEFAULT:当前数据库默认的
- ISOLATION_READ_UNCOMMITTED:读-未提交的,上述三种问题都不能解决
- ISOLATION_READ_COMMITTED:读-已提交的,可解决脏读
- ISOLATION_REPEATABLE_READ:可重复读,解决了不可重复读
- ISOLATION_SERIALIZABLE:串行化,所有问题都能解决,但是性能很低
事务的传播行为
TransactionStatus
事务的状态信息
基于 XML 的声明式事务控制
配置要点:
平台事务管理器配置:
<!-- 配置平台事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
事务通知的配置:
<!-- 通知 事务的增强 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 设置事务的属性信息 --> <tx:attributes> <tx:method name="*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" timeout="-1"/> </tx:attributes> </tx:advice>
事务 AOP 织入的配置:
<!-- 配置事务的 aop 织入 --> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.service.impl.*.*(..))"/> </aop:config>
基于注解的声明式事务控制
- 使用 @Transactional 在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如:隔离级别,传播行为等。
- 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。
- 使用在方法上,不同的方法可以采用不同的事务参数配置。
Xml 配置文件中要开启事务的注解驱动
<!-- 事务的注解驱动 --> <tx:annotation-driven transaction-manager="transactionManager"/>
@Service("accountService") @Transactional(isolation = Isolation.REPEATABLE_READ) public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; @Override @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) public void transfer(String outMan, String inMan, double money) { accountDao.out(outMan, money); // int i = 1/0; accountDao.in(inMan, money); } }
配置要点:
- 平台事务管理器配置(xml 方式)
- 事务通知的配置(@Transactional 注解配置)
事务注解驱动的配置
<tx:annotation-driven/>