Spring04
回顾
aop:面向切面编程(oop的补充):在程序运行期间,在不破坏源代码的情况下,实现对业务方法的增强,让开发者只关注业务部分
常用场景:
打印日志
测试程序运行时间
控制事务
...
导入依赖:spring-context和aspectjweaver
aop-xml方式
配置切面
切入点+通知
通知类型:
before
afterReturning
afterThrowing
after
around
开发中一般使用环绕通知可以保证其余代码的执行顺序,可以使用ProceedingJoinPoint获取目标方法
别忘记:1.返回值 2.异常
切点表达式
execution([修饰符] 返回值类型 包名.类名.方法名(参数类型))
execution(* com.itheima.service..*.*(..))
aop的xml+注解
通过@Aspect声明某个组件为通知类
通过@Pointcut("切入点表达式")作用在某个方法上抽取切入点表达式
通过注解在通知类中的方法上进行声明通知类型
@Before("切入点表达式或者带有切点表达式的方法名")
@AfterReturning("")
@AfterThrowing("")
@After("")
@Around("")
在配置文件中开启组件扫描和aop注解支持(自动代理)
<aop:aspectj-autoproxy/>
aop纯注解
自定义注解类
@EnableAspectJAutoProxy
JDBCTemplate:
spring提供的dao层的框架,使用的时候和DBUtils差不多
new JdbcTemplate(DataSource ds)
方法:
update
query
queryForObject
查询的时候若需要将结果封装成指定的javabean对象,使用new BeanPropertyRowMapper<>(User.class)
整合spring
把JdbcTemplate交给spring管理
内容介绍
spring的事务(非常简单,非常常用)
- 编程式事务–没人用
- 声明式事务–必须掌握
- xml
- 注解
springmvc:web层框架
- 接受请求(封装请求参数)
- 生成响应(转发,重定向,打印)
一 Spring的事务
1 事务回顾
事务就是一件完整的业务(事情),它有可能包含多个操作,这些操作要么全部成功,要么全部失败.
事务的特性:
ACID
原子性:事务是最小单元,不可切割,
一致性:事务执行前后状态要和业务状态保持一致
隔离性:事务执行的时候尽量隔离起来,不受其他事务干扰
持久性:事务一旦提交.就会写入磁盘中
不考虑隔离性会产生那些读问题
脏读:一个事务读取到另外一个事务未提交的脏数据
不可重复读:在一次事务中,两次读取的结果不一致,针对是对记录进行修改操作
虚读(幻读):在一次事务中,两次统计的结果不一致,针对的是对记录的插入和删除操作.
隔离级别
通过设置数据库的隔离级别来避免上述的问题
- read uncommitted : 读未提交,任何读问题都避免不了,效率是最高的
- read commmitted : 读已提交,可以避免脏读, oracle默认
- repeatable read : 可重复读,可以避免脏读和不可重复读, mysql默认
- serializable : 串行化,可以避免所有的读问题,效率是最低
开发中一般使用数据库默认的隔离级别
在spring中事务有两种方式:
- 编程式事务:事务代码和业务代码耦合在一起.没人用
- 声明式事务:通过配置的方式就可以将事务添加进来.
- xml方式
- 注解方式
- xml+注解
- 纯注解
2 编程式事务【了解】
开发者直接把事务的代码和业务代码耦合到一起,在实际开发中不用。
需要知道的几个类或者接口
PlatformTransactionManager
平台事务管理器,是一个接口, 提交事务和回滚事务
三个常用或常见的实现类,需要导入spring-orm包才能显示出来hibernate和jpa
* jdbc 和 mybatis
DataSourceTransactionManager
* hibernate
HibernateTransactionManager
* jpa(spring data)
JpaTransactionManager
* 分布式事务
JtaTransactionManager
TransactionDefinition
事务定义,用来定义事务的:隔离级别,执行时候的超时时间,是否只读,传播行为
事务隔离级别
1. 读未提交 【会出现脏读、不可重复读、幻读问题】
read_uncommitted
2. 读已提交【会出现不可重复读、幻读问题】
read_committed
3. 可重复读【会出现幻读问题】
repeatable_read
5. 串行化 【相当于锁表】
serializable
事务传播行为
事务传播行为指的就是当一个业务方法被另一个业务方法调用时,应该如何进行事务控制。
业务层A方法,调用业务层B方法【主要角色】
* REQUIRED【必须】默认值 运行的时候必须有事务
A方法调用C方法,如果A方法没有事务,C方法创建一个事务,如果A方法有事务,那么C方法直接使用A方法的事务控制
* SUPPORTS【支持】运行的时候 有就用 没有就不用
A方法调用C方法,如果A方法没有事务,C方法也没有事务,如果A方法有事务,那么C方法直接使用A方法的事务控制
TransactionStatus
事务的运行状态
总之记住一句话:代码在操作的时候,先使用TransactionDefinition定义事务的规则(隔离级别,传播行为,超时时间,是否只读),使用PlatformTransactionManager进行事务的控制,事务的运行状态都会保存在TransactionStatus中.
3 声明式事务【重点】
事务的代码是固定的,spring已经将事务的代码封装好了,我们需要做的操作:
- 给那些方法(切入点)添加事务,
- 事务的运行规则如何
开发者采用配置的方式来实现的事务控制,业务代码与事务代码实现解耦合,使用的AOP思想。
底层使用的aop+编程式事务
因为底层使用的aop,所以别忘了导入aspectjweaver包
别忘记配置事务管理器
- 我们使用的dao层解决方案是JdbcTemplate或者mybatis,我们使用的DataSourceTransactionManager
xml配置声明式事务(常用)
需求
使用spring声明式事务控制转账业务。
步骤分析:
- 复制昨天最后一个模块,重命名为spring04_1_tx_xml
- 导入依赖aspectjweaver
- 修改配置文件beans.xml
- 加载properties配置文件
- 组件扫描
- 配置数据源
- 配置jdbcTemplate
- 配置事务管理器
- 注入数据源
- 配置事务运行规则(属性)
- 那些方法执行的时候使用的隔离级别,传播行为,超时时间及是否只读等
- aop配置
- 定位到service中的方法上,使用spring内置的通知结合定义好的规则运行代码
spring配置文件
<!--配置事务管理器 id不建议随便起,建议叫transactionManager-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--
配置事务规则(属性)
给那些方法规定它的隔离级别,传播行为,超时时间,是否只读
id属性:给规则起个名字
transaction-manager属性:指定使用那个事务管理器,默认值为transactionManager
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
name属性:指定方法名称的,支持通配符*
propagation属性:指定事务传播行为,一般使用默认值,若是查询建议使用supports
isolation属性:指定事务的隔离级别,一般使用默认值
timeout属性:设置超时时间,一般使用默认值-1:永不超时
read-only属性:设置事务是否只读.查询操作建议设置为只读.其他使用默认值false
-->
<tx:method name="find*" propagation="SUPPORTS" isolation="DEFAULT" timeout="-1" read-only="true"/>
<!--其他的方法都使用默认值-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--aop配置-->
<aop:config>
<!--定义切入点-->
<aop:pointcut id="service_pc" expression="execution(* com.itheima.service..*.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="service_pc"/>
</aop:config>
去掉注释,使用默认值
<!--配置事务管理器 id不建议随便起,建议叫transactionManager-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="find*" propagation="SUPPORTS"read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--aop配置-->
<aop:config>
<!--定义切入点-->
<aop:pointcut id="service_pc" expression="execution(* com.itheima.service..*.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="service_pc"/>
</aop:config>
xml配置事务的好处:一劳永逸
注解+xml配置声明式事务
步骤分析:
- 复制spring04_1_tx_xml,重命名为spring04_2_tx_xml_anno
- 删除beans.xml中tx:advice标签和aop:config标签
- 在beans.xml中开启事务的注解支持 tx:annotation-driven
- 在需要添加事务的类上或者方法上通过@Transactional配置事务
spring配置文件添加了注解支持
<!--开启事务注解支持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
在service类上或者service中方法上添加了注解
纯注解【了解】
步骤分析:
- 复制spring04_2_tx_xml_anno,重命名为spring04_3_tx_anno
- 创建一个配置类SpringConfig
- 添加注解
- @Configuration
- @ComponentScan
- @PropertySource
- @Import 导入其他的配置类
- @EnableTransactionManager //开启事务注解支持
- 添加注解
- 创建一个配置类DaoConfig
- 配置数据源
- 配置JdbcTemplate
- 创建一个配置类TXConfig
- 配置事务管理器
- 删除beans.xml,修改测试类
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:db.properties")//只能使用一次,建议写在主配置类上或者对应的标签写在主配置文件中
@EnableTransactionManagement //开启事务注解支持
@Import({DaoConfig.class,TXConfig.class})
public class SpringConfig {
}
public class DaoConfig {
@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("dataSource")
public DataSource createDS(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
@Bean
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
}
public class TXConfig {
@Bean("transactionManager")
public PlatformTransactionManager createPTM(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
二 SpringMVC简介
1 回顾MVC模式
只是一种思想,将业务代码和信息展示分离.所有后台语言都要遵守这个思想
![image-20200919004815592](spring04.assets/image-20200919004815592.png)
2 springMVC
springMVC是按照MVC设计模式开发的轻量级web框架,他也是spring framework家族的一部分。
springMVC封装了servlet公共的特性,使开发者只需要关注业务的私有特性(通过一个普通的类),来提高开发效率。
web层框架中封装共有的特性在前端控制器
中完成,前端控制器
中肯定要获取request和response对象,所以前端控制器一般是servlet或者filter
- springmvc的前端控制器就是一个Servlet:DispatcherServlet
- 配置普通的javabean就可以完成请求的处理操作
扩展:
- strust2的前端控制器是一个Filter:Struts2PrepareAndExecuteFilter
学习springmvc:
- 如何封装请求参数(包括文件上传)
- 如何生成响应信息
- 拦截器:类似于web中过滤器
3 快速入门
需求
在index.jsp上有一个连接(/springmvc/hello),点击连接,在控制台打印一句话,转发到/WEB-INF/pages/success.jsp
步骤分析
- 建web模块,导依赖:servlet,jsp,spring-webmvc
- 在index.jsp上新建一个连接,在web-inf下新建pages目录,新建一个success.jsp
- 在web包下新建一个类:A_HelloController,编写一个方法:sayHello
- 给类上添加一个注解 : @Controller
- 给方法添加一个注解 : @RequestMapping(“hello”)
- 返回值为要转发的路径
- 在resources目录新建一个spring的配置文件:文件名字自定义,建议springmvc.xml
- 开启组件扫描
- 开启mvc注解支持
- 暂时不配-视图解析器
- 在web.xml中配置前端控制器:DispatcherServlet
- servlet
- 配置名字
- 通过servlet的初始化参数配置springmvc配置文件的路径
- 修改servlet的初始化时机 load-on-startup
- servlet-mapping
- 配置路径暂时为 /
- servlet
代码实现
web模块的pom.xml
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!--测试和编译环境下使用,运行时使用servlet容器的jar包-->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
</dependencies>
controller代码
@Controller //将此类的对象加入springmvc容器
public class A_HelloController {
@RequestMapping("hello")//配置处理的路径
//@RequestMapping("/hello")
public String sayHello(){
System.out.println("收到了请求");
//返回值转发的路径
//return "/WEB-INF/pages/success.jsp";
//当在springmvc.xml中配置了视图解析器之后,路径就可以下面的方式
return "success";
}
}
springmvc.xml
<!--开启组件扫描-->
<context:component-scan base-package="com.itheima.web"/>
<!--开启mvc注解支持-->
<mvc:annotation-driven/>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前缀-->
<property name="prefix" value="/WEB-INF/pages/"/>
<!--配置后缀-->
<property name="suffix" value=".jsp"/>
</bean>
web.xml
<!--前端控制器-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--springmvc配置文件的路径-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--servlet的初始化时机-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<!--处理除了jsp之外所有的请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yPmE33Hq-1622723497303)(spring04.assets/image-20200919151337918.png)]
4 快速入门执行流程
三 SpringMVC组件概述
1 springmvc框架执行流程(面试题)
项目启动的时候
- web.xml中配置了前端控制器,且把它的初始化时机修改成随着项目启动而进行初始化
- 加载并解析servlet初始化参数中配置的springmvc.xml
- 创建springmvc容器(spring容器)
- 扫描web包下的所有有@Component和衍生注解的类,创建对象,加入springmvc容器中
- 创建处理器映射器对象和处理器适配器对象,并放入springmvc容器中(mvc注解支持)
- 处理器映射对象,会把controller中含有@RequestMapping的方法找到,会形成一种映射关系
- 路径名称—类中方法名
- 路径名称—拦截器(类似于过滤器)
- 创建视图解析器对象,放入springmvc容器中
请求来的时候
- 请求先到达前端控制器,
- 前端控制器拿到请求之后会访问处理器映射器:"/hello"这个请求会执行那些拦截器和controller(处理器)
- 处理器映射器将这个请求对应的拦截器和处理器都返回:例如 com.itheima.web.ctrl.AHelloCTRL.sayHello
- 前端控制器拿到返回处理的执行链之后,找到处理适配器:“你去执行这些拦截器和处理器吧”
- 处理器适配器就去找到指定的拦截器和处理器,然后执行他们
- 处理器执行完毕之后,返回了一个值.
- 处理器适配器拿到返回值之后,将返回值包装成ModelAndView对象,然后将对象返回给前端控制器
- model中存放的域中的数据
- view中存放是跳转的(逻辑)视图,例如:success
- 前端控制器收到modelandview对象后,找到视图解析器,说:你给我看看success对应是那个(物理)视图
- 视图解析器拿到之后,将前缀和后缀拼接好,返回view对象,对应的是:/WEB-INF/pages/success.jsp
- 前端控制器拿到物理视图之后,转发或者重定向到/WEB-INF/pages/success.jsp,且把域中的数据携带过去,物理视图编译然后运行生成响应信息,然后将响应信息返回给服务器.
- 服务器拿到响应之后生成响应行、头、体,将响应返回给浏览器
2 三大组件
- 处理器映射器(mvc注解支持)
负责将url路径和方法上的路径产生映射关系,返回处理器执行链- 处理器适配器(mvc注解支持)
根据执行路径查找多个处理器中的某一个方法,执行业务逻辑,返回ModelAndView- 视图解析器(在springmvc.xml中配置)
将逻辑视图解析为view对象(真实资源文件对象)
我们需要做的事情:
- 配置前端控制器(web.xml),视图解析器,mvc注解支持
- 编写处理器(controller)和 视图(jsp)
3 最最常用注解解析
@Controller
作用在web层的类上,将这个类对应的对象创建好,且放入springmvc容器中
@RequestMapping
作用:将请求的路径和某个类中的某个方法形成映射关系
常见和常用的属性:
- value属性:配置访问路径的
- method属性:配置请求的方式 例如:get或者post等
- param属性(了解):配置请求路径中必须包含的参数信息,若不满足就会报错:400(参数错误)
这个注解既可以作用在类上也可以作用在方法上.
- 若作用在类上,这个路径就是一级路径,若方法上也有,方法上的路径就是二级路径.最终的访问路径是"/一级路径/二级路径"
- 方便分模块开发
- 例如:一级路径 /user或者/order,二级路径随便编写都不会重复的路径
<a href="${pageContext.request.contextPath}/requestMapping/test1">b_requestmapping-value属性</a><br/>
<a href="${pageContext.request.contextPath}/requestMapping/test2">b_requestmapping-method属性</a><br/>
<a href="${pageContext.request.contextPath}/requestMapping/test3">b_requestmapping-param属性-没有username报错400</a><br/>
<a href="${pageContext.request.contextPath}/requestMapping/test3?username=tom">b_requestmapping-param属性-有username参数不报错</a><br/>
@Controller
@RequestMapping("requestMapping")
public class B_RequestMappingController {
@RequestMapping("test1")
public String test1(){
System.out.println("requestMapping的路径");
return "success";
}
//method属性:只能处理那些请求方式
@RequestMapping(value="test2",method = {RequestMethod.POST,RequestMethod.GET})
public String test2(){
System.out.println("requestMapping的method属性");
return "success";
}
//param属性是用来指定请求的路径中必须包含的请求信息
@RequestMapping(value="test3",params = "username")
public String test3(){
System.out.println("requestMapping的param属性");
return "success";
}
}
四 SpringMVC的请求
1 接收请求参数
- 基本数据类型和String类型(请求参数的名称和方法变量名称保持一致)
- 对象类型(请求参数的名称和对象中属性名保持一致)
- 数组类型或者以逗号分隔字符串
- 对象中的集合类型.使用ognl表达式,将表单中的数据封装给对象中的集合类型
<a href="${pageContext.request.contextPath}/param/test1?username=tom&age=18">c_请求参数_封装简单类型</a><br/>
<form action="${pageContext.request.contextPath}/param/test2" method="post">
<fieldset>
<legend>c_请求参数_封装成javabean</legend>
银行名称:<input type="text" name="bankName"/><br/>
银行余额:<input type="text" name="money"/><br/>
<input type="submit" value="提交">
</fieldset>
</form>
<form action="${pageContext.request.contextPath}/param/test3" method="post">
<fieldset>
<legend>c_请求参数_多选封装成数组</legend>
<input type="checkbox" name="ids" value="1">id:1 name:xx<br/>
<input type="checkbox" name="ids" value="2">id:2 name:xx<br/>
<input type="checkbox" name="ids" value="3">id:3 name:xx<br/>
<input type="checkbox" name="ids" value="4">id:4 name:xx<br/>
<input type="checkbox" name="ids" value="5">id:5 name:xx<br/>
<input type="checkbox" name="ids" value="6">id:6 name:xx<br/>
<input type="submit" value="删除选中">
</fieldset>
</form>
<form action="${pageContext.request.contextPath}/param/test4" method="post">
<fieldset>
<legend>c_请求参数_多选封装成用逗号分隔的字符串</legend>
<input type="checkbox" name="ids" value="1">id:1 name:xx<br/>
<input type="checkbox" name="ids" value="2">id:2 name:xx<br/>
<input type="checkbox" name="ids" value="3">id:3 name:xx<br/>
<input type="checkbox" name="ids" value="4">id:4 name:xx<br/>
<input type="checkbox" name="ids" value="5">id:5 name:xx<br/>
<input type="checkbox" name="ids" value="6">id:6 name:xx<br/>
<input type="submit" value="删除选中">
</fieldset>
</form>
<form action="${pageContext.request.contextPath}/param/test5" method="get">
<fieldset>
<legend>c_请求参数_封装成javabean及其javabean中的集合</legend>
姓名:<input type="text" name="username"/><br/>
年龄:<input type="text" name="age"/><br/>
银行名称1:<input type="text" name="accountList[0].bankName"/><br/>
银行余额1:<input type="text" name="accountList[0].money"/><br/>
银行名称2:<input type="text" name="accountList[1].bankName"/><br/>
银行余额2:<input type="text" name="accountList[1].money"/><br/>
银行名称3:<input type="text" name="accountMap['aa'].bankName"/><br/>
银行余额3:<input type="text" name="accountMap['aa'].money"/><br/>
<input type="submit" value="提交">
</fieldset>
</form>
public class User {
private String username;
private int age;
private List<Account> accountList;
private Map<String,Account> accountMap;
//自己补全get,set和tostring
}
public class Account {
private String bankName;
private Double money;
//自己补全get,set和tostring
}
@Controller
@RequestMapping("param")
public class C_ParamController {
//封装简单类型,只需要在方法中声明请求参数即可,保证请求参数名字和方法的参数名字一致
@RequestMapping("test1")
public String test1(String username,int age){
System.out.println(username+"--"+age);
return "success";
}
//将请求参数封装javabean,只需要在方法参数中声明参数即可
@RequestMapping("test2")
public String test2(Account account){
System.out.println(account);
return "success";
}
//将多选提交过来的值封装成数组或者字符串,方法的参数名称必须和请求参数的名字一致
@RequestMapping("test3")
public String test3(int[] ids){
System.out.println(Arrays.toString(ids));
return "success";
}
@RequestMapping("test4")
public String test3(String ids){
System.out.println(ids);
return "success";
}
@RequestMapping("test5")
public String test5(User user){
System.out.println(user);
System.out.println(user.getAccountList());
System.out.println(user.getAccountMap());
return "success";
}
}
2 post请求中文过滤器
spring已经提供了一个过滤器CharacterEncodingFilter,我们只需要去web.xml中配置即可
<!--配置字符编码过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<!--指定post请求的编码-->
<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>
3 类型转换
如果我们提交的是一个字符串类型的日期,那么springMVC框架会自动转换为日期类型,要求:字符串格式必须为:yyyy/MM/dd
,如果不是此格式那么转换失败,但是对于国内用户而言习惯日期格式:yyyy-MM-dd
,这个时候就需要我们进行类型转换
方式1:在需要转换的字段上面添加注解
方式2:自定义转换器
1. 编写一个类 实现Converter接口(原来的类型,目标类型),重写转换的方法
2. 在springMVC配置文件中配置自定义转换器服务
3. 进行测试
public class MyString2DateConverter implements Converter<String,Date> {
@Override
//导入hutools依赖
public Date convert(String source) {
if (StringUtils.isNotBlank(source)) {
try {
return DateUtil.parse(source,"yyyy-MM-dd");
} catch (ParseException e) {
e.printStackTrace();
}
}
return null;
}
}