一、Spring
1. Spring的系统架构
spring无论是从广义上还是狭义上指的都是SpringFarmWork
我们要学习Spring的系统架构,那么Spring的系统架构是什么样的?【一定背下来】
2. SpringFarmwork的学习路线
3. Spring的核心概念
spring主要分为IOC和AOP
3.1 Ioc----控制反转
Ⅰ 概念引入
什么是控制反转,我们首先用一组代码来说明
假设我们现在创建了一个service层和一个dao层,然后service层需要调用dao层,那么按照原始的方法我们应该是怎么做的?
我们会创建接口,然后使用Impl类实现接口;对于service层我们需要在里面创建一个dao层的实现对象,这样才可以调用dao层,那么话不多说,上代码
1. UserDao接口
public Interface UserDao{
public void save();
}
2. UserDaoImpl
public void UserDaoImpl implements UserDao{
@Override
public void save(){
System.out.println("吃饭");
}
}
3. UserService
public Interface UserService{
public void sleep();
}
4. UserServiceImpl
public void UserServiceImpl implements UserService{
//创建一个UserDao对象并初始化
private UserDao userDao=new UserDaoImpl();
@Override
public void sleep(){
userDao.save(); //使用创建的对象调用UserDaoImpl里面的方法
System.out.println("睡觉");
}
}
上面就是简单的一个实现,也是我们平常生活中的一个实现,这个方法是可以用,那么大家可以发现它的不足在哪里吗?❓
🤔请先想一想🤔
=========================================================================
好,下面就公布答案了,我们都知道service是需要调用dao层的,那么如果我们一直使用在service里面创建对象并初始化的方式,倘若有一天我们将做好的项目发布到了网络上,会出现一个什么问题,假设现在我们需要修改一些内容,然后我们创建了UserDaoImpl2来实现,那么我们将其修改到service层的时候就需要修改源代码了!然后修改完之后我们还需要重新发布,这会造成什么现象?耗时并且浪费资源,耦合度极其的高❗
所以这个时候我们的Spring框架就为我们引入了一个新的概念-----Ioc
Ⅱ Ioc解析
1. Ioc的概念
使用对象时, 由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转
Spring技术对IoC思想进行了实现
Spring提供了一个容器,称为IoC容器,用来充当IoC思想中的外部
IoC容器负责对象的创建、初始化等-系列工作, 被创建或被管理的对象在IoC容器中统称为Bean
我们还需要在这里补充一个概念就是依赖注入
为什么要有依赖注入呢?
因为我们使用Ioc容器的时候只是简单的将我们的service层和dao层添加进去了,只是解决了耦合性问题,但是存在一个什么问题,我们没有将两者联系起来,这个时候就需要依赖注入这个概念了!
2. 在学习Ioc的时候我们将会从以下五个方面进行解析
- 管理什么? ( Service与Dao )
- 如何将被管理的对象告知IoC容器? (配置)
- 被管理的对象交给IoC容器,如何获取到IoC容器? ( 接口)
- IoC容器得到后,如何从容器中获取bean ? (接口方法)
- 使用Spring导入哪些坐标? ( pom.xml )
同样,我们也是使用代码进行一个实现解析
1)service和dao层代码
//dao层//
public void UserDaoImpl implements UserDao{
@Override
public void save(){
System.out.println("吃饭");
}
}
//service层//
public void UserServiceImpl implements UserService{
//创建一个UserDao对象,这里不初始化
private UserDao userDao;
@Override
public void sleep(){
userDao.save(); //使用创建的对象调用UserDaoImpl里面的方法
System.out.println("睡觉");
}
public void sette(UserDao userDao){
this.userDao=userDao;
}
}
2)配置文件 applicationContext.xml文件
我们需要通过这个文件告知Ioc容器被管理对象有哪些
在这里,有三种配置的方式
①构造函数
②setter方法
③p标签
这里我们使用setter的方法【在这里我只简单的例举出中间的代码,至于其他的大家前缀啥的,创建文件的时候会自动产生,我也背不下来,哈哈哈😀】
<bean id="userDao" class="com.xxx.impl.userDaoImpl" ></bean>
<bean id="userService" class="com.xxx.impl.userServiceImpl">
<property name="userDao" ref="userDao"/> <!--依赖注入-->
</bean>
最后我们来看看我们的pom文件里面应该引入哪些?
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.17.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
然后在主函数中我们怎么实现调用,我们可以使用工厂模式ApplicationContext这个类
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService= (UserService) app.getBean("userService");//注意名字一定要写对
userService.sleep();
3.2 Bean的基础配置
Ⅰ bean的别名配置
我们直到我们在使用ApplicationContext调用的时候,我们需要获取bean,我们前面使用的是获取bean的id,那么可不可以不根据id获取?
有的,我们可以通过name获取,我们可以自己设置别名,然后在传入的时候使用name指定的名字来获取
<bean id="userDao" name="userdao" class="com.xxx.impl.userDaoImpl" ></bean>
<bean id="userService" name="service;service2" class="com.xxx.impl.userServiceImpl">
<property name="userDao" ref="userDao"/> <!--依赖注入-->
</bean>
我们的name之间使用 ; 或者空格隔开,可以设置多个
Ⅱ bean的作用范围
bean存在两种作用范围,singleton和prototype两种
默认使用的是singleton单例模式
1. 那么为什么spring默认单例模式?
因为我们可以发现,像dao层、service层这种bean,我们每次调用都可以使用原来的那一个,而多次创建完全没有必要,会造成资源的浪费
2. 什么情况下适合交给spring进行管理,什么时候不适合?
3.3 bean的实例化
Ⅰ 使用构造函数
知识点补充:
在spring中出现错误的时候我们需要看最后面的一行错误一般都可以解决问题,但是如果解决不了的话就网上看一行就可以了
Ⅱ 静态工厂
Ⅲ 实例工厂与FactoryBean
1. 实例工厂
2. FactoryBean
3.4 bean的生命周期
方式一:使用配置
方式二:使用接口
bean的销毁时机
4. 依赖注入方式
依赖注入方式有两种,setter注入和构造器注入
而对于这两种注入方式,会分为两种不同的情况,引用注入和普通注入两种
4.1 setter注入
1. 引用注入
2. 普通注入
4.2 构造器注入
1. 引用注入
2. 普通注入
那么看了上面的代码会发现存在一个什么问题?
我们会发现如果我们修改Impl中的参数名字,在配置文件中就匹配不到了,也需要更改,所以他们之间存在的问题就是耦合度很高,那么怎么解决?
我们可以使用参数适配的方式
4.3 依赖注入方式的选择
5. 依赖自动装配
5.1 按照类型
按照类型匹配有什么问题?
我们匹配的时候只可以匹配唯一的,比如这里我们如果将bookDao添加一个bookDao2同时也实现BookDao,那么肯定会出错,因为找不到对应的是哪一个!
5.2 按照名称
5.3 按照构造方法
5.4 不启动自动装配
6. 集合注入
7. 数据源对象管理
我们这里主要处理两个数据源 druid 和 C3P0
首先要导入依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.10</version>
</dependency>
然后我们进行文件的配置
8. 加载properties文件
1. 我们需要添加新的命名空间,context
这样我们就可以获得一个全新的命名空间了
2. 使用context空间加载properties文件
3. 使用属性占位符${}读取properties文件中的属性
然后如果我们现在在properties文件里面添加一个username=root,然后我们修改配置文件为value=${username} 我们会获取到对应的值吗?并不会,为什么,因为默认主函数里面会有一个加载优先级大于他,因此我们怎么做嘞,如下所示,我们首先将username=root加载到一个进行properties文件中,然后就可以重新使用context加载配置文件了
<context:property-placeholder location:"classpath:*.properties" system-properties-mode="NAVER">
具体看下面
9. 容器
9.1 加载容器
方式一
使用ClassPathXmlApplicationContext("配置文件名称") 加载文件
方式二
使用FileSystemXmlApplicationContext("绝对路径下的文件") 这种方式和上面不一样的就是不能直接写applicationContext.xml文件,需要添加绝对路径
9.2 获取bean的方式
方式一
BookDao bookDao=(BookDao)ctx.getBean("bookDao");
方式二
BookDao bookDao=ctx.getBean("bookDao",BookDao.Class);
方式三
BookDao bookDao=ctx.getBean(BookDao.Class);
9.3 ApplicationContext和BeanFactory
BeanFactory是延迟加载Bean的!ApplicationContext立即加载Bean!
10. 注解开发
10.1 注解开发定义Bean
我们还是根据前面的代码来进行一步一步的解析
首先我们直到我们存在
一个接口BookDao
一个实现类BookDaoImpl
一个配置文件applicationContext.xml 配置了BookDao
还有一个运行类APP
实现步骤:
我们知道配置文件中会存在一个<bean id="bookDao" class="com.xxx.BookDaoImpl">
那么如果我们现在删除这一行,使用注解如何进行替换?
也就是说如何让其在容器中获取这个文件?
1.首先在实现类上面添加注解@Component,相当于配置了一个bean,同时我们在后面添加参数,也就相当于给了他id了
2. 然后我们就删除了这个文件了,那么这个时候加载配置文件怎么可以知道最后我们的impl类在哪里? 我们还需要添加context标签,然后进行扫描,扫描到包,这样就可以读取到impl了
【如果我们这里不添加id名字,我们在获取bean的时候就只可以使用.class的方式进行加载!】
同时我们在不同的层上有不同的注解,Dao层----@Repository Service层-----@Service 但是@Component是可以通用的!
10.2 纯注解开发
我们怎么取消使用配置文件?
我们创建一个新的类springConfig.class,用来替换applicationContext.xml文件
新的类的编辑如下所示:
@Configuration
@ComponentScan("com.wxy")
public void SpringCongfig{
}
然后我们在加载配置文件的类中就不能加载配置文件了,需要加载配置类
public void App(){
public static void main(String[] args){
ApplicationContext app=new AnnotationConfigApplicationContext(SpringConfig.class);
//下面的就是一样了
BookDao bookDao=app.getBean("bookDao");
}
}
@Configuration用于设置当前类为配置类
@ComponentScan用于设置扫描路径,这个注解只可以添加一次,多个注解使用数组格式
10.3 注解开发bean的作用范围和生命周期
使用@Scope("singleton") //表示单例
生命周期除了使用前面说明过的接口InitializingBean之外,因为我们这里是使用注解开发,所以使用
10.4 依赖注入---自动装配
Ⅰ 引用类型
我们知道,在service层调用dao层的时候,需要进行依赖注入,如果没有注入就没有办法通过service层调用dao层,那么使用注解开发的时候是怎么实现注入的?
使用@Autowired进行注入,注入之后就不用再添加setter方法了,它使用的方式是反射里面的暴力注入的方式!!!
其实按照之前的配置的方式,我们进行注入的话先要找到容器,然后在容器中进行依赖注入,实现方式有两种 ----- setter方法注入以及构造器注入!那么在这里其实是将这两个入口也直接取消了的,直接使用注解@Autorwired注入就可以了!
这个注解就相当于自动装配中间的使用类型进行装配,那么如果我们有两个实现类的话,我们应该怎么做?
方式一:
我们可以在不同的实现类上定义一个bean
我们在service层进行注入的时候我们会定义一个private BookDao bookDao,这个时候哪个实现类的bean的名字为bookDao的话就会调用谁
方式二:
我们可以添加一个新的注解@Qualifier("bean的名字") //这里bean的名字对应的是谁就是调用的谁!
注意@Quailfier注解必须依赖于@Autorwired这个注解
Ⅱ 普通类型
我们怎么给普通类型的参数进行注入?
使用注解@Value("") 为参数注入对应的值
那么我们为什么不直接在这里将数据赋给普通类型?直接指定为name="wxy"?
public class BookDaoImpl implements BookDao{
@Value("wxy")
private String name;
public void save(){
System.out.println("hahahah"+name);
}
}
因为这里的name可能来自外部的properties文件,所以不能直接赋值,那么怎么将外部的properties文件中的参数加载进来?
1. 我们需要在替换applicationContext.xml文件的类中添加注解@PropertySource("jdbc.properties"),告诉加载哪里的properties文件
2. 将@Value的参数替换为@Value("${name}"),就可以直接加载properties文件中的name参数了
❗注意❗
我们知道在配置文件中加载properties文件的时候如果有多个properties文件,我们可以使用*.properties进行代替,这样就不用添加多个了
但是在使用注解开发的时候是不允许使用*.properties这样的形式的!我们只可以使用集合的方式进行!
10.5 第三方bean的管理
10.6 注解开发实现为第三方bean注入资源
11. Spring整合Mybatis
11.1 导入对应的依赖
1. 操作数据库一定要导入的包有spring-jdbc、mybatis-spring
2. 然后我们需要创建SpringConfig类来代替applicationContext.xml文件
文件内容如下所示:
@Configuration
@ComponentScan("com.wxy") //扫描文件
@PropertySource("jdbc.properties") //加载jdbc配置文件
@Import({JdbcConfig.class,MybatisConfig.class}) //我们需要扫描到这些文件,或者还可以在对应的文件上添加@Configuration
public class SpringConfig{
}
3. 然后我们需要创建JDBCConfig.class文件来加载
4. 最后我们都知道mybatis存在一个核心配置文件 SqlMapConfig.xml 那么我们就需要使用一个文件来代替他----MybatisConfig.class文件
然后我们来仔细看看SqlMapConfig.xml文件是什么样的?
然后我们替换之后为
12. Spring整合JUnit
12.1 导入坐标
1. junit包
2. spring-test包
12.2 创建对应的test类
1. 创建AccountServiceTest.class类;实现如下所示
//指定运行器
@RunWith(SpringJunit4ClassRunner.class)
//这里只用注意这里指定Spring的配置的地方,方式和前面不一样,这里是 classes=SpringConfig.class 方式
@ContextConfiguration(classes = SpringConfig.class)//这里已经启用了spring和Junit的整合
public class AccountServiceTest{
@Autowired;
private AccountService accountService;
@Test
public void testFindById(){
System.out.println(accountService.findById(1));
}
}
13. Spring AOP
13.1 AOP的核心概念与作用
AOP 面向切面编程,一种编程范式,指导开发者如何组织程序结构
OOP 面向对象编程
AOP的作用:在不惊动原始设计的基础上为其进行功能增强
Spring理念:无入侵式编程
面向切面编程的实际思想笼统上来讲就是所有需要某一个功能的类不用每个类都写一次这个功能,只用定义一个通用的类,然后在其他类中使用面向编程的方式进行引用就可以使得其他的类全部获得这个功能!
一些简单的概念:
1. 连接点:程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等;在spring aop中理解为方法的执行
2. 切入点:匹配连接点的式子;在spring aop中一个切入点可以只描述一个具体方法,也可以匹配多个方法;
- 一个具体方法: com. itheima. dao包下的BookDao接口中的无形参无返回值的save方法
- 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
3. 通知:在切入点处执行的操作,也就是共性功能
4. 通知类:定义通知的类
5. 切面:描述通知和切入点的关系
【注意:连接点范围大于切入点,连接点范围一定大于切入点!】
13.2 AOP的入门案例
在接口执行前输出当前系统时间
开发模式:XML 或者 注解
思路:
1. 导入坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
2. 制作连接点方法(原始操作,Dao接口和实现类)
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println(System.currentTimeMillis());
System.out.println("book dao save ...");
}
public void update(){
System.out.println("book dao update ...");
}
}
3. 制作共性功能(通知与通知类)
4. 定义切入点
package com.itheima.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
//设置切入点,要求配置在方法上方
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
//设置在切入点pt()的前面运行当前操作(前置通知)
// @Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
5. 绑定切入点与通知关系(切面)
//设置在切入点pt()的前面运行当前操作(前置通知)
// @Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
13.3 AOP的工作流程与核心概念
1. spring容器启动
2. 读取所有切面配置的切入点
3. 初始化Bean,判定bean对应的类中的方法是不是匹配到任意的切入点
- 匹配失败,创建对象
- 匹配成功,创建原始对象的代理对象
4. 获取bean执行方法
- 获取bean,调用方法并执行,完成操作
- 获取bean是代理对象的时候,根据代理对象的运行模式运行原始方式与增强的内容,完成操作
核心概念
目标对象:原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终的工作的
代理:目标对象没有办法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
13.4 AOP切入点表达式
切入点:要进行增强的方法
切入点表达式:要增强的方法的描述方式
1. 描述方式一:执行com.wxy.dao包下的BookDao接口中的无参数的update方法
2. 描述方式二:执行com.wxy.dao.impl包下的BookDaoImpl类中无参数的update()方法
标准格式为:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名 (参数)异常名)
- 动作关键字:描述切入点的行为动作
- 访问修饰符:public private 等,可以省略
- 返回值
- 包名
- 类/接口名
- 方法名
- 参数
- 异常名:方法定义中抛出指定异常,可以省略
书写技巧
❓留下一个小疑问❓
为什么通常不适用异常作为匹配规则
13.5 AOP的通知类型
AOP描述了抽取的共性功能,根据共性功能的抽取位置的不同,最终运行代码时要将其加入到合理的位置
AOP通知共分为5中类型:
- 前置通知 ---- @Before(" ")
- 后置通知 ---- @After(" ") --- 原始方法执行完之后
- 环绕通知(重点)---- @Around(" ") 在内部必须添加一个表示对原始操作的调用
//@Around:环绕通知,在原始方法运行的前后执行
// @Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Object ret = pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
在环绕通知这里要注意一个点就是:
如果原理的切入点(执行方法)有返回值的话,我们在进行环绕的时候也一定要给他一个返回值!
就比如说这里,我们使用的Public int select();
我们使用AOP进行操作的时候就需要给他一个返回值才可以,执行方法的返回值类型为Object
- 返回后通知 ---- 只有在方法正常结束的时候才会执行
- 抛出异常后通知
13.6 案例
需求:任意的业务接口执行都可以显示其执行效率
分析:①业务功能:求得执行效率
②通知类型选择前后均可以增强的类型-----环绕通知
AOP切面代码如下所示
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class ProjectAdvice {
//匹配业务层的所有方法
@Pointcut("execution(* com.itheima.service.*Service.*(..))")
private void servicePt(){}
//设置环绕通知,在原始操作的运行前后记录执行时间
@Around("ProjectAdvice.servicePt()")
public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
//获取执行的签名对象
Signature signature = pjp.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
}
}
13.7 AOP通知获取数据
1. 获取参数
ProceedJointPoint属于JoinPoint接口
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
//JoinPoint:用于描述切入点的对象,必须配置成通知方法中的第一个参数,可用于获取原始方法调用的参数
@Before("pt()")
public void before(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice ..." );
}
// @After("pt()")
public void after(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("after advice ...");
}
//ProceedingJoinPoint:专用于环绕通知,是JoinPoint子类,可以实现对原始方法的调用
// @Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
Object ret = null;
try {
ret = pjp.proceed(args);
} catch (Throwable t) {
t.printStackTrace();
}
return ret;
}
//设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(JoinPoint jp,String ret) {
System.out.println("afterReturning advice ..."+ret);
}
//设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ..."+t);
}
2. 获取返回值
3. 获取异常
13.8 AOP最终案例 ---- 百度网盘密码数据兼容处理
AOP切面的方法如下所示
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class DataAdvice {
@Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))")
private void servicePt(){}
@Around("DataAdvice.servicePt()")
public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
for (int i = 0; i < args.length; i++) {
//判断参数是不是字符串
if(args[i].getClass().equals(String.class)){
args[i] = args[i].toString().trim();
}
}
Object ret = pjp.proceed(args);
return ret;
}
}
14. Spring事务
银行转账案例
14.1 Spring事务的简介
事务的作用:在数据层保障一系列数据库操作同成功同失败
Spring事务的作用:在数据层或者业务层保障一系列数据库操作同成功同失败
分析:
①数据层提供基本操作,指定账户减钱,指定账户加钱
②业务层提供转账操作,调用减钱和加钱操作
③提供两个账户和操作金额执行转账操作
④基于Spring整合Mybatis环境搭建上述操作
如果我们在执行的时候不开启事务的话,中途遇到异常可能就会出现转账上的错误,那么就需要开启事务
//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
//配置当前接口方法具有事务
@Transactional
public void transfer(String out,String in ,Double money) ;
14.2 Spring事务角色
1. 事务管理员
发起事务方,在Spring中通常指代业务层开启事务的方法
2. 事务协调员
加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
❓小疑问❓
datasource来自哪里? datasource指的是数据源,也就是来自我们配置的外部数据源【druid、c3p0等】
14.3 Spring事务属性
事务遇到下面两种异常会进行事务的回滚 ----- Error、运行时异常;其他的异常都不会进行事务的回滚【比如IOException】
解决办法:
//rollback:设置当前事务参与回滚的异常,默认非运行时异常不参与回滚
@Transactional(rollbackFor = IOException.class)
二、SpringMVC
springMVC技术和servlet技术类似,都是用来做web层开发的
1. 请求与响应
1.1 SpringMVC 入门案例
1. 导入对应的依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
2. 编写对应的响应类
package com.itheima.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
//定义表现层控制器bean
@Controller
public class UserController {
//设置映射路径为/save,即外部访问路径
@RequestMapping("/save")
//设置当前操作返回结果为指定json数据(本质上是一个字符串信息)
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'info':'springmvc'}";
}
//设置映射路径为/delete,即外部访问路径
@RequestMapping("/delete")
@ResponseBody
public String delete(){
System.out.println("user save ...");
return "{'info':'springmvc'}";
}
}
3. 创建SpringMVC的配置文件
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
//springmvc配置类,本质上还是一个spring配置类
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}
4. 创建Tomcat容器启动的配置,让Tomcat容器知道配置文件的存在
package com.itheima.config;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
//web容器配置类
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc配置类,产生springmvc容器(本质还是spring容器)
protected WebApplicationContext createServletApplicationContext() {
//初始化WebApplicationContext对象
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
//加载指定配置类
ctx.register(SpringMvcConfig.class);
return ctx;
}
//设置由springmvc控制器处理的请求映射路径
protected String[] getServletMappings() {
return new String[]{"/"};
}
//加载spring配置类
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
执行过程
1.2 Bean加载控制
因为功能不同,如何避免Spring错误的加载到SpringMVC的bean-------加载Spring控制的bean的时候排除掉SpringMVC控制的bean
@ComponentScan(value="com.itheima",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class
)
)
关于tomcat的中加载Spring和SpringMVC的简化
package com.itheima.config;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
//web配置类简化开发,仅设置配置类类名即可
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
/*
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
*/
1.3 PostMan简介
可以做post等请求
1.4 请求与响应
1. 请求的映射路径
我们在请求的时候要添加所在的包名,比如我们在book包下,就要添加/book/前缀作为访问路径
当我们一个类有多个方法的时候,我们可以在类的上面添加统一路径
2. 使用postman进行get和post请求的使用
①get请求
②post请求
乱码处理【为web容器添加过滤器,使得其可以处理中文】
//乱码处理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
1.5 五种类型传递参数
1. 普通参数传递
当请求参数和参数名称不一致的时候,我们使用@RequestParam
//普通参数:请求参数名与形参名不同时,使用@RequestParam注解关联请求参数名称与形参名称之间的关系
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(@RequestParam("name") String userName , int age){
System.out.println("普通参数传递 userName ==> "+userName);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param different name'}";
}
2. POJO传递
①简单的pojo
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}
在请求的时候会自动匹配简单类
②POJO中又封装了POJO实体
//嵌套POJO参数:嵌套属性按照层次结构设定名称即可完成参数传递
@RequestMapping("/pojoContainPojoParam")
@ResponseBody
public String pojoContainPojoParam(User user){
System.out.println("pojo嵌套pojo参数传递 user ==> "+user);
return "{'module':'pojo contain pojo param'}";
}
③数组元素
//数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] likes){
System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes));
return "{'module':'array param'}";
}
④集合元素
//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
System.out.println("集合参数传递 likes ==> "+ likes);
return "{'module':'list param'}";
}
注意这里一定要加@RequestParam这个注解,否则会显示匹配不到List,最后就会出错
1.6 请求参数---传递json数据
我们想要处理json数据首先要导入对应的坐标
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
然后我们要在postman里面做如下处理
然后,最最重要的就是,我们进行数据传输的时候,怎么可以将json数据转换为对象?我们需要添加如下注解@EnableWebMvc
①集合参数
//集合参数:json格式
//1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
//2.使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> likes){
System.out.println("list common(json)参数传递 list ==> "+likes);
return "{'module':'list common for json param'}";
}
②pojo参数
//POJO参数:json格式
//1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
//2.使用@RequestBody注解将外部传递的json数据映射到形参的实体类对象中,要求属性名称一一对应
@RequestMapping("/pojoParamForJson")
@ResponseBody
public String pojoParamForJson(@RequestBody User user){
System.out.println("pojo(json)参数传递 user ==> "+user);
return "{'module':'pojo for json param'}";
}
访问json的时候需要使用@RequestBody这个注解才可以!!
③多个pojo
/集合参数:json格式
//1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
//2.使用@RequestBody注解将外部传递的json数组数据映射到形参的保存实体类对象的集合对象中,要求属性名称一一对应
@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'}";
}
@RequestParam和@RequestBody的区别
1.7 日期参数的传递
日期类型参数传递存在一个问题 ----- 格式多样化
//日期参数
//使用@DateTimeFormat注解设置日期类型数据格式,默认格式yyyy/MM/dd
@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'}";
}
类型转换器的使用
1.8 响应
1. 响应页面
//响应页面/跳转页面
//返回值为String类型,设置返回值为页面名称,即可实现页面跳转
@RequestMapping("/toJumpPage")
public String toJumpPage(){
System.out.println("跳转页面");
return "page.jsp";
}
2. 响应数据
//响应文本数据
//返回值为String类型,设置返回值为任意字符串信息,即可实现返回指定字符串信息,需要依赖@ResponseBody注解
@RequestMapping("/toText")
@ResponseBody
public String toText(){
System.out.println("返回纯文本数据");
return "response text";
}
这也就告诉我们,我们前面使用的响应数据的方式响应的都是字符串,那么要怎么才可以响应json数据??也就是将我们的普通数据转换为json数据? ---- 如下所示
3. 响应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;
}
然后这里是怎么响应的?? 我们使用了一个全新的响应接口
2. REST风格
REST ---- 表现形式状态转换
是可以打破这个规则的,并不一定完全要按照这个风格,你可以打破rest风格
//设置当前请求方法为POST,表示REST风格中的添加操作
@RequestMapping(value = "/users",method = RequestMethod.POST)
@ResponseBody
public String save(){
System.out.println("user save...");
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'}";
}
RESTFUL快速开发
public class BookController2 {
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
@PutMapping
public String update(@RequestBody Book book){
System.out.println("book update..."+book);
return "{'module':'book update'}";
}
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println("book getById..."+id);
return "{'module':'book getById'}";
}
@GetMapping
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}
}
2.1 案例!!---重要
1. 后台接口测试代码
@RestController
@RequestMapping("/books")
public class BookController {
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save ==> "+ book);
return "{'module':'book save success'}";
}
@GetMapping
public List<Book> getAll(){
System.out.println("book getAll is running ...");
List<Book> bookList = new ArrayList<Book>();
Book book1 = new Book();
book1.setType("计算机");
book1.setName("SpringMVC入门教程");
book1.setDescription("小试牛刀");
bookList.add(book1);
Book book2 = new Book();
book2.setType("计算机");
book2.setName("SpringMVC实战教程");
book2.setDescription("一代宗师");
bookList.add(book2);
Book book3 = new Book();
book3.setType("计算机丛书");
book3.setName("SpringMVC实战教程进阶");
book3.setDescription("一代宗师呕心创作");
bookList.add(book3);
return bookList;
}
}
2. 因为我们前面设置了所有的请求都要经过springMVC,但是不能这样,那么我们应该怎么做?
我们可以设置一个类,用来排除一些静态资源使得不被MVC拦截
package com.itheima.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
//设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//当访问/pages/????时候,从/pages目录下查找内容
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}
3. 查询数据
//主页列表查询
getAll() {
axios.get("/books").then((res)=>{
this.dataList = res.data;
});
},
4. 添加数据
//添加
saveBook () {
axios.post("/books",this.formData).then((res)=>{
});
},
3. SSM整合
整合流程如下所示:
4. 拦截器
4.1 拦截器的概念
拦截器( Interceptor )是一种动态拦截方法调用的机制
1. 作用
在指定的方法调用前后执行预先设定后的的代码
阻止原始方法的执行
2. 拦截器与过滤器的区别
归属不同: Filter属于Servlet技术, Interceptor属于SpringMVC技术
拦截内容不同: Filter对所有访问进行增强, Interceptor仅针对SpringMVC的访问进行增强
三、Mybatis
在这里我们只简单的进行Mybatis的学习,重心放在Spring整合Mybatis上面
四、Springboot
1. SpringBoot 简介
1.1 SpringBoot项目入门案例
③创建控制类
④运行程序
1.2 Spring和SpringBoot的对比
1.3 SpringBoot项目快速启动
如果这个时候我们前端需要通过我们的后端进行调试的话,那怎么办呢?
我们需要将其打包发送给前端!
① 进行打包,使用MAVEN对应的package指令
② 使用java -jar …… 执行对应的jar包
1.4 SpringBoot简介
2. SpringBoot 基本配置
2.1 基础配置
1. 修改端口
①添加配置文件application.properties 并在其中修改端口【使用语句server.port=80】
②使用配置文件application.yml
③使用配置文件application.yaml
【如果我们在使用yml和yaml文件的时候,没有显示提示我们应该怎么办?】
加载顺序为 properties>yml>yaml
2.2 yaml格式
1. 一种数据序列化的格式
优点:容易阅读、容易与脚本语言交互、以数据为中心、重数据轻格式
2. 语法规则
① 大小写敏感
② 属性层级关系使用多行描述,每行结尾使用冒号结束
③ 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab健)
④ 属性前面添加空格(属性名和属性值之间使用冒号和空格进行分离)
⑤ #表示注释
2.3 yaml数据读取方式
方式一:直接注入元素
方式二:定义一个类用来加载配置文件
当我们在做自定义对象封装的时候出现警告的解决方案
添加依赖
2.4 多环境开发配置
yaml和yml
#设置启用的环境
spring:
profiles:
active: dev
---
#开发环境
spring:
profiles: dev
server:
port: 80
---
#生产环境
spring:
profiles: pro
server:
port: 81
---
#测试环境
spring:
profiles: test
server:
port: 82
propertie文件
然后我们可以进行打包发送给前端
2.5 多环境开发兼容问题
maven为主,boot为辅
1. 我们的maven环境配置如下
2. boot中进行加载
3. pom中配置的属性只可以在pom文件中使用,要想在配置文件中使用就需要进行干预
我们需要在pom文件中添加插件
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<encoding>utf-8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
</configuration>
</plugin>
2.6 配置文件分类
【注意】在springboot 2.4.5 中不存在,但是在2.4.6中就开始存在,就是我们的1级配置文件中的config文件夹下面必须有一个子文件夹才可以!!
3. SpringBoot 整合其他技术
3.1 整合Junit
3.2 整合SSM
1. 整合mybatis
2. 整合之前SSM的案例
① 代码拷贝并修改
②配置文件
③ 静态资源