👉 在线笔记:https://du1in9.github.io/ssm.github.io/
第1章 - Spring
1.1 Spring 入门
1.1.1 课程介绍
-
为什么要学?
-
Spring 技术是 JavaEE 开发必备技能,企业开发技术选型命中率 > 90%
-
专业角度
- 简化开发,降低企业级开发的复杂性
- 框架整合,高效整合其他技术,提高企业级应用开发与运行效率
-
-
学什么?
-
简化开发
IOC、AOP(事务处理)
-
-
框架整合
MyBatis、MyBatis-plus、Struts、Struts2、Hibernate
-
怎么学?
-
学习 Spring 框架设计思想
-
学习基础操作,思考操作与思想间的联系
-
学习案例,熟练应用操作的同时,体会思想
-
1.1.2 相关概念
① 初识 Spring
- 官网:https://spring.io
- Spring 发展到今天已经形成了一种开发的生态圈, Spring 提供了若干个项目, 每个项目用于完成特定的功能
② spring 系统架构
介绍完 Spring 的体系结构后,从中我们可以得出对于 Spring 的学习主要包含四部分内容,分别是:
- 核心容器(Core Container):Spring 的 IOC / DI
- 数据集成(Data Integration):IOC / DI 的具体应用,整合 Mybatis
- 面向切面编程(AOP),Aspects:Spring 的 AOP
- 事务(Transactions):AOP 的具体应用,事务管理
③ Spring 核心概念
-
代码书写现状:耦合度高
-
解决方案:使用对象时,在程序中不要主动使用 new 产生对象,转换为由外部提供对象
-
IOC(Inversion of Control)控制反转
- 使用对象时,由主动 new 产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部
-
Spring 技术对 IOC 思想进行了实现
- Spring 提供了一个容器,称为 IOC 容器,用来充当 IOC 思想中的 "外部"
- IOC 容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在 IOC 容器中统称为 Bean
- DI(Dependency Injection)依赖注入
- 在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
1.1.3 入门案例
① IOC 入门案例
-
添加 Spring 的依赖 jar 包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency>
-
添加案例中需要的类
public interface BookDao { public void save(); } public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } } public interface BookService { public void save(); } public class BookServiceImpl implements BookService { private BookDao bookDao = new BookDaoImpl(); public void save() { System.out.println("book service save ..."); bookDao.save(); } }
-
添加 spring 配置文件 applicationContext.xml,在文件中完成 bean 的配置
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>
-
获取 IOC 容器,并从容器中获取对象进行方法调用
public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookService bookService = (BookService) ctx.getBean("bookService"); bookService.save(); } }
-
IOC 入门案例已经完成,但是在 BookServiceImpl 的类中依然存在 BookDaoImpl 对象的 new 操作,
它们之间的耦合度还是比较高,这块该如何解决,就需要用到下面的 DI: 依赖注入。
② DI 入门案例
-
去除代码中的 new,并为属性提供 setter 方法
public class BookServiceImpl implements BookService { private BookDao bookdao; public void save() { System.out.println("book service save ..."); bookDao.save(); } public void setBookDao(BookDao bookdao) { this.bookdao = bookdao; } }
-
修改配置完成注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> <property name="bookdao" ref="bookDao"/> </bean>
1.2 IOC & DI
1.2.1 IOC 相关内容
① bean 基础配置
-
bean 基础配置
-
id:使用容器可以通过 id 值获取对应的 bean,在一个容器中 id 值唯一
-
class:bean 的类型,即配置的 bean 的全路径类名
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>
-
-
bean 别名配置
定义 bean 的别名,可定义多个,使用逗号(,)分号(;)空格( )分隔
<bean id="bookService service" class="com.itheima.service.impl.BookServiceImpl"/> <bean id="bookDao,dao" name="dao" class="com.itheima.dao.impl.BookDaoImpl"/>
注意:获取 bean 无论是通过 id 还是 name,如果无法获取到,将抛出异常
NoSuchBeanDefinitionException
-
bean 作用范围配置
- singleton:单例(默认)
- prototype:非单例
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl scope="prototype"/>
哪些bean对象适合交给容器进行管理?
- 表现层对象、业务层对象、数据层对象、工具对象
哪些bean对象不适合交给容器进行管理?
- 封装实例的域对象,因为会引发线程安全问题,所以不适合
② bean 实例化
-
构造方法(常用)
public class BookDaoImpl implements BookDao { public BookDaoImpl() { System.out.println("book dao constructor is running ...."); } public void save() { System.out.println("book dao save ..."); } }
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
注意:无参构造方法如果不存在,将抛出异常
BeancreationException
-
静态工厂(了解)
public class OrderDaoFactory { public static OrderDao getOrderDao(){ return new OrderDaoImpl(); } }
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
-
实例工厂(了解)
public class UserDaoFactory { public UserDao getUserDao(){ return new UserDaoImpl(); } }
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/> <bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
- FactoryBean(实用)
public class UserDaoFactoryBean implements FactoryBean<UserDao> { // 代替实例工厂中创建对象的方法 public UserDao getObject() throws Exception { return new UserDaoImpl(); } public Class<?> getObjectType() { return UserDao.class; } }
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>
③ bean 的生命周期
-
bean 生命周期控制
- 配置:init-method、destroy-method
public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } public void init(){ System.out.println("init..."); } public void destory(){ System.out.println("destory..."); } }
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
- 接口:InitializingBean、DisposableBean(了解)
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean { public void save() { System.out.println("book service save ..."); } public void destroy() throws Exception { System.out.println("service destroy"); } public void afterPropertiesSet() throws Exception { System.out.println("service init"); } }
- 关闭容器:close、registerShutdownHook
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); ctx.registerShutdownHook(); // 1. 注册钩子关闭容器 ctx.close(); // 2. 手工关闭容器
-
bean 生命周期
- 初始化容器
- 创建对象 (内存分配)
- 执行构造方法
- 执行属性注入 (set 操作)
- 执行 bean 初始化方法
- 使用 bean
- 执行业务操作
- 关闭 / 销毁容器
- 执行 bean 销毁方法
- 初始化容器
1.2.2 DI 相关内容
① setter 注入(推荐)
-
简单类型
public class BookDaoImpl implements BookDao { private int connectionNum; public void setConnectionNum(int connectionNum) { this.connectionNum = connectionNum; } }
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <property name="connectionNum" value="100"/> </bean>
-
引用类型
public class BookServiceImpl implements BookService{ private BookDao bookDao2; public void setBookDao(BookDao bookDao) { this.bookDao2 = bookDao; } }
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> <property name="bookDao2" ref="bookDao"/> </bean>
② 构造器注入(了解)
-
简单类型
public class BookDaoImpl implements BookDao { private int connectionNum; public BookDaoImpl(int connectionNum) { this.connectionNum = connectionNum; } }
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <constructor-arg name="connectionNum" value="100"/> </bean>
-
引用类型
public class BookServiceImpl implements BookService{ private BookDao bookDao; public BookServiceImpl(BookDao bookDao2) { this.bookDao = bookDao2; } }
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> <constructor-arg name="bookDao2" ref="bookDao"/> </bean>
③ 自动配置
-
IoC 容器根据 bean 所依赖的资源在容器中自动查找并注入到 bean 中的过程称为自动装配、
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>
-
自动装配方式:按类型(byType)、按名称(byName)、按构造方法、不启用自动装配
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时(byType)必须保障容器中相同类型的 bean 唯一(推荐)
- 使用按名称装配时(byName)必须保障容器中具有指定名称的 bean,因变量名与配置耦合(不推荐)
- 自动装配优先级低于 setter 注入与构造器注入,同时出现时自动装配配置失效
④ 集合注入
public class BookDaoImpl implements BookDao {
private int[] array;
public void setArray(int[] array) {
this.array = array;
}
}
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
</bean>
1.2.3 管理第三方 bean
① 案例
-
导入 druid 的依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency>
-
配置第三方 bean
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/spring_db"/> <property name="username" value="root"/> <property name="password" value="1234"/> </bean>
② 加载 properties 文件
-
开启 context 命名空间
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> </beans>
-
加载 properties 配置文件
<context:property-placeholder location="jdbc.properties"/> <property name="username" value="${jdbc.username}"/>
1.3 核心容器
1.3.1 容器
-
创建容器
// 1. 类路径加载 XML 配置文件 (常用) ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 文件系统加载 XML 配置文件 ApplicationContext ctx = new FileSystemXmlApplicationContext("applicationContext.xml"); // 加载多个 XML 配置文件 ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xml");
-
获取 bean
// 1. 使用 bean 名称获取 BookDao bookDao = (BookDao) ctx.getBean("bookDao"); // 2. 使用 bean 名称获取并指定类型 BookDao bookDao = ctx.getBean("bookDao",BookDao.class); // 3. 使用 bean 类型获取 BookDao bookDao = ctx.getBean(BookDao.class);
-
容器类层次结构
从图中可以看出,容器类也是从无到有根据需要一层层叠加上来的,重点理解下这种设计思想
-
BeanFactory
Resource resources = new ClassPathResource("applicationContext.xml"); BeanFactory bf = new XmlBeanFactory(resources); BookDao bookDao = bf.getBean(BookDao.class);
1.3.2 总结
-
bean 相关
- 依赖注入相关
-
容器相关
- BeanFactory 是 IoC 容器的顶层接口,初始化 BeanFactory 对象时,加载的 bean 延迟加载
- ApplicationContext 接口是 Spring 容器的核心接口,初始化时 bean 立即加载
- ApplicationContext 接口提供基础的 bean 操作相关方法,通过其他接口扩展其功能
- ApplicationContext 接口常用初始化类
- ClassPathXmlApplicationContext (常用)
- FileSystemXmlApplicationContext
1.4 注解开发
1.4.1 定义 bean
-
使用 @Component 定义 bean
@Component("bookDao") public class BookDaoImpl implements BookDao { }
-
核心配置文件中通过组件扫描加载 bean
<context:component-scan base-package="com.itheima"/>
-
Spring 提供 @Component 注解的三个衍生注解
- @Controller:用于表现层 bean 定义
- @service:用于业务层 bean 定义
- @Repository:用于数据层 bean 定义
1.4.2 注解开发
-
纯注解开发模式
-
Spring 3.0 开启了纯注解开发模式,使用 Java 类替代配置文件,开启了 Spring 快速开发赛道
-
Java 类替换 Spring 核心配置文件:
@Configuration // 用于设定当前类为配置类 @ComponentScan("com.itheima") // 用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式 public class SpringConfig { }
// 读取 Spring 核心配置文件初始化容器对象,切换为读取 Java 配置类初始化容器对象 ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
-
-
bean 作用范围
@Scope("prototype") // 用于设置 bean 的作用范围 public class BookDaoImpl implements BookDao { }
-
bean 生命周期
@Repository public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } @PostConstruct // 在构造方法之后执行 public void init() { System.out.println("init ..."); } @PreDestroy // 在销毁方法之前执行 public void destroy() { System.out.println("destroy ..."); } }
1.4.3 依赖注入
-
使用 @Autowired 实现引用类型注入(按类型)
@Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; } // 注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供 setter 方法 // 注意:自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法
- 使用 @Qualifier 注解开启指定名称装配 bean
@Service public class BookServiceImpl implements BookService { @Autowired @Qualifier("bookDao2") private BookDao bookDao; } // 注意:@Qualifier 注解无法单独使用,必须配合 @Autowired 注解使用
-
使用 @Value 实现简单类型注入
@Repository public class BookDaoImpl implements BookDao { @Value("itheima") private String name; }
- 使用 @PropertySource 注解加载 properties 文件
@PropertySource("jdbc.properties") public class SpringConfig { } // 注意:路径仅支持单一文件配置,多文件请使用数组格式配置,不允许使用通配符
@Repository public class BookDaoImpl implements BookDao { @Value("${name}") private String name; }
1.4.4 管理第三方 bean
-
第三方 bean 管理
@Configuration @Import(JdbcConfig.class) public class SpringConfig { }
public class JdbcConfig { @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setUrl("jdbc:mysql://localhost:3306/spring_db"); ds.setUsername("root"); ds.setPassword("1234"); return ds; } }
-
第三方 bean 依赖注入
- 简单类型:成员变量
public class JdbcConfig { @Value("com.mysql.jdbc.Driver") private String driver; @Value("jdbc:mysql://localhost:3306/spring_db") private String url; @Value("root") private String userName; @Value("1234") private String password; @Bean public DataSource dataSource(){...} }
- 引用类型:方法形参
public class JdbcConfig { @Bean public DataSource dataSource(BookDao bookDao){...} } // 只需要为 bean 定义方法设置形参即可,容器会根据类型自动装配对象
1.5 Spring 整合
1.5.1 整合 mybatis
- MyBatis 程序核心对象分析:由图可知,真正需要交给 Spring 管理的是 SqlSessionFactory
- 整合 Mybatis,就是要将 Mybatis 用到的内容交给 Spring 管理,分析配置文件:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
@Configuration
@ComponentScan("com.itheima")
@PropertySource("jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
public class JdbcConfig {...}
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.itheima.domain"); // 1. 初始化类型别名
ssfb.setDataSource(dataSource); // 2. 初始化 dataSource
return ssfb;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.dao"); // 3. 初始化映射配置
return msc;
}
}
1.5.2 整合 junit
- 使用 spring 整合 junit 专用的类加载器
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
}
1.6 AOP
1.5.1 AOP 简介
-
AOP 概念与作用
AOP (Aspect Oriented Programming) 面向切面编程,一种编程范式,指导开发者如何组织程序结构
- OOP (Object Oriented Programming) 面向对象编程
作用:在不惊动原始设计的基础上为其进行功能增强
-
AOP 核心概念
- 连接点 (JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
- 在 SpringAOP 中,理解为方法的执行
- 切入点 (Pointcut):匹配连接点的式子
- 在 SpringAOP 中,一个切入点可以描述一个具体方法,也可也匹配多个方法
- 通知 (Advice):在切入点处执行的操作,也就是共性功能
- 在 SpringAOP 中,功能最终以方法的形式呈现
- 通知类:定义通知的类
- 切面 (Aspect):描述通知与切入点的对应关系
- 连接点 (JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
1.5.2 AOP 入门案例
- 案例设定:在接口执行前输出当前系统时间
-
添加依赖(pom.xml)
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
-
定义接口与实现类(dao 包)
public interface BookDao { public void save(); public void update(); } @Repository public class BookDaoImpl implements BookDao { public void save() { System.out.println(System.currentTimeMillis()); } public void update(){ System.out.println("book dao update ..."); } }
-
定义通知类、通知、切入点和切面,将通知类配给容器并标识其为切面类(aop 包)
@Component @Aspect public class MyAdvice { // 1. 切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑 @Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt(){} // 2. 绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置 @Before("pt()") public void method(){ System.out.println(System.currentTimeMillis()); } }
-
开启注解格式 AOP 功能(config 包)
@Configuration @ComponentScan("com.itheima") @EnableAspectJAutoProxy public class SpringConfig { }
1.5.3 AOP 工作流程
-
Spring 容器启动,读取所有切面配置中的切入点
-
初始化 bean,判定 bean 对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
-
获取 bean 执行方法
-
获取 bean,调用方法并执行,完成操作
-
获取的 bean 是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
-
- 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
- 代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
1.5.4 AOP 配置管理
① AOP 切入点表达式
-
语法格式
// 方式一: 执行 com.itheima.dao 包下的 BookDao 接口中的无参数 update 方法 execution(void com.itheima.dao.BookDao.update()) // 方式二: 执行 com.itheima.dao.impl 包下的 BookDaoImpl 类中的无参数 update 方法 execution(void com.itheima.dao.impl.BookDaoImpl.update())
标准格式:
动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
- execution:动作关键字,描述切入点的行为动作,例如 execution 表示执行到指定切入点
- public:访问修饰符,还可以是 public,private 等,可以省略
- 异常名:方法定义中抛出指定异常,可以省略
-
通配符
- * :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
- .. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
- + :专用于匹配子类类型(了解)
// 匹配 com.itheima 包下的任意包中的 UserService 类或接口中所有 find 开头的带有一个参数的方法 execution(public * com.itheima.*.UserService.find*(*)) // 匹配 com 包下的任意包中的 UserService 类或接口中所有名称为 findById 的方法 execution(public User com..UserService.findById(..)) // 这个使用率较低,描述子类的,咱们做 JavaEE 开发,继承机会就一次 execution(* *..*Service+.*(..))
-
书写技巧
- 所有代码按照标准规范开发,否则以下技巧全部失效
- 描述切入点通常描述接口,而不描述实现类,如果描述到实现类,就出现紧耦合了
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用 * 通配快速描述
- 包名书写尽量不使用 .. 匹配,效率过低,常用*做单个包描述匹配,或精准匹配
- 接口名/类名书写名称与模块相关的采用 * 匹配,例如 UserService 书写成 *Service,绑定业务层接口名
- 方法名书写以动词进行精准匹配,名词采用 * 匹配,例如 getById 书写成 getBy*,selectAll 书写成 selectAll
② AOP 通知类型
-
AOP 通知类型
-
AOP 通知描述了抽取的共性功能,根据抽取的位置不同,最终运行代码时要将其加入到合理的位置
-
AOP 通知共分为 5 种类型:前置通知、后置通知、环绕通知、返回后通知、抛出异常后通知
@Before("pt()") // 1. 前置通知 public void before() { System.out.println("around before advice ..."); } @After("pt()") // 2. 后置通知 public void after() { System.out.println("around after advice ..."); } @Around("pt()") // 3. 环绕通知 (重点) 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; } @AfterReturning("pt()") // 4. 返回后通知 public void afterReturning() { System.out.println("afterReturning advice ..."); } @AfterThrowing("pt()") // 5. 抛出异常后通知 public void afterThrowing() { System.out.println("afterThrowing advice ..."); }
-
-
@Around 注意事项
- 环绕通知必须依赖形参 ProceedingJoinPoint 才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
- 通知中如果未使用 ProceedingJoinPoint 对原始方法进行调用将跳过原始方法的执行
- 对原始方法的调用可以不接收返回值,通知方法设置成 void 即可,如果接收返回值,最好设定为 Object 类型
- 原始方法的返回值如果是 void 类型,通知方法的返回值类型可以设置成 void,也可以设置成 Object 类型
- 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理 Throwable 异常
-
案例:测量业务层接口执行效率
需求:任意业务层接口执行均可显示其万次执行效率(执行时长)
@Component @Aspect public class ProjectAdvice { @Pointcut("execution(* com.itheima.service.*Service.*(..))") private void servicePt(){} @Around("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"); } }
③ AOP 通知获取数据
-
获取参数
- JoinPoint:适用于前置、后置、返回后、抛出异常后通知
@Before("pt()") public void before(JoinPoint jp){ Object[] args = jp.getArgs(); System.out.println("before advice ..."+Arrays.toString(args)); }
- ProceedingJoinPoint:适用于环绕通知
@Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); System.out.println("around advice ..."+Arrays.toString(args)); Object ret = pjp.proceed(); return ret; }
-
获取返回值:返回后通知、环绕通知(了解)
@AfterReturning(value = "pt()",returning = "ret") public void afterReturning(Object ret) { System.out.println("afterReturning advice ..."+ret); } @Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable{ Object[] args = pjp.getArgs(); Object ret = pjp.proceed(args); System.out.println("around advice ..."+ret); return ret; }
-
获取异常:抛出异常后通知、环绕通知(了解)
-
案例:网盘密码数据兼容处理
需求:对百度网盘分享链接输入密码时尾部多输入的空格做兼容处理
@Component @Aspect public class DataAdvice { @Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))") private void servicePt(){} @Around("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; } }
1.5.5 AOP 事务管理
① Spring 事务简介
-
事务作用:在数据层保障一系列的数据库操作同成功同失败
-
Spring 事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
-
案例:模拟银行账户间转账业务,需求:实现任意两个账户间转账操作
-
在业务层接口上添加 Spring 事务管理
public interface AccountService { @Transactional public void transfer(String out,String in ,Double money) ; } // Spring 注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合 // Spring 注解式事务也可以添加到接口上表示当前接口所有方法开启事务
-
设置事务管理器
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } // 事务管理器要根据实现技术进行选择; MyBatis 框架使用的是 JDBC 事务
-
开启注解式事务驱动
@EnableTransactionManagement public class SpringConfig { }
② Spring 事务角色
-
事务管理员:发起事务方,在 Spring 中通常指代业务层开启事务的方法
@Transactional public void transfer(String out,String in ,Double money) { accountDao.outMoney(out,money); accountDao.inMoney(in,money); }
-
事务协调员:加入事务方,在 Spring 中通常指代数据层方法,也可以是业务层方法
@Update("update tbl_account set money = money + #{money} where name = #{name}") void inMoney(@Param("name") String name, @Param("money") Double money); @Update("update tbl_account set money = money - #{money} where name = #{name}") void outMoney(@Param("name") String name, @Param("money") Double money);
③ Spring 事务属性
属性 | 作用 |
---|---|
readOnly | 设置是否为只读事务 |
timeout | 设置事务超时时间 |
rollbackFor | 设置事务回滚异常 |
propagation | 设置事务传播行为 |
-
案例:转账业务追加日志
-
需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕
// 1. 添加 AccountService 接口与实现类, 设置记录日志
public interface AccountService {
@Transactional(rollbackFor = IOException.class)
public void transfer(String out,String in ,Double money);
}
public void transfer(String out,String in ,Double money) {
try{
accountDao.outMoney(out,money);
if(true) {throw new IOException();}
accountDao.inMoney(in,money);
}finally {
logService.log(out,in,money);
}
}
// 注意: 并不是所有异常都会回滚, 可以使用 rollbackFor 属性来设置出现 IOException 回滚
// 2. 添加 LogService 接口与实现类, 设置事务的传播行为
public interface LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
void log(String out, String in, Double money);
}
public void log(String out,String in,Double money ) {
logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
}
第2章 - SpringMVC
2.1 SpringMVC 概述
- SpringMVC 技术与 Servlet 技术功能等同,均属于 web 层开发技术
-
SpringMVC 是一种基于 Java 实现 MVC 模型的轻量级 Web 框架
-
优点
- 使用简单,开发便捷(相比于 servlet)
- 灵活性强
2.2 SpringMVC 入门案例
2.2.1 入门案例
-
导入 SpringMVC 与 Servlet 坐标
<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>
-
创建 SpringMVC 控制器类
@Controller public class UserController { @RequestMapping("/save") // 4. @ResponseBody public String save(){ System.out.println("user save ..."); return "{'info':'springmvc'}"; } }
-
初始化 SpringMVC 环境,设定 SpringMVC 加载对应的 bean
@Configuration @ComponentScan("com.itheima.controller") // 3. public class SpringMvcConfig { }
-
初始化 Servlet 容器,加载 SpringMVC 环境,并设置 SpringMVC 技术处理的请求
// AbstractDispatcherServletInitializer 类是 SpringMVC 提供的快速初始化 web3.0 容器的抽象类 public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer { // 1. protected WebApplicationContext createServletApplicationContext() { // 2. AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(SpringMvcConfig.class); return ctx; } protected String[] getServletMappings() { // 5. return new String[]{"/"}; } // 如果需要加载非 SpringMVC 对应的 bean, 使用当前方法 protected WebApplicationContext createRootApplicationContext() { return null; } }
- SpringMVC 入门程序开发总结(1+N)
- 一次性工作
- 创建工程,设置服务器,加载工程
- 导入坐标
- 创建 web 容器启动类,加载 SpringMVC 配置,并设置 SpringMVC 请求拦截路径
- SpringMVC 核心配置类(设置配置类,扫描 controller 包,加载 Controller 控制器 bean)
- 多次工作
- 定义处理请求的控制器类
- 定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回 json 数据(@ResponseBody)
- 一次性工作
2.2.2 工作流程解析
-
服务器启动,执行
ServletContainersInitConfig
类,初始化 web 容器 -
执行
createServletApplicationContext
方法,创建了 WebApplicationContext 对象 -
加载
SpringMvcConfig
配置类,执行@ComponentScan
加载对应的 bean -
加载
UserController
类,每个@RequestMapping
的名称对应一个具体的方法 -
执行
getServletMappings
方法,设定 SpringMVC 拦截请求的路径规则
-
简化形式
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; } protected String[] getServletMappings() { return new String[]{"/"}; } }
2.2.3 bean 加载控制
- SpringMVC 相关的 bean:表现层(controller)
- Spring 控制的 bean:业务层(service)、功能层(dao)
-
方式一:设定扫描范围为 com.itheima,排除掉 controller 包内的 bean
@Configuration @ComponentScan( value="com.itheima", excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class) ) public class SpringConfig {}
@Configuration public class SpringMvcConfig {}
-
方式二:设定扫描范围为精准范围,例如 service 包、dao 包等(常用)
@Configuration @ComponentScan({"com.itheima.service","com.itheima.dao"}) public class SpringConfig {}
@Configuration @ComponentScan("com.itheima.controller") public class SpringMvcConfig {}
-
方式三:不区分 Spring 与 SpringMVC 的环境,加载到同一个环境中(了解)
2.2.4 PostMan 工具
-
Postman 是一款功能强大的网页调试与发送网页 HTTP 请求的 Chrome 插件
-
作用:常用于进行接口测试
-
特征:简单、实用、美观、大方
2.3 请求与响应
2.3.1 映射路径
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
public void save(){
System.out.println("user save ...");
}
@RequestMapping("/delete")
public void delete(){
System.out.println("user delete ...");
}
}
2.3.2 请求参数
@RequestMapping("/commonParam")
public void commonParam(String name,int age){
System.out.println("普通参数传递 name ==> "+name);
System.out.println("普通参数传递 age ==> "+age);
}
解决 Post 请求中文乱码问题
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Filter[] getServletFilters() { // 配置过滤器
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
2.3.3 参数类型
-
普通参数:url 地址传参,地址参数名与形参变量名相同,定义形参即可接收参数
@RequestMapping("/commonParam") public void commonParam(String name,int age){ System.out.println("普通参数传递 name ==> "+name); System.out.println("普通参数传递 age ==> "+age); }
- 普通参数:请求参数名与形参变量名不同,使用 @RequestParam 绑定参数关系
@RequestMapping("/commonParam") public void commonParam(@RequestParam("name") String userName,int age){ System.out.println("普通参数传递 userName ==> "+userName); System.out.println("普通参数传递 age ==> "+age); }
-
POJO 参数:请求参数名与形参对象属性名相同,定义 POJO 类型形参即可接收参数
@RequestMapping("/pojoParam") public void pojoParam(User user){ System.out.println("pojo参数传递 user ==> "+user); }
- 嵌套 POJO 参数:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套 POJO 属性参数(同上)
public class User { private String name; private int age; private Address address; }
-
数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型形参即可接收参数
@RequestMapping("/arrayParam") public void arrayParam(String[] likes){ System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes)); }
- 集合参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam 绑定参数关系
@RequestMapping("/listParam") public void listParam(@RequestParam List<String> likes){ System.out.println("集合参数传递 likes ==> "+ likes); }
-
JSON 数据
- 添加 json 数据转换相关坐标
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency>
- 开启自动转换 json 数据的支持
@EnableWebMvc public class SpringMvcConfig {} // @EnablewebMvc 注解功能强大, 整合了多个功能, 比如 POJO、日期类型与 JSON 的转换
- 设置接收 json 数据
@RequestMapping("/listParamForJson") public void listParamForJson(@RequestBody List<String> likes){} // 1. 集合参数 @RequestMapping("/pojoParamForJson") public void pojoParamForJson(@RequestBody User user){} // 2. POJO 参数 @RequestMapping("/listPojoParamForJson") public void listPojoParamForJson(@RequestBody List<User> list){} // 3. POJO 集合参数
-
日期类型:接收形参时,根据不同的日期格式设置不同的接收方式
@RequestMapping("/dataParam") 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); }
2.3.4 响应数据
-
响应页面 / 跳转页面
@RequestMapping("/toJumpPage") public String toJumpPage(){ return "page.jsp"; }
-
响应文本数据
@RequestMapping("/toText") @ResponseBody public String toText(){ return "response text"; }
-
响应 json 数据(POJO 转 json)
@RequestMapping("/toJsonPOJO") @ResponseBody public User toJsonPOJO(){ User user = new User(); user.setName("itcast"); user.setAge(15); return user; }
2.4 REST 风格
2.4.1 REST 简介
-
REST(Representational State Transfer),表现形式状态转换
-
优点
- 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
- 书写简化
-
按照 REST 风格访问资源时使用行为动作区分对资源进行了何种操作
- 根据 REST 风格对资源进行访问称为 RESTful
http://1ocalhost/users 查询全部用户信息 GET(查询) http://1ocalhost/users/1 查询指定用户信息 GET(查询) http://localhost/users 添加用户信息 POST(新增/保存) http://1ocalhost/users 修改用户信息 PUT(修改/更新) http://1ocalhost/users/1 删除用户信息 DELETE(删除)
-
注意事项
-
上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
-
描述模块的名称通常使用复数,也就是加 s 的格式描述,表示此类资源,而非单个资源
-
2.4.2 RESTful 入门
@Controller
public class UserController {
@RequestMapping(value = "/users", method = RequestMethod.GET)
public void getAll(){}
@RequestMapping(value = "/users", method = RequestMethod.POST)
public void save(){}
@RequestMapping(value = "/users", method = RequestMethod.PUT)
public void update(@RequestBody User user){}
@RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
public void getById(@PathVariable Integer id){}
@RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE)
public void delete(@PathVariable Integer id){}
}
@RequestParam、@RequestBody、@PathVariable
-
区别
-
@RequestParam 用于接收 url 地址传参或表单传参
-
@RequestBody 用于接收 json 数据
-
@Pathvariable 用于接收路径参数,使用参数名称描述路径参数
-
-
应用
- 后期开发中,发送请求参数超过1个时,以 json 格式为主,@RequestBody 应用较广
- 如果发送非 json 格式数据,选用 @RequestParam 接收请求参数
- 采用 RESTful,当参数数量较少时,例如1个,用 @Pathvariable 接收请求路径变量,通常用于传递 id 值
2.4.3 RESTful 快速开发
@RestController:设置控制类为 RESTful 风格,等同于 @Controller 与 @ResponseBody 两个注解的组合功能
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping
public void getAll(){}
@PostMapping
public void save(@RequestBody Book book){}
@PutMapping
public void update(@RequestBody Book book){}
@GetMapping("/{id}")
public void getById(@PathVariable Integer id){}
@DeleteMapping("/{id}")
public void delete(@PathVariable Integer id){}
}
2.4.4 RESTful 案例
需求:基于 RESTful 页面数据交互
-
config 包
@Configuration @ComponentScan({"com.itheima.controller","com.itheima.config"}) @EnableWebMvc public class SpringMvcConfig { }
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getRootConfigClasses() { return new Class[0]; } protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } protected String[] getServletMappings() { return new String[]{"/"}; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); return new Filter[]{filter}; } }
@Configuration public class SpringMvcSupport extends WebMvcConfigurationSupport { @Override // 2. 设置对静态资源的访问放行 protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/pages/**").addResourceLocations("/pages/"); } }
-
controller 包
// 1. 制作 SpringMVC 控制器,并通过 PostMan 测试接口功能 @RestController @RequestMapping("/books") public class BookController { @PostMapping public String save(@RequestBody Book book){ return "{'module':'book save success'}"; } @GetMapping public List<Book> getAll(){ List<Book> bookList = new ArrayList<Book>(); Book book1 = new Book(); book1.setType("计算机"); book1.setName("SpringMVC 入门教程"); bookList.add(book1); Book book2 = new Book(); book2.setType("计算机"); book2.setName("SpringMVC 实战教程"); bookList.add(book2); return bookList; } }
-
webapp\pages\books.html
<script> // 3. 前端页面通过异步提交访问后台控制器 var vue = new Vue({ methods: { saveBook () { axios.post("/books",this.formData).then((res)=>{}); }, getAll() { axios.get("/books").then((res)=>{this.dataList = res.data;}); } } }) </script>
2.5 拦截器
2.5.1 拦截器概念
拦截器(Interceptor)是一种动态拦截方法调用的机制,在 SpringMVC 中动态拦截控制器方法的执行
拦截器作用
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
拦截器与过滤器区别
- 归属不同:Fiiter 属于 Servlet 技术,Interceptor 属于 SpringMvC 技术
- 拦截内容不同:Filter 对所有访问进行增强,Interceptor 仅针对 SpringMVC 的访问进行增强
2.5.2 入门案例
-
声明拦截器的 bean,并实现 HandlerInterceptor 接口
@Component // 记得扫描加载 bean: "com.itheima.controller" public class ProjectInterceptor implements HandlerInterceptor { @Override public boolean preHandle(...) throws Exception { System.out.println("preHandle..."+contentType); return true; } @Override public void postHandle(...) throws Exception { System.out.println("postHandle..."); } @Override public void afterCompletion(...) throws Exception { System.out.println("afterCompletion..."); } }
-
定义配置类,添加拦截器并设定拦截的访问路径,路径可以通过可变参数设置多个
@Configuration // 记得扫描加载配置: "com.itheima.config" public class SpringMvcSupport extends WebMvcConfigurationSupport { @Autowired private ProjectInterceptor projectInterceptor; @Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*"); } }
2.5.3 拦截器参数
-
前置处理
public boolean preHandle(HttpServletRequest request, // 1. 请求对象 HttpServletResponse response, // 2. 响应对象 Object handler) throws Exception { // 3. 被调用的处理器对象 return true; // 返回值为 false, 被拦截的处理器将不执行 }
-
后置处理
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // modelAndview: 如果处理器执行完成具有返回结果, 可以读取到对应数据与页面信息, 并进行调整 }
-
完成后处理
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // ex: 如果处理器执行过程中出现异常对象, 可以针对异常情况进行单独处理 }
2.5.4 拦截器链配置
多拦截器执行顺序
- 当配置多个拦截器时,形成拦截器链
- 拦截器链的运行顺序参照拦截器添加顺序为准
- 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
- 当拦截器运行中断,仅运行配置在前面的拦截器的 aftercompletion 操作
第3章 - Maven 高级
3.1 分模块开发
- 意义:将原始模块按照功能拆分成若干个子模块,方便模块间的相互调用,接口共享。
-
创建 Maven 模块(maven_03_pojo、maven_04_dao)
<!-- maven_02_ssm/pom.xml --> <dependency> <groupId>com.itheima</groupId> <artifactId>maven_03_pojo</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.itheima</groupId> <artifactId>maven_04_dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
<!-- maven_04_dao/pom.xml --> <dependency> <groupId>com.itheima</groupId> <artifactId>maven_03_pojo</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
书写模块代码(Book.java、BookDao.java)
现实中,需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕,然后进行拆分
-
通过 maven 指令安装模块到本地仓库(install 指令)
现实中,团队内部开发,需要发布模块功能到团队内部可共享的仓库中(私服)
3.2 依赖管理
-
依赖传递
-
依赖具有传递性
- 直接依赖:在当前项目中通过依赖配置建立的依赖关系
- 间接依赖:被资源的资源如果依赖其他资源,当前项目间接依赖其他资源
-
依赖传递冲突问题
- 路径优先:当依赖中出现相同的资源时,层级越深,优先级越低,层级越浅,优先级越高
- 声明优先:当资源在相同层级被依赖时,配置顺序靠前的覆盖配置顺序靠后的
- 特殊优先:当同级配置了相同资源的不同版本,后配置的覆盖先配置的
-
-
可选依赖、排除依赖
- 可选依赖:指对外隐藏当前所依赖的资源(不透明)
<dependency> <groupId>com.itheima</groupId> <artifactId>maven_03_pojo</artifactId> <version>1.0-SNAPSHOT</version> <optional>true</optional> </dependency>
- 排除依赖:指主动断开依赖的资源,被排除的资源无需指定版本(不需要)
<dependency> <groupId>com.itheima</groupId> <artifactId>maven_04_dao</artifactId> <version>1.0-SNAPSHOT</version> <exclusions> <exclusion> <groupId>com.itheima</groupId> <artifactId>maven_03_pojo</artifactId> </exclusion> </exclusions> </dependency>
3.3 聚合和继承
3.3.1 聚合
<!-- 1. 创建 Maven 模块,设置打包类型为 pom -->
<groupId>com.itheima</groupId>
<artifactId>maven_01_parent</artifactId>
<version>1.0-RELEASE</version>
<packaging>pom</packaging>
<!-- 注: 每个 maven 工程默认打包方式为 jar, web 工程打包方式为 war -->
<!-- 2. 设置当前聚合工程所包含的子模块名称 -->
<modules>
<module>../maven_02_ssm</module>
<module>../maven_03_pojo</module>
<module>../maven_04_dao</module>
</modules>
<!-- 注: 聚合工程中所包含的模块在进行构建时会根据依赖关系设置顺序,与配置书写位置无关 -->
3.3.2 继承
-
概念:继承描述的是两个工程间的关系,与 java 中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承
注意:继承是针对项目之间的配置关系,而依赖传递是针对项目与库 / 框架之间的依赖关系
-
作用:简化配置、减少版本冲突
<!-- ssm_parent/pom.xml -->
<!-- 1. 配置依赖关系(简化配置)-->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
<!-- 2. 配置子工程中可选的依赖关系(减少版本冲突)-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- ssm_crm/pom.xml -->
<!-- ssm_goods/pom.xml -->
<!-- 1. 在子工程中配置当前工程所继承的父工程 -->
<parent>
<groupId>com.itheima</groupId>
<artifactId>ssm_parent</artifactId>
<version>1.0-RELEASE</version>
<relativePath>../ssm_parent/pom.xml</relativePath>
</parent>
<!-- 2. 在子工程中配置使用父工程中可选依赖的坐标 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
</dependencies>
3.3.3 区别
- 作用
- 聚合用于快速构建项目
- 继承用于快速配置
- 相同点
- 聚合与继承的 pom.xml 文件打包方式均为 pom,可以将两种关系制作到同一个 pom 文件中
- 聚合与继承均属于设计型模块,并无实际的模块内容
- 不同点
- 聚合是在当前模块中配置关系,聚合可以感知到参与聚合的模块有哪些
- 继承是在子摄块中配置关系,交想块无法感知哪些子模块继承了自己
3.4 属性
- 引用属性
<!-- 1. 定义属性 -->
<properties>
<spring.version>5.2.10.RELEASE</spring.version>
</properties>
<!-- 2. 引用属性 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
- 加载属性
<!-- 1. 定义属性 -->
<properties>
<jdbc.driver>com.mysql.jdbc.Driver</jdbc.driver>
<jdbc.url>jdbc:mysql://127.0.0.1:3306/ssm_db</jdbc.url>
<jdbc.username>root</jdbc.username>
<jdbc.password>1234</jdbc.password>
</properties>
# 2. 加载属性
jdbc.driver=${jdbc.driver}
jdbc.url=${jdbc.url}
jdbc.username=${jdbc.username}
jdbc.password=${jdbc.password}
-
版本管理
-
SNAPSHOT(快照版本)
-
项目开发过程中临时输出的版本
-
快照版本会随着开发的进展不断更新
-
-
-
RELEASE(发布版本)
- 项目开发到进入阶段里程碑后,向团队外部发布较为稳定的版本
- 这种版本所对应的构件文件是稳定的,即便进行功能的后续开发,也不会改变当前发布版本内容
3.5 多环境开发
- 多环境配置:maven 提供配置多种环境的设定,帮助开发者使用过程中快速切换环境
-
定义多环境
<profiles> <profile> <id>env_pro</id> <properties> <jdbc.url>jdbc:mysql://61.41.134.129:3306/ssm_db</jdbc.url> </properties> </profile> <profile> <id>env_dep</id> <properties> <jdbc.url>jdbc:mysql://127.0.0.1:3306/ssm_db</jdbc.url> </properties> </profile> <profile> <id>env_test</id> <properties> <jdbc.url>jdbc:mysql://65.13.40.251:3306/ssm_db</jdbc.url> </properties> </profile> </profiles>
-
使用多环境
mvn install -P env_dep
-
跳过测试:在执行 install 指令的时候,Maven 都会按照顺序从上往下依次执行,每次都会执行 test
mvn instal1 -D skipTests
3.6 私服
3.6.1 简介与安装
-
私服是一台独立的服务器,用于解决团队内部的资源共享与资源同步问题
Nexus:Sonatype 公司的一款 maven 私服产品
-
启动服务器(命令行启动)
nexus.exe / run nexus
-
访问服务器(默认端口:8081)
http://1ocalhost:8081
-
修改基础配置信息
etc 目录中
nexus-defauit.properties
文件保存有基础配置信息,例如默认访问端口 -
修改服务器配置信息
bin 目录中
nexus.vmoptions
文件保存有服务器启动对应的配置信息,例如默认占用内存空间
3.6.2 私服仓库分类
仓库类别 | 英文名称 | 功能 | 关联操作 |
---|---|---|---|
宿主仓库 | hosted | 保存自主研发+第三方资源 | 上传 |
代理仓库 | proxy | 代理连接中央仓库下载 | 下载 |
仓库组 | group | 为仓库编组简化下载操作 | 下载 |
3.6.3 私服上传下载
-
私服上配置仓库
-
创建 itheima-snapshot 仓库
-
创建 itheima-release 仓库
-
-
配置本地 Maven 对私服的访问权限(settings.xml)
<server> <id>itheima-snapshot</id> <username>admin</username> <password>admin</password> </server> <server> <id>itheima-release</id> <username>admin</username> <password>admin</password> </server>
配置私服的访问路径(settings.xml)
<mirror> <!--仓库组 ID--> <id>maven-public</id> <!--所有内容都从私服获取--> <mirrorOf>*</mirrorOf> <!--仓库组的访问路径--> <url>http://localhost:8081/repository/maven-public/</url> </mirror>
-
配置当前工程保存在私服中的具体位置(pom.xml)
<distributionManagement> <repository> <url>http://localhost:8081/repository/itheima-release/</url> </repository> <snapshotRepository> <url>http://localhost:8081/repository/itheima-snapshot/</url> </snapshotRepository> </distributionManagement>
发布命令
mvn deploy
第4章 - SpringBoot
4.1 SpringBoot 简介
4.1.1 SpringBoot 入门
-
入门案例
-
快速开发
- 第一步:创建 SpringBoot 新模块,配置基础信息,并选择 web 技术集
-
- 第二步:开发 Controller 控制器类
@RestController
@RequestMapping("/books")
public class BookController {
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
System.out.println("id ==> "+id);
return "hello , spring boot!";
}
}
- 第三步:运行自动生成的 Application 启动类,并进行 Postman 测试
- 快速启动
- 执行 maven 指令打包项目:
mvn package
- 执行 cmd 指令启动项目:
java -jar springboot.jar
- 执行 maven 指令打包项目:
-
Spring 与 SpringBoot 对比
类 / 配置文件 Spring SpringBoot pom.xml 坐标 手工添加 勾选添加 web3.0 配置类 手工制作 无 Spring / SpringMVC 配置类 手工制作 无 Controller 控制器类 手工制作 手工制作 最简 SpringBoot 程序所包含的基础文件:
- pom.xml
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> </parent> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
- Application 启动类
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
-
官网创建方式
-
基于 Idea 的 Spring Initializr 快速构建 SpringBoot 工程时需要联网
-
4.1.2 SpringBoot 简介
-
起步依赖(pom.xml)
-
parent
-
所有 SpringBoot 项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到减少依赖冲突的目的
-
spring-boot-starter-parent(2.5.0)与 spring-boot-starter-parent(2.4.6)共计 57 处坐标版本不同
-
-
starter
- SpringBoot 中常见项目名称,定义了当前项目使用的所有项目坐标,以达到减少依赖配置的目的
- 也可以使用 maven 依赖管理,变更起步依赖项:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> <!-- 使用任意坐标时, 仅书写GAV中的G和A, V由SpringBoot提供 --> </dependency>
-
-
程序启动(Application 引导类)
- SpringBoot 在创建项目时,采用 jar 的打包方式
- SpringBoot 的引导类是项目的入口,运行 main 方法就可以启动项目
4.2 配置文件
4.2.1 配置文件格式
-
SpringBoot 提供了多种属性配置方式
-
application.properties
server.port=80
-
application.yml(主流)
server: port: 81
-
application.yaml
server: port: 82
-
-
自动提示功能
4.2.2 yaml 数据读取
-
yaml 简介
YAML(YAML Ain‘t Markup Language),一种数据序列化
优点:容易阅读、容易与脚本语言交互、以数据为核心,重数据轻格式
语法:数据前面要加空格与冒号隔开
-
yaml 数据读取
@Value("${server.port}") // 方式1: @value(直接读取) private Integer port; @Autowired // 方式2: Environment(封装后读取) private Environment environment; @Autowired // 方式3: 实体类封装属性(封装后读取) private Enterprise enterprise; System.out.println(port); System.out.println(environment.getProperty("server.port")); System.out.println(enterprise);
@Component @ConfigurationProperties(prefix = "enterprise") public class Enterprise { private String name; private Integer age; private String tel; private String[] subject; }
4.2.3 多环境开发
-
多环境开发配置
- yaml 版
spring: profiles: active: dev --- spring: profiles: dev server: port: 80 --- spring: profiles: pro server: port: 81 --- spring: profiles: test server: port: 82
- properties 版
# application.properties spring.profiles.active=dev # application-dev.properties server.port=80 # application-pro.properties server.port=81 # application-test.properties server.port=82
-
命令行启动:参数设置
java -jar springboot.jar --spring·profiles.active=test java -jar springboot.jar --server.port=88 java -jar springboot.jar --server.port=88 --spring·profiles.active=test
-
多环境兼容问题(Maven 与 SpringBoot)
- Maven 中设置多环境属性
<profiles> <profile> <id>dev</id> <properties> <profile.active>dev</profile.active> </properties> </profile> <profile> <id>pro</id> <properties> <profile.active>pro</profile.active> </properties> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <id>test</id> <properties> <profile.active>test</profile.active> </properties> </profile> </profiles>
- SpringBoot 中引用 Maven 属性
spring: profiles: active: ${profile.active} ... # 此时 Maven 打包会失败,因为 package 生成了对应的包,其中类参与编译,但是配置文件并没有编译,而是复制到包中 # 解决思路: 对于源码中非 java 类的操作要求加载 Maven 对应的属性,解析 ${} 占位符
- 对资源文件开启对默认占位符的解析
<build> <plugins> <plugin> <artifactId>maven-resources-plugin</artifactId> <configuration> <encoding>UTF-8</encoding> <useDefaultDelimiters>true</useDefaultDelimiters> </configuration> </plugin> </plugins> </build>
4.2.4 配置文件分类
- SpringBoot 中 4 级配置文件
- 1级:file:
config/application.yml
- 2级:file:
application.yml
- 3级:classpath:
config/application.yml
- 4级:classpath:
application.yml
- 1级:file:
- 作用:
- 1、2 级留做系统打包后设置通用属性
- 3、4 级用于系统开发阶段设置通用属性
4.3 SpringBoot 整合
4.3.1 整合 junit
-
测试类与启动类同包 / 子包下
@SpringBootTest class ApplicationTests { @Autowired private BookService bookService; @Test public void save() { bookService.save(); } }
-
测试类与启动类不同包下
@SpringBootTest(classes = Application.class)
4.3.2 整合 mybatis
-
创建 SpringBoot 新模块,配置基础信息,并选择 MyBatis、MySQL 技术集
-
在 application.yml 设置数据源参数
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db username: root password: 1234
-
定义 dao 数据层接口与映射配置
@Mapper public interface BookDao { @Select("select * from tbl_book where id = #{id}") public Book getById(Integer id); }
第5章 - MyBatisPlus
5.1 MyBatisPlus 入门
5.1.1 入门案例
-
创建 SpringBoot 新模块,选择 MySQL 起步依赖,手动添加 MyBatisPlus 依赖:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency>
-
在 application.yml 配置数据源、端口等
-
并制作实体类与表结构(类名与表名对应,属性名与字段名对应)
-
定义数据接口(继承 BaseMapper<User>)
@Mapper public interface UserDao extends BaseMapper<User> { }
-
测试类中注入 dao 接口,测试功能
5.1.2 简介
- MyBatisPlus(简称 MP)是基于 MyBatis 框架基础上开发的增强型工具,旨在简化开发、提高效率
- 官网:https://mp.baomidou.com/(苞米豆拼音,国人开发)
- MyBatisPlus 特性
- 无侵入:只做增强不做改变,不会对现有工程产生影响
- 强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表 CRUD 操作
- 支持 Lambda:编写查询条件无需担心字段写错
- 支持主键自动生成
- 内置分页插件
5.2 标准数据层开发
5.2.1 CRUD 制作
-
增加 (Create)
User user = new User(); user.setName("黑马程序员"); user.setPassword("itheima"); userDao.insert(user);
-
读取 (Read)
User user = userDao.selectById(1L); System.out.println(user); List<User> userList = userDao.selectList(null);
-
更新 (Update)
User user = new User(); user.setId(2L); user.setPassword("tom"); userDao.updateById(user);
-
删除 (Delete)
userDao.deleteById(3L);
5.2.2 分页功能制作
-
设置分页拦截器作为 Spring 管理的 bean
@Configuration public class MpConfig { @Bean public MybatisPlusInterceptor mpInterceptor(){ MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return mpInterceptor; } }
-
执行分页查询
IPage page = new Page(2,3); userDao.selectPage(page,null); System.out.println("当前页码值:"+page.getCurrent()); System.out.println("每页显示数:"+page.getSize()); System.out.println("一共多少页:"+page.getPages()); System.out.println("一共多少条数据:"+page.getTotal()); System.out.println("数据:"+page.getRecords());
-
开启日志
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
5.3 DQL 编程控制
5.3.1 条件查询
-
两种格式
// 1. 常规格式: e.g. less equal 18 or greater equal 65 QueryWrapper<User> qw = new QueryWrapper<User>(); qw.le("age",18).or.ge("age",65); List<User>userList = userDao.selectList(qw); // 2. lambda 格式(推荐): e.g. less than 65 and greater than 18 LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); lqw.lt(User::getAge,65).gt(User::getAge,18); List<User>userList = userDao.selectList(lqw);
-
null 判定
@Data public class UserQuery extends User { private Integer age2; }
UserQuery uq = new UserQuery(); // 先判定第一个参数是否为 true,如果为 true 连接当前条件 lqw.gt(null != uq.getAge(), User::getAge, uq.getAge()); lqw.lt(null != uq.getAge2(), User::getAge, uq.getAge2());
5.3.2 查询投影
-
查询结果包含模型类中部分属性
lqw.select(User::getName, User::getPassword); List<User> userList = userDao.selectList(lqw);
-
查询结果包含模型类中未定义的属性
qw.select("gender","count(*) as nums").groupBy("gender"); List<Map<String, Object>> maps = userDao.selectMaps(qw);
5.3.3 条件设置
-
用户登录(eq 匹配)
lqw.eq(User::getName,"Jerry").eq(User::getPassword,"jerry"); User loginUser = userDao.selectOne(lqw);
-
购物设定价格区间(le、ge、lt、gt、between 匹配)
lqw.between(User::getAge, 10, 30); List<User> userList = userDao.selectList(lqw);
-
查信息,搜索新闻(like 匹配)
lqw.likeLeft(User::getName, "J"); List<User> userList = userDao.selectList(lqw);
-
统计报表(分组查询聚合函数)
qw.select("gender","count(*) as nums").groupBy("gender"); List<Map<String, Object>> maps = userDao.selectMaps(qw);
5.3.4 映射兼容性
@Data
// 解决1: 表名与编码类名不同
@TableName("tbl_user")
public class User {
private Long id;
private String name;
// 解决2: 表字段与编码属性不同
// 解决3: 默认查询开放了过多的字段权限
@TableField(value = "pwd", select = false)
private String password;
private Integer age;
// 解决4: 编码属性在表内不存在
@TableField(exist = false)
private Integer online;
}
5.4 DML 编程控制
5.4.1 id 生成策略
-
id 生成策略
-
AUTO
(0):使用数据库 id 自增策略控制id生成 -
NONE
(1):不设置 id 生成策略 -
INPUT
(2):用户手工输入 id -
ASSIGN_ID
(3):雪花算法生成 id(可兼容数值与字符串) -
ASSIGN_UUID
(4):以 UUID 生成算法作为 id 生成策略
-
-
雪花算法 (SnowFlake)
@TableName("tbl_user") // 1. 属性设置
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
}
mybatis-plus: ## 2. 全局策略
global-config:
db-config:
id-type: assign_id
table-prefix: tbl_
5.4.2 多数据操作
List<Long> list = new ArrayList<>();
list.add(1L);
list.add(2L);
list.add(3L);
userDao.selectBatchIds(list); // 1. 根据 id 查询多条记录
userDao.deleteBatchIds(list); // 2. 根据 id 删除多条记录
5.4.3 逻辑删除
- 删除操作业务问题:业务数据从数据库中丢弃
-
逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中
public class User { // 1. 属性设置 private Long id; @TableLogic(value = "0" ,delval = "1") private Integer deleted; }
mybatis-plus: ## 2. 全局策略 global-config: db-config: logic-delete-field: deleted logic-not-delete-value: 0 logic-delete-value: 1
5.4.4 乐观锁
-
业务并发现象带来的问题:秒杀
-
乐观锁:主要解决的是当要更新一条记录的时候,希望这条记录没有被别人更新
使用乐观锁机制,在 update 前必须先获取到对应数据的 version 方可正常进行
User user1 = userDao.selectById(1L); // version=3 User user2 = userDao.selectById(1L); // version=3 user2.setName("aaa"); userDao.updateById(user2); // version=4 // UPDATE tbl_user SET name="aaa",version=4 WHERE id=1L AND version=3 user1.setName("bbb"); userDao.updateById(user1); // error! // UPDATE tbl_user SET name="bbb",version=4 WHERE id=1L AND version=3
-
实现步骤
-
数据库表中添加锁标记字段(
version
) -
实体类中添加对应字段,并设定当前字段为逻辑删除标记字段
public class User { private Long id; @Version private Integer version; }
- 配置乐观锁拦截器,实现锁机制对应的动态 SQL 语句拼装(
MpConfig.java
)
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
-
5 代码生成器
- 代码生成器能够快速根据数据库表来创建对应的类,简化我们的代码开发
-
创建 SpringBoot 新模块,导入相关的 jar 包(pom.xml)
<!--1. mybatisplus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> <!--2. 代码生成器--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.1</version> </dependency> <!--3. velocity模板引擎--> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency> <!-- others: spring webmvc, druid, mysql, test, lombok -->
-
创建代码生成器类(CodeGenerator.java)
public class CodeGenerator { public static void main(String[] args) { AutoGenerator autoGenerator = new AutoGenerator(); // 1. 设置数据库相关配置 DataSourceConfig dataSource = new DataSourceConfig(); dataSource.setDriverName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db"); dataSource.setUsername("root"); dataSource.setPassword("1234"); autoGenerator.setDataSource(dataSource); // 2. 设置全局配置 GlobalConfig globalConfig = new GlobalConfig(); globalConfig.setOutputDir(System.getProperty("user.dir")+"/src/main/java"); // 设置代码生成位置 globalConfig.setOpen(false); // 设置生成完毕后是否打开生成代码所在的目录 globalConfig.setAuthor("黑马程序员"); // 设置作者 globalConfig.setFileOverride(true); // 设置是否覆盖原始生成的文件 globalConfig.setMapperName("%sDao"); // 设置数据层接口名,%s为占位符,指代模块名称 globalConfig.setIdType(IdType.ASSIGN_ID); // 设置Id生成策略 autoGenerator.setGlobalConfig(globalConfig); // 3. 设置包名相关配置 PackageConfig packageInfo = new PackageConfig(); packageInfo.setParent("com.aaa"); // 设置生成的包名 packageInfo.setEntity("domain"); // 设置实体类包名 packageInfo.setMapper("dao"); // 设置数据层包名 autoGenerator.setPackageInfo(packageInfo); // 4. 策略设置 StrategyConfig strategyConfig = new StrategyConfig(); strategyConfig.setInclude("tbl_user"); // 设置当前参与生成的表名,参数为可变参数 strategyConfig.setTablePrefix("tbl_"); // 设置数据库表的前缀名称 strategyConfig.setRestControllerStyle(true); // 设置是否启用Rest风格 strategyConfig.setVersionFieldName("version"); // 设置乐观锁字段名 strategyConfig.setLogicDeleteFieldName("deleted"); // 设置逻辑删除字段名 strategyConfig.setEntityLombokModel(true); // 设置是否启用lombok autoGenerator.setStrategy(strategyConfig); autoGenerator.execute(); } }
-
运行成功后,会在当前项目中生成很多代码,包含 controller , service,mapper 和 entity
网友评论