【SpringCache】通用缓存使用与注解介绍（快速入门）
1、简介Spring Cache 是一个抽象层，它提供了一种在应用程序中使用缓存的统一方式，而具体使用哪种数据库进行缓存是可以根据需要进行配置的。Spring Cache 支持使用多种缓存技术，包括但不限于：ConcurrentMapCache: 使用ConcurrentHashMap作为缓存实现，适合于单个应用节点的缓存需求；EhCache: 一个流行的开源缓存框架，可以单独使用，也可以与Spring集成；Redis: 一个流行的内存数据库，也可以用作缓存；Caffeine: 一个基于 Java 8 的高性能缓存库，支持内存缓存和硬盘缓存等；Hazelcast: 分布式内存数据网格，支持缓存、分布式Map、消息等。以上只是其中的几个例子，具体使用哪种缓存技术取决于应用的具体需求和限制条件。2、案例使用Redis作为Spring Cache缓存数据库需要进行以下配置：首先，在pom.xml文件中添加Spring Boot对Redis和Spring Cache的依赖，例如： <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>然后，在application.properties或application.yml中添加Redis相关配置，例如：# Redis配置 spring.redis.host=localhost spring.redis.port=6379 spring.redis.password= # Spring Cache配置 spring.cache.type=redis spring.cache.redis.time-to-live=3600在以上配置中，我们指定了Redis的主机名、端口号和密码（如果有的话），并将Spring Cache的类型指定为Redis。此外，我们还设置了缓存的存活时间为3600秒，即1小时。最后，在需要使用缓存的方法上添加@Cacheable注解即可，例如：@Service public class MyService { @Cacheable(value = "myCache", key = "#id") public MyObject getById(Long id) { // 从数据库中查询MyObject，并返回 } }在以上代码中，我们使用@Cacheable注解对getById方法进行缓存配置，指定了缓存的名称为myCache，缓存的键值为方法的参数id。这样，当getById方法被调用时，Spring会先检查缓存中是否有相应的缓存值，如果有的话则直接返回，否则才会从数据库中查询并返回对象，并将其缓存起来。问：其中value = "myCache", key = "#id"的含义是什么？[scode type="green" size=""]答：在使用@Cacheable注解时，value和key是必须指定的两个参数，它们的含义如下：value: 指定缓存的名称，可以理解为缓存的命名空间，不同的缓存名称对应不同的缓存区域。key: 指定缓存的键值，用于在缓存中唯一标识该缓存项。在Spring Cache中，当一个方法被标记为@Cacheable时，Spring会根据value和key的值将其结果缓存起来。具体来说，Spring会将key作为缓存的键，在缓存中存储该方法的返回值。当下一次调用该方法时，Spring会先检查缓存中是否有对应的键值，如果有则直接返回缓存值，否则才会执行该方法，并将返回值存入缓存。在上面的示例代码中，value指定了缓存的名称为myCache，key指定了缓存的键值为方法的参数id。这意味着，当调用getById方法时，Spring会将id参数作为缓存的键，在myCache缓存中查找是否有对应的缓存值。如果有，则直接返回缓存值；如果没有，则执行方法，将方法的返回值缓存起来，并以id作为键值存储在myCache缓存中。[/scode]问：如果我没有配置任何数据库，它还能进行缓存吗？[scode type="green" size=""]如果没有配置任何缓存数据库，Spring Cache会使用默认的缓存配置，将缓存数据存储在内存中，这样可以进行基本的缓存操作，但是缓存的生命周期只在应用程序运行期间有效，一旦应用程序停止，缓存中的数据就会被清空。默认情况下，Spring Cache使用ConcurrentHashMap作为缓存存储，该缓存是基于内存的，可以快速读写。如果没有配置其他缓存数据库，Spring会使用该缓存存储数据。在这种情况下，您只需要在需要进行缓存的方法上添加@Cacheable、@CachePut或@CacheEvict注解即可，Spring会自动将结果缓存起来。但需要注意的是，使用内存缓存的方式存在一些限制，例如：内存缓存的容量受限于应用程序所在的服务器的内存容量；应用程序重启或服务器重启会导致缓存失效，需要重新加载数据；缓存无法实现分布式部署，不适用于多台服务器的集群环境。因此，如果需要进行更高效、更稳定、更可靠的缓存操作，建议配置一个外部的缓存数据库，例如Redis、Ehcache等。[/scode]3、常用注解Spring Cache提供了多种注解，常用的注解包括@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfi。下面是这些注解的介绍及示例：3.1、@Cacheable注解@Cacheable注解可以标记在方法上，用于将方法返回值缓存起来。当下一次调用该方法时，Spring会先从缓存中查找对应的缓存值，如果存在则直接返回缓存值，否则执行该方法并将返回值存入缓存。@Cacheable注解支持以下参数：value：缓存的名称，必须指定；key：缓存的键值，用于在缓存中查找缓存项，可以使用SpEL表达式动态生成键值；condition：缓存的条件，当满足条件时才进行缓存；unless：缓存的排除条件，当满足条件时不进行缓存；sync：是否使用异步模式进行缓存操作。示例代码：@Service public class UserService { @Cacheable(value = "userCache", key = "#id") public User getUserById(Long id) { // 从数据库中获取用户信息 User user = userDao.getUserById(id); return user; } }3.2、@CachePut注解@CachePut注解可以标记在方法上，用于更新或添加缓存项。当方法执行完成后，Spring会将方法的返回值存入缓存中，即使缓存中已经存在相同的缓存项，也会覆盖掉原来的值。@CachePut注解的参数和@Cacheable注解一样。示例代码：@Service public class UserService { @CachePut(value = "userCache", key = "#user.id") public User updateUser(User user) { // 更新数据库中的用户信息 userDao.updateUser(user); return user; } }[scode type="green" size="simple"]@CachePut注解只能把方法返回结果存入缓存，但不能从缓存中取数据。[/scode]3.3、@CacheEvict注解@CacheEvict注解可以标记在方法上，用于清除缓存中的指定缓存项。当方法执行完成后，Spring会将指定的缓存项从缓存中清除。@CacheEvict注解支持以下参数：value：缓存的名称，必须指定；key：缓存的键值，用于在缓存中查找缓存项，可以使用SpEL表达式动态生成键值；condition：缓存的条件，当满足条件时才进行缓存。示例代码：@Service public class UserService { @CacheEvict(value = "userCache", key = "#id") public void deleteUserById(Long id) { // 从数据库中删除用户信息 userDao.deleteUserById(id); } }3.4、@Caching注解@Caching注解可以将多个缓存注解组合起来使用，可以用于复杂的缓存场景。示例代码：@Service public class UserService { @Caching( cacheable = { @Cacheable(value = "userCache", key = "#id"), @Cacheable(value = "userCache", key = "#username") }, put = { @CachePut(value = "userCache", key = "#user.id"), @CachePut(value = "userCache", key = "#user.username") } ) public User getUserByIdOrUsername(Long id, String username) { // 从数据库中获取用户信息 User user = userDao.getUserByIdOrUsername(id, username); return user; } }3.5、@CacheConfig注解@CacheConfig注解可以用于统一配置缓存的名称和属性，避免在每个缓存注解上都重复指定。示例代码：@Service @CacheConfig(cacheNames = "userCache") public class UserService { @Cacheable(key = "#id") public User getUserById(Long id) { // 从数据库中获取用户信息 User user = userDao.getUserById(id); return user; } @CachePut(key = "#user.id") public User updateUser(User user) { // 更新数据库中的用户信息 userDao.updateUser(user); return user; } @CacheEvict(key = "#id") public void deleteUserById(Long id) { // 从数据库中删除用户信息 userDao.deleteUserById(id); } }以上是Spring Cache常用的注解及示例，它们可以大大简化缓存的使用，提高系统性能。4、总结注解功能示例@Cacheable查询缓存@Cacheable(value = "userCache", key = "#id")@CachePut更新缓存@CachePut(value = "userCache", key = "#user.id")@CacheEvict删除缓存@CacheEvict(value = "userCache", key = "#id")@Caching组合多个缓存注解@Caching(cacheable = {...}, put = {...})@CacheConfig统一配置缓存名称和属性@CacheConfig(cacheNames = "userCache")通过这些注解，我们可以方便地使用缓存，提高系统的性能和响应速度。Spring Cache是一个非常方便易用的缓存框架，它可以很好地与Spring框架集成，支持多种缓存实现（如Ehcache、Redis等），并提供了多种注解，可以方便地进行缓存的配置和使用。使用Spring Cache可以大大提高系统的性能和响应速度，特别是在读取频繁、计算复杂的业务场景下，使用缓存可以显著降低系统的响应时间和资源消耗。另外，Spring Cache还支持缓存的自动刷新、失效等功能，可以保证缓存数据的准确性和实时性。同时，它还提供了丰富的缓存统计和监控功能，可以方便地对缓存进行管理和优化。综上所述，Spring Cache是一个非常优秀的缓存框架，它的设计和实现都非常优秀，使用起来非常方便、稳定和高效。它在大型Web应用和分布式系统中得到了广泛的应用和验证。
学习目标基于SpringBoot框架的程序开发步骤熟练使用SpringBoot配置信息修改服务器配置基于SpringBoot的完成SSM整合项目开发一、SpringBoot简介1. 入门案例问题导入SpringMVC的HelloWord程序大家还记得吗？SpringBoot是由Pivotal团队提供的全新框架，其设计目的是用来简化Spring应用的初始搭建以及开发过程原生开发SpringMVC程序过程1.1 入门案例开发步骤①：创建新模块，选择Spring初始化，并配置模块相关基础信息②：选择当前模块需要使用的技术集③：开发控制器类@RestController @RequestMapping("/books") public class BookController { @GetMapping("/{id}") public String getById(@PathVariable Integer id) { System.out.println("id ==> " + id); return "hello , spring boot! "; } }④：运行自动生成的Application类最简SpringBoot程序所包含的基础文件<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> </parent> <groupId>com.itheima</groupId> <artifactId>springboot-01-quickstart</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }Spring程序与SpringBoot程序对比注意事项：基于idea开发SpringBoot程序需要确保联网且能够加载到程序框架结构1.2 基于SpringBoot官网创建项目1.3 SpringBoot项目快速启动① 对SpringBoot项目打包（执行Maven构建指令package）② 执行启动指令java -jar springboot_01_quickstart.jar # 项目的名称根据实际情况修改注意事项：jar支持命令行启动需要依赖maven插件支持，请确认打包时是否具有SpringBoot对应的maven插件。<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>2. SpringBoot概述问题导入学习了SpringBoot入门案例之后，感觉对比SpringMVC哪一个更加方便简洁？SpringBoot是由Pivotal团队提供的全新框架，其设计目的是用来简化Spring应用的初始搭建以及开发过程Spring程序缺点配置繁琐依赖设置繁琐SpringBoot程序优点自动配置起步依赖（简化依赖配置）辅助功能（内置服务器，……）2.1 起步依赖starterSpringBoot中常见项目名称，定义了当前项目使用的所有项目坐标，以达到减少依赖配置的目的<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> </parent> <groupId>com.itheima</groupId> <artifactId>springboot-01-quickstart</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project><project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.5.0</version> <packaging>pom</packaging> <properties> <servlet-api.version>4.0.1</servlet-api.version> ... </properties> </project>parent所有SpringBoot项目要继承的项目，定义了若干个坐标版本号（依赖管理，而非依赖），以达到减少依赖冲突的目的spring-boot-starter-parent（2.5.0）与 spring-boot-starter-parent（2.4.6）共计57处坐标版本不同<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.5.0</version> </parent> <artifactId>spring-boot-starter-parent</artifactId> <packaging>pom</packaging> ... </project>实际开发使用任意坐标时，仅书写GAV中的G和A，V由SpringBoot提供如发生坐标错误，再指定version（要小心版本冲突）<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet-api.version}</version> </dependency><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>2.2 辅助功能SpringBoot程序启动@SpringBootApplication public class Springboot01QuickstartApplication { public static void main(String[] args) { SpringApplication.run(Springboot01QuickstartApplication.class, args); } }SpringBoot在创建项目时，采用jar的打包方式SpringBoot的引导类是项目的入口，运行main方法就可以启动项目使用maven依赖管理变更起步依赖项Jetty比Tomcat更轻量级，可扩展性更强（相较于Tomcat），谷歌应用引擎（GAE）已经全面切换为Jetty<dependencies> <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> </dependency> </dependencies>二、基础配置1. 配置文件格式问题导入框架常见的配置文件有哪几种形式？1.1 修改服务器端口http://localhost:8080/books/1 >>> http://localhost/books/1SpringBoot提供了多种属性配置方式application.propertiesserver.port=80application.ymlserver: port: 81application.yamlserver: port: 821.2 自动提示功能消失解决方案操作步骤：1.3 SpringBoot配置文件加载顺序（了解）application.properties > application.yml > application.yaml注意事项：SpringBoot核心配置文件名为applicationSpringBoot内置属性过多，且所有属性集中在一起修改，在使用时，通过提示键+关键字修改属性2. yaml问题导入什么是yaml，和properties有什么区别？YAML（YAML Ain't Markup Language），一种数据序列化格式优点：容易阅读容易与脚本语言交互以数据为核心，重数据轻格式YAML文件扩展名.yml（主流）.yaml2.1 yaml语法规则大小写敏感属性层级关系使用多行描述，每行结尾使用冒号结束使用缩进表示层级关系，同层级左侧对齐，只允许使用空格（不允许使用Tab键）属性值前面添加空格（属性名与属性值之间使用冒号+空格作为分隔）#表示注释核心规则：数据前面要加空格与冒号隔开2.2 yaml数组数据数组数据在数据书写位置的下方使用减号作为数据开始符号，每行书写一个数据，减号与数据间空格分隔2.3 yaml数据读取使用@Value读取单个数据，属性名引用方式：${一级属性名.二级属性名……}封装全部数据到Environment对象自定义对象封装指定数据【常用】public class Enterprise { private String name; private Integer age; private String tel; private String[] subject; //自行添加getter、setter、toString()等方法 }自定义对象封装数据警告解决方案<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>3. 多环境开发配置问题导入在实际开发中，项目的开发环境、测试环境、生产环境的配置信息是否会一致？如何快速切换？3.1 多环境启动配置yaml文件多环境启动properties文件多环境启动#主启动配置文件 application.properties spring.profiles.active=pro#环境分类配置文件 application-pro.properties server.port=80#环境分类配置文件 application-dev.properties server.port=81#环境分类配置文件application-test.properties server.port=823.2 多环境启动命令格式带参数启动SpringBootjava –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参数加载优先顺序参看文档：https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config3.3 多环境开发控制Maven与SpringBoot多环境兼容（步骤）①：Maven中设置多环境属性<profiles> <profile> <id>dev_env</id> <properties> <profile.active>dev</profile.active> </properties> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <id>pro_env</id> <properties> <profile.active>pro</profile.active> </properties> </profile> <profile> <id>test_env</id> <properties> <profile.active>test</profile.active> </properties> </profile> </profiles>②：SpringBoot中引用Maven属性③：执行Maven打包指令Maven指令执行完毕后，生成了对应的包，其中类参与编译，但是配置文件并没有编译，而是复制到包中解决思路：对于源码中非java类的操作要求加载Maven对应的属性，解析${}占位符④：对资源文件开启对默认占位符的解析<build> <plugins> <plugin> <artifactId>maven-resources-plugin</artifactId> <configuration> <encoding>utf-8</encoding> <useDefaultDelimiters>true</useDefaultDelimiters> </configuration> </plugin> </plugins> </build>Maven打包加载到属性，打包顺利通过4. 配置文件分类问题导入SpringBoot的配置文件可以放在项目的哪些地方？java –jar springboot.jar --spring.profiles.active=test --server.port=85 --server.servlet.context-path=/heima --server.tomcat.connection-timeout=-1 ... ...SpringBoot中4级配置文件1级： file ：config/application.yml 【最高】2级： file ：application.yml3级：classpath：config/application.yml4级：classpath：application.yml 【最低】作用：1级与2级留做系统打包后设置通用属性3级与4级用于系统开发阶段设置通用属性三、整合第三方技术1. 整合JUnit问题导入回忆一下Spring整合JUnit的步骤？1.1 Spring整合JUnit（复习）1.2 SpringBoot整合JUnit【第一步】添加整合junit起步依赖(可以直接勾选)<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>【第二步】编写测试类，默认自动生成了一个@SpringBootTest class Springboot07JunitApplicationTests { @Autowired private BookService bookService; @Test public void testSave() { bookService.save(); } }2. 基于SpringBoot实现SSM整合问题导入回忆一下Spring整合MyBatis的核心思想？2.1 Spring整合MyBatis（复习）SpringConfig导入JdbcConfig导入MyBatisConfig@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class, MyBatisConfig.class}) public class SpringConfig { }JDBCConfig定义数据源（加载properties配置项：driver、url、username、password）#jdbc.properties jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_db jdbc.username=root jdbc.password=itheimapublic class JdbcConfig { @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 public DataSource getDataSource() { DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } }MyBatisConfig定义SqlSessionFactoryBean定义映射配置@Bean public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean(); ssfb.setTypeAliasesPackage("com.itheima.domain"); ssfb.setDataSource(dataSource); return ssfb; }@Bean public MapperScannerConfigurer getMapperScannerConfigurer() { MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("com.itheima.dao"); return msc; }2.2 SpringBoot整合MyBatisSpringBoot整合Spring（不存在）SpringBoot整合SpringMVC（不存在）SpringBoot整合MyBatis（主要）①：创建新模块，选择Spring初始化，并配置模块相关基础信息②：选择当前模块需要使用的技术集（MyBatis、MySQL）③：设置数据源参数spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC username: root password: root type: com.alibaba.druid.pool.DruidDataSource注意事项：SpringBoot版本低于2.4.3(不含)，Mysql驱动版本大于8.0时，需要在url连接串中配置时区，或在MySQL数据库端配置时区解决此问题jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC④：定义数据层接口与映射配置@Mapper public interface UserDao { @Select("select * from tbl_book where id=#{id}") Book getById(Integer id); }⑤：测试类中注入dao接口，测试功能@SpringBootTest class Springboot08MybatisApplicationTests { @Autowired private BookDao bookDao; @Test public void testGetById() { Book book = bookDao.getById(1); System.out.println(book); } }2.3 案例-SpringBoot实现ssm整合【第一步】创建SpringBoot工程，添加druid依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.6</version> </dependency>【第二步】复制springmvc_11_page工程各种资源(主java类、页面、测试类)【第三步】删除config包中的所有配置，在BookDao接口上加@Mapper注解//todo 3 在BookDao接口上加@Mapper注解，让SpringBoot给接口创建代理对象 @Mapper public interface BookDao { //... }【第四步】将application.properties修改成application.yml，配置端口号和连接参数server: port: 80 # todo 4 配置数据库连接参数 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db username: root password: root type: com.alibaba.druid.pool.DruidDataSource【第五步】修改BookServiceTest配置类，进行配置// todo 5 修改单元测试类，添加@SpringBootTest主键，修复@Test注解导包 @SpringBootTest public class BookServiceTest { @Autowired private BookService bookService; @Test public void testGetById(){ Book book = bookService.getById(2); //传递参数1会抛出异常 System.out.println(book); } @Test public void testGetAll(){ List<Book> all = bookService.getAll(); System.out.println(all); } }【第六步】在static目录中提供index.html页面，跳转到"pages/books.html" 最后：运行引导类即可访问
今日目标能够理解AOP的作用能够完成AOP的入门案例能够理解AOP的工作流程能够说出AOP的五种通知类型能够完成"测量业务层接口万次执行效率"案例能够掌握Spring事务配置一、AOP1 AOP简介问题导入[scode type="yellow" size=""]问题1：AOP的作用是什么？问题2：连接点和切入点有什么区别，二者谁的范围大？问题3：请描述什么是切面？[/scode]1.1 AOP简介和作用【理解】AOP(Aspect Oriented Programming)面向切面编程，一种编程范式，指导开发者如何组织程序结构OOP(Object Oriented Programming)面向对象编程作用：在不惊动原始设计的基础上为其进行功能增强。简单的说就是在不改变方法源代码的基础上对方法进行功能增强。Spring理念：无入侵式/无侵入式1.2 AOP中的核心概念【理解】连接点（JoinPoint）：正在执行的方法，例如：update()、delete()、select()等都是连接点。切入点（Pointcut）：进行功能增强了的方法，例如:update()、delete()方法，select()方法没有被增强所以不是切入点，但是是连接点。在SpringAOP中，一个切入点可以只描述一个具体方法，也可以匹配多个方法一个具体方法：com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法匹配多个方法：所有的save方法，所有的get开头的方法，所有以Dao结尾的接口中的任意方法，所有带有一个参数的方法通知（Advice）：在切入点前后执行的操作，也就是增强的共性功能在SpringAOP中，功能最终以方法的形式呈现通知类：通知方法所在的类叫做通知类切面（Aspect）：描述通知与切入点的对应关系，也就是哪些通知方法对应哪些切入点方法。2 AOP入门案例【重点】问题导入[scode type="yellow" size=""]问题1：在通知方法中如何定义切入点表达式？问题2：如何配置切面？问题3：在配置类上如何开启AOP注解功能？[/scode]2.1 AOP入门案例思路分析案例设定：测定接口执行效率简化设定：在接口执行前输出当前系统时间开发模式：XML or [font color="#FF0000"]注解[/font]思路分析：导入坐标（pom.xml）制作连接点方法（原始操作，dao接口与实现类）制作共性功能（通知类与通知）定义切入点绑定切入点与通知关系（切面）2.2 AOP入门案例实现【第一步】导入aop相关坐标<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>【第二步】定义dao接口与实现类public interface BookDao { public void save(); public void update(); } @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 ..."); } }【第三步】定义通知类，制作通知方法//通知类必须配置成Spring管理的bean @Component public class MyAdvice { public void method(){ System.out.println(System.currentTimeMillis()); } }【第四步】定义切入点表达式、配置切面(绑定切入点与通知关系)//通知类必须配置成Spring管理的bean @Component //设置当前类为切面类类 @Aspect public class MyAdvice { //设置切入点，@Pointcut注解要求配置在方法上方 @Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt(){} //设置在切入点pt()的前面运行当前操作(前置通知) @Before("pt()") public void method(){ System.out.println(System.currentTimeMillis()); } }【第五步】在配置类中进行Spring注解包扫描和开启AOP功能@Configuration @ComponentScan("com.itheima") //开启注解开发AOP功能 @EnableAspectJAutoProxy public class SpringConfig { }测试类和运行结果public class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); BookDao bookDao = ctx.getBean(BookDao.class); bookDao.update(); } }3 AOP工作流程【理解】问题导入[scode type="yellow" size=""]什么是目标对象？什么是代理对象？[/scode]3.1 AOP工作流程Spring容器启动读取所有切面配置中的切入点初始化bean，判定bean对应的类中的方法是否匹配到任意切入点匹配失败，创建原始对象匹配成功，创建原始对象（目标对象）的代理对象获取bean执行方法获取的bean是原始对象时，调用方法并执行，完成操作获取的bean是代理对象时，根据代理对象的运行模式运行原始方法与增强的内容，完成操作3.2 AOP核心概念目标对象（Target）：被代理的对象，也叫原始对象，该对象中的方法没有任何功能增强。代理对象（Proxy）：代理后生成的对象，由Spring帮我们创建代理对象。3.3 在测试类中验证代理对象public class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); BookDao bookDao = ctx.getBean(BookDao.class); bookDao.update(); //打印对象的类名 System.out.println(bookDao.getClass()); } }4 AOP切入点表达式问题导入[scode type="yellow" size=""]在切入点表达式中如何简化包名和参数类型书写？[/scode]4.1 语法格式切入点：要进行增强的方法切入点表达式：要进行增强的方法的描述方式描述方式一：执行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(public User com.itheima.service.UserService.findById(int))动作关键字：描述切入点的行为动作，例如execution表示执行到指定切入点访问修饰符：public，private等，可以省略返回值：写返回值类型包名：多级包使用点连接类/接口名：方法名：参数：直接写参数的类型，多个类型用逗号隔开异常名：方法定义中抛出指定异常，可以省略4.2 通配符目的：可以使用通配符描述切入点，快速描述。*：单个独立的任意符号，可以独立出现，也可以作为前缀或者后缀的匹配符出现匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法execution（public * com.itheima.*.UserService.find*(*)).. ：多个连续的任意符号，可以独立出现，常用于简化包名与参数的书写匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法execution（public User com..UserService.findById(..))+：专用于匹配子类类型execution(* *..*Service+.*(..))4.3 书写技巧所有代码按照标准规范开发，否则以下技巧全部失效描述切入点通[font color="#FF0000"]常描述接口[/font]，而不描述实现类访问控制修饰符针对接口开发均采用public描述（[font color="#FF0000"]可省略访问控制修饰符描述[/font]）返回值类型对于增删改类使用精准类型加速匹配，对于查询类使用*通配快速描述[font color="#FF0000"]包名[/font]书写[font color="#FF0000"]尽量不使用..匹配[/font]，效率过低，常用*做单个包描述匹配，或精准匹配[font color="#FF0000"]接口名/类名[/font]书写名称与模块相关的采用*匹配，例如UserService书写成*Service，绑定业务层接口名[font color="#FF0000"]方法名[/font]书写以[font color="#FF0000"]动词[/font]进行[font color="#FF0000"]精准匹配[/font]，名词采用*匹配，例如getById书写成getBy*,selectAll书写成selectAll参数规则较为复杂，根据业务方法灵活调整通常[font color="#FF0000"]不使用异常[/font]作为[font color="#FF0000"]匹配[/font]规则5 AOP通知类型【重点】问题导入[scode type="yellow" size=""]请描述一下如何定义环绕通知方法？[/scode]5.1 AOP通知分类AOP通知描述了抽取的共性功能，根据共性功能抽取的位置不同，最终运行代码时要将其加入到合理的位置AOP通知共分为5种类型前置通知：在切入点方法执行之前执行后置通知：在切入点方法执行之后执行，无论切入点方法内部是否出现异常，后置通知都会执行。[font color="#FF0000"]环绕通知(重点)：[/font]手动调用切入点方法并对其进行增强的通知方式。返回后通知(了解)：在切入点方法执行之后执行，如果切入点方法内部出现异常将不会执行。抛出异常后通知(了解)：在切入点方法执行之后执行，只有当切入点方法内部出现异常之后才执行。5.2 AOP通知详解5.2.1 前置通知名称：@Before类型：[font color="#FF0000"]方法注解[/font]位置：通知方法定义上方作用：设置当前通知方法与切入点之间的绑定关系，当前通知方法在原始切入点方法前运行范例：@Before("pt()") public void before() { System.out.println("before advice ..."); }5.2.2 后置通知名称：@After类型：[font color="#FF0000"]方法注解[/font]位置：通知方法定义上方作用：设置当前通知方法与切入点之间的绑定关系，当前通知方法在原始切入点方法后运行范例：@After("pt()") public void after() { System.out.println("after advice ..."); }5.2.3 返回后通知名称：@AfterReturning（了解）类型：[font color="#FF0000"]方法注解[/font]位置：通知方法定义上方作用：设置当前通知方法与切入点之间的绑定关系，当前通知方法在原始切入点方法正常执行完毕后运行范例：@AfterReturning("pt()") public void afterReturning() { System.out.println("afterReturning advice ..."); }5.2.4 抛出异常后通知名称：@AfterThrowing（了解）类型：[font color="#FF0000"]方法注解[/font]位置：通知方法定义上方作用：设置当前通知方法与切入点之间的绑定关系，当前通知方法在原始切入点方法运行抛出异常后执行范例：@AfterThrowing("pt()") public void afterThrowing() { System.out.println("afterThrowing advice ..."); }5.2.5 环绕通知名称：@Around（重点，常用）类型：[font color="#FF0000"]方法注解[/font]位置：通知方法定义上方作用：设置当前通知方法与切入点之间的绑定关系，当前通知方法在原始切入点方法前后运行范例：：@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; }[font color="#FF0000"]环绕通知注意事项[/font][scode type="red" size=""]环绕通知方法形参必须是ProceedingJoinPoint，表示正在执行的连接点，使用该对象的proceed()方法表示对原始对象方法进行调用，返回值为原始对象方法的返回值。环绕通知方法的返回值建议写成Object类型，用于将原始对象方法的返回值进行返回，哪里使用代理对象就返回到哪里。[/scode]二、AOP案例1 案例-测量业务层接口万次执行效率问题导入[scode type="yellow" size=""]能不能描述一下环绕通知里面的实现步骤？[/scode]1.1 需求和分析需求：任意业务层接口执行均可显示其执行效率（执行时长）分析：[scode type="yellow" size=""]业务功能：业务层接口执行前后分别记录时间，求差值得到执行效率通知类型选择前后均可以增强的类型——环绕通知[/scode]1.2 代码实现【前置工作】环境准备Spring整合mybatis对spring_db数据库中的Account进行CRUD操作Spring整合Junit测试CRUD是否OK。在pom.xml中添加aspectjweaver切入点表达式依赖... ...【第一步】编写通知类@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"); } }【第二步】在SpringConfig配置类上开启AOP注解功能@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) @EnableAspectJAutoProxy //开启AOP注解功能 public class SpringConfig { }【第三步】运行测试类，查看结果@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceTestCase { @Autowired private AccountService accountService; @Test public void testFindById(){ Account account = accountService.findById(2); } @Test public void testFindAll(){ List<Account> list = accountService.findAll(); } }2 AOP切入点数据获取问题导入[scode type="yellow" size=""]在环绕通知中可以获取到哪些数据？[/scode]2.1 获取参数说明：在前置通知和环绕通知中都可以获取到连接点方法的参数们JoinPoint对象描述了连接点方法的运行状态，可以获取到原始方法的调用参数@Before("pt()") public void before(JoinPoint jp) { Object[] args = jp.getArgs(); //获取连接点方法的参数们 System.out.println(Arrays.toString(args)); }ProccedJointPoint是JoinPoint的子类@Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); //获取连接点方法的参数们 System.out.println(Arrays.toString(args)); Object ret = pjp.proceed(); return ret; }2.2 获取返回值说明：在返回后通知和环绕通知中都可以获取到连接点方法的返回值抛出异常后通知可以获取切入点方法中出现的异常信息，使用形参可以接收对应的异常对象@AfterReturning(value = "pt()",returning = "ret") public void afterReturning(String ret) { //变量名要和returning="ret"的属性值一致 System.out.println("afterReturning advice ..."+ret); }环绕通知中可以手工书写对原始方法的调用，得到的结果即为原始方法的返回值@Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { // 手动调用连接点方法，返回值就是连接点方法的返回值 Object ret = pjp.proceed(); return ret; }2.3 获取异常说明：在抛出异常后通知和环绕通知中都可以获取到连接点方法中出现的异常抛出异常后通知可以获取切入点方法中出现的异常信息，使用形参可以接收对应的异常对象@AfterThrowing(value = "pt()",throwing = "t") public void afterThrowing(Throwable t) {//变量名要和throwing = "t"的属性值一致 System.out.println("afterThrowing advice ..."+ t); }抛出异常后通知可以获取切入点方法运行的异常信息，使用形参可以接收运行时抛出的异常对象@Around("pt()") public Object around(ProceedingJoinPoint pjp) { Object ret = null; //此处需要try...catch处理，catch中捕获到的异常就是连接点方法中抛出的异常 try { ret = pjp.proceed(); } catch (Throwable t) { t.printStackTrace(); } return ret; }3 案例-百度网盘密码数据兼容处理问题导入[scode type="yellow" size=""]请说出我们该使用什么类型的通知来完成这个需求？[/scode]3.1 需求和分析需求：对百度网盘分享链接输入密码时尾部多输入的空格做兼容处理分析：①：在业务方法执行之前对所有的输入参数进行格式处理——trim()②：使用处理后的参数调用原始方法——环绕通知中存在对原始方法的调用3.2 代码实现【前置工作】环境准备//-------------service层代码----------------------- public interface ResourcesService { public boolean openURL(String url ,String password); } @Service public class ResourcesServiceImpl implements ResourcesService { @Autowired private ResourcesDao resourcesDao; public boolean openURL(String url, String password) { return resourcesDao.readResources(url,password); } } //-------------dao层代码----------------------- public interface ResourcesDao { boolean readResources(String url, String password); } @Repository public class ResourcesDaoImpl implements ResourcesDao { public boolean readResources(String url, String password) { System.out.println(password.length()); //模拟校验 return password.equals("root"); } }【第一步】编写通知类@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; } }【第二步】在SpringConfig配置类上开启AOP注解功能@Configuration @ComponentScan("com.itheima") @EnableAspectJAutoProxy public class SpringConfig { }【第三步】运行测试类，查看结果public class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); ResourcesService resourcesService = ctx.getBean(ResourcesService.class); boolean flag = resourcesService.openURL("http://pan.baidu.com/haha", "root "); System.out.println(flag); } }4 AOP开发总结4.1 AOP的核心概念概念：AOP(Aspect Oriented Programming)面向切面编程，一种编程范式作用：在不惊动原始设计的基础上为方法进行功能增强核心概念代理（Proxy）：SpringAOP的核心本质是采用代理模式实现的连接点（JoinPoint）： 在SpringAOP中，理解为任意方法的执行切入点（Pointcut）：匹配连接点的式子，也是具有共性功能的方法描述通知（Advice）：若干个方法的共性功能，在切入点处执行，最终体现为一个方法切面（Aspect）：描述通知与切入点的对应关系目标对象（Target）：被代理的原始对象成为目标对象4.2 切入点表达式语法切入点表达式标准格式：动作关键字(访问修饰符 返回值 包名.类/接口名.方法名（参数）异常名)execution(* com.itheima.service.*Service.*(..))切入点表达式描述通配符：作用：用于快速描述，范围描述*：匹配任意符号（常用）.. ：匹配多个连续的任意符号（常用）+：匹配子类类型切入点表达式书写技巧1.按标准规范开发2.查询操作的返回值建议使用*匹配3.减少使用..的形式描述包4.对接口进行描述，使用*表示模块名，例如UserService的匹配描述为*Service5.方法名书写保留动词，例如get，使用*表示名词，例如getById匹配描述为getBy*6.参数根据实际情况灵活调整4.3 五种通知类型前置通知后置通知环绕通知（重点）环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用环绕通知可以隔离原始方法的调用执行环绕通知返回值设置为Object类型环绕通知中可以对原始方法调用过程中出现的异常进行处理返回后通知抛出异常后通知三、Spring事务管理1 Spring事务简介【重点】问题导入[scode type="yellow" size=""]Spring提供的事务管理是数据层的事务还是业务层的事务?[/scode]1.1 Spring事务作用事务作用：在数据层保障一系列的数据库操作同成功同失败Spring事务作用：在数据层或[font color="#FF0000"]业务层[/font]保障一系列的数据库操作同成功同失败1.2 需求和分析需求：实现任意两个账户间转账操作需求微缩：A账户减钱，B账户加钱分析：①：数据层提供基础操作，指定账户减钱（outMoney），指定账户加钱（inMoney）②：业务层提供转账操作（transfer），调用减钱与加钱的操作③：提供2个账号和操作金额执行转账操作④：基于Spring整合MyBatis环境搭建上述操作结果分析：①：程序正常执行时，账户金额A减B加，没有问题②：程序出现异常后，转账失败，但是异常之前操作成功，异常之后操作失败，整体业务失败1.3 代码实现【前置工作】环境准备Spring整合Mybatis相关代码(依赖、JdbcConfig、MybatisConfig、SpringConfig)省略。public interface AccountDao { @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); } public interface AccountService { /** * 转账操作 * @param out 传出方 * @param in 转入方 * @param money 金额 */ public void transfer(String out,String in ,Double money) ; } @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; public void transfer(String out,String in ,Double money) { accountDao.outMoney(out,money); int i = 1/0; accountDao.inMoney(in,money); } }【第一步】在业务层接口上添加Spring事务管理public interface AccountService { //配置当前接口方法具有事务 @Transactional public void transfer(String out,String in ,Double money) ; }注意事项Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中，降低耦合注解式事务可以添加到业务方法上表示当前方法开启事务，也可以添加到接口上表示当前接口所有方法开启事务【第二步】设置事务管理器(将事务管理器添加到IOC容器中)说明：可以在JdbcConfig中配置事务管理器//配置事务管理器，mybatis使用的是jdbc事务 @Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager dtm = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; }注意事项事务管理器要根据实现技术进行选择MyBatis框架使用的是JDBC事务【第三步】开启注解式事务驱动@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) //开启注解式事务驱动 @EnableTransactionManagement public class SpringConfig { }【第四步】运行测试类，查看结果@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceTest { @Autowired private AccountService accountService; @Test public void testTransfer() throws IOException { accountService.transfer("Tom","Jerry",100D); } }2 Spring事务角色【理解】问题导入[scode type="yellow" size=""]什么是事务管理员，什么是事务协调员？[/scode]2.1 Spring事务角色事务管理员：发起事务方，在Spring中通常指代业务层开启事务的方法事务协调员：加入事务方，在Spring中通常指代数据层方法，也可以是业务层方法3 Spring事务相关配置问题导入[scode type="yellow" size=""]什么样的异常，Spring事务默认是不进行回滚的？[/scode]3.1 事务配置说明：对于RuntimeException类型异常或者Error错误，Spring事务能够进行回滚操作。但是对于编译器异常，Spring事务是不进行回滚的，所以需要使用rollbackFor来设置要回滚的异常。3.2 案例：转账业务追加日志需求和分析需求：实现任意两个账户间转账操作，并对每次转账操作在数据库进行留痕需求微缩：A账户减钱，B账户加钱，数据库记录日志分析：①：基于转账操作案例添加日志模块，实现数据库中记录日志②：业务层转账操作（transfer），调用减钱、加钱与记录日志功能实现效果预期：无论转账操作是否成功，均进行转账操作的日志留痕存在的问题：日志的记录与转账操作隶属同一个事务，同成功同失败实现效果预期改进：无论转账操作是否成功，日志必须保留事务传播行为：事务协调员对事务管理员所携带事务的处理态度【准备工作】环境整备USE spring_db; CREATE TABLE tbl_log( id INT PRIMARY KEY AUTO_INCREMENT, info VARCHAR(255), createDate DATE );public interface LogService { //propagation设置事务属性：传播行为设置为当前操作需要新事务 @Transactional void log(String out, String in, Double money); } @Service public class LogServiceImpl implements LogService { @Autowired private LogDao logDao; public void log(String out,String in,Double money ) { logDao.log("转账操作由"+out+"到"+in+",金额："+money); } } public interface LogDao { @Insert("insert into tbl_log (info,createDate) values(#{info},now())") void log(String info); }【第一步】在AccountServiceImpl中调用logService中添加日志的方法@Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; @Autowired private LogService logService; public void transfer(String out,String in ,Double money) { try{ accountDao.outMoney(out,money); int i = 1/0; accountDao.inMoney(in,money); }finally { logService.log(out,in,money); } } }【第二步】在LogService的log()方法上设置事务的传播行为public interface LogService { //propagation设置事务属性：传播行为设置为当前操作需要新事务 @Transactional(propagation = Propagation.REQUIRES_NEW) void log(String out, String in, Double money); }【第三步】运行测试类，查看结果@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceTest { @Autowired private AccountService accountService; @Test public void testTransfer() throws IOException { accountService.transfer("Tom","Jerry",50D); } }3.3 事务传播行为
今日目标能够掌握注解开发定义Bean对象能够掌握纯注解开发模式能够配置注解开发依赖注入能够配置注解开发管理第三方Bean能够配置注解开发为第三方Bean注入资源能够使用Spring整合Mybatis能够使用Spring整合Junit一、第三方资源配置管理说明：以管理DataSource连接池对象为例讲解第三方资源配置管理，DataSource是获取物理数据源连接的工厂类。1 管理DataSource连接池对象问题导入配置数据库连接参数时，注入驱动类名是用driverClassName还是driver？1.1 管理Druid连接池【重点】数据库准备create database if not exists spring_db character set utf8; use spring_db; create table if not exists tbl_account( id int primary key auto_increment, name varchar(20), money double ); insert into tbl_account values(null,'Tom',1000); insert into tbl_account values(null,'Jerry',1000);【第一步】添加Druid连接池依赖<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>注意：除了添加以上两个依赖之外，别忘了添加spring-context依赖。【第二步】配置DruidDataSource连接池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="root"/> </bean>【第三步】在测试类中从IOC容器中获取连接池对象并打印public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource dataSource = (DataSource) ctx.getBean("dataSource"); System.out.println(dataSource); } }1.2 管理c3p0连接池【第一步】添加c3p0连接池依赖<dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency>【第二步】配置c3p0连接池Bean对象<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/> <property name="user" value="root"/> <property name="password" value="root"/> <property name="maxPoolSize" value="1000"/> </bean>注意：同一个Spring容器中不能有两个id="dataSource"的连接池。【第三步】在测试类中从IOC容器中获取连接池对象并打印public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource dataSource = (DataSource) ctx.getBean("dataSource"); System.out.println(dataSource); } }2 加载properties属性文件【重点】目的：将数据库的连接参数抽取到一个单独的文件中，与Spring配置文件解耦。问题导入问题1：如何解决使用EL表达式读取属性文件中的值结果读取到了系统属性问题？问题2：加载properties文件写法标准写法该怎么写？2.1 基本用法【第一步】编写jdbc.properties属性文件jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db jdbc.username=root jdbc.password=root【第二步】在applicationContext.xml中开启开启context命名空间，加载jdbc.properties属性文件[scode type="green" size=""]小技巧：如果同学们觉得上述复制粘贴方式不好改或者容易改错，其实idea是有提示功能的，注意不要选错就行了。有些版本的idea没有这个提示，那么就按照上面复制粘贴的方式改，改完之后可以做成live template模板，后期直接用。[/scode]<context:property-placeholder location="jdbc.properties"/>【第三步】在配置连接池Bean的地方使用EL表达式获取jdbc.properties属性文件中的值<bean class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>配置完成之后，运行之前的获取Druid连接池代码，可以获取到连接池对象就表示配置成功。2.2 配置不加载系统属性问题如果属性文件中配置的不是jdbc.username，而是username=root666，那么使用${username}获取到的不是root666，而是计算机的名称。原因系统属性的优先级比我们属性文件中的高，替换了我们的username=root666。解决解决1：换一个名称，例如不叫username，叫jdbc.username。解决2：使用system-properties-mode="NEVER"属性表示不使用系统属性。<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>2.3 加载properties文件写法不加载系统属性<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>加载多个properties文件<context:property-placeholder location="jdbc.properties,msg.properties"/>加载所有properties文件<context:property-placeholder location="*.properties"/>加载properties文件[font color="#FF0000"]标准格式[/font]<context:property-placeholder location="classpath:*.properties"/>加载properties文件标准格式<context:property-placeholder location="classpath*:*.properties"/>二、Spring容器1 Spring核心容器介绍问题导入问题：按照Bean名称获取Bean有什么弊端，按照Bean类型获取Bean有什么弊端？1.1 创建容器方式一：类路径加载配置文件ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");方式二：文件路径加载配置文件ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");加载多个配置文件ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xml");1.2 获取bean对象方式一：使用bean名称获取弊端：需要自己强制类型转换BookDao bookDao = (BookDao) ctx.getBean("bookDao");[font color="#FF0000"]方式二：使用bean名称获取并指定类型（推荐使用）[/font]BookDao bookDao = ctx.getBean("bookDao", BookDao.class);方式三：使用bean类型获取弊端：如果IOC容器中同类型的Bean对象有多个，此处获取会报错BookDao bookDao = ctx.getBean(BookDao.class);1.3 容器类层次结构1.4 BeanFactory类路径加载配置文件Resource resources = new ClassPathResource("applicationContext.xml"); BeanFactory bf = new XmlBeanFactory(resources); BookDao bookDao = bf.getBean("bookDao", BookDao.class); bookDao.save();BeanFactory创建完毕后，所有的Bean均为延迟加载，也就是说我们调用getBean()方法获取Bean对象时才创建Bean对象并返回给我们2 Spring核心容器总结2.1 容器相关BeanFactory是IoC容器的顶层接口，初始化BeanFactory对象时，加载的bean延迟加载ApplicationContext接口是Spring容器的核心接口，初始化时bean立即加载ApplicationContext接口提供基础的bean操作相关方法，通过其他接口扩展其功能ApplicationContext接口常用初始化类[font color="#FF0000"]ClassPathXmlApplicationContext(常用)[/font]FileSystemXmlApplicationContext2.2 bean相关2.3 依赖注入相关三、Spring注解开发1 注解开发定义Bean对象【重点】目的：xml配置Bean对象有些繁琐，使用注解简化Bean对象的定义问题导入问题1：使用什么标签进行Spring注解包扫描？问题2：@Component注解和@Controller、@Service、@Repository三个衍生注解有什么区别？1.1 基本使用【第一步】在applicationContext.xml中开启Spring注解包扫描<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> <context:component-scan base-package="cn.fivk"/> </beans>【第二步】在类上使用@Component注解定义Bean。//@Component定义bean @Component("bookDao") public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } } @Component public class BookServiceImpl implements BookService { private BookDao bookDao; public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } public void save() { System.out.println("book service save ..."); bookDao.save(); } }补充说明：如果@Component注解没有使用参数指定Bean的名称，那么类名首字母小写就是Bean在IOC容器中的默认名称。例如：BookServiceImpl对象在IOC容器中的名称是bookServiceImpl。【第三步】在测试类中获取Bean对象public class AppForAnnotation { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); System.out.println(bookDao); //按类型获取bean BookService bookService = ctx.getBean(BookService.class); System.out.println(bookService); } }注意：在测试类中不要调用bookService的save方法，因为还没有给BookServiceImpl中的bookDao赋值，调用bookService的save方法会出现空指针异常。运行结果1.2 @Component三个衍生注解说明：加粗的注解为常用注解Spring提供@Component注解的三个衍生注解@Controller：用于表现层bean定义@Service：用于业务层bean定义@Repository：用于数据层bean定义@Repository("bookDao") public class BookDaoImpl implements BookDao { } @Service public class BookServiceImpl implements BookService { } 2 纯注解开发模式【重点】问题导入问题1：配置类上使用什么注解表示该类是一个配置类？问题2：配置类上使用什么注解进行Spring注解包扫描？2.1 纯注解开发模式介绍Spring3.0开启了纯注解开发模式，使用Java类替代配置文件，开启了Spring快速开发赛道Java类代替Spring核心配置文件@Configuration注解用于设定当前类为配置类@ComponentScan注解用于设定扫描路径，此注解只能添加一次，多个数据请用数组格式@ComponentScan({cn.fivk.service","cn.fivk.dao"})读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象//加载配置文件初始化容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //加载配置类初始化容器 ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);2.2 代码演示【第一步】定义配置类代替配置文件//声明当前类为Spring配置类 @Configuration //Spring注解扫描，相当于<context:component-scan base-package="cn.fivk"/> @ComponentScan("cn.fivk") //设置bean扫描路径，多个路径书写为字符串数组格式 //@ComponentScan({"cn.fivk.service","cn.fivk.dao"}) public class SpringConfig { }【第二步】在测试类中加载配置类，获取Bean对象并使用public class AppForAnnotation { public static void main(String[] args) { //AnnotationConfigApplicationContext加载Spring配置类初始化Spring容器 ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); System.out.println(bookDao); //按类型获取bean BookService bookService = ctx.getBean(BookService.class); System.out.println(bookService); } }3 注解开发Bean作用范围和生命周期管理问题导入在类上使用什么注解定义Bean的作用范围？3.1 bean作用范围注解配置使用@Scope定义bean作用范围@Repository @Scope("singleton") public class BookDaoImpl implements BookDao { }3.2 bean生命周期注解配置使用@PostConstruct、@PreDestroy定义bean生命周期@Repository @Scope("singleton") public class BookDaoImpl implements BookDao { public BookDaoImpl() { System.out.println("book dao constructor ..."); } @PostConstruct public void init(){ System.out.println("book init ..."); } @PreDestroy public void destroy(){ System.out.println("book destory ..."); } }[scode type="red" size=""]注意：@PostConstruct和@PreDestroy注解是jdk中提供的注解，从jdk9开始，jdk中的javax.annotation包被移除了，也就是说这两个注解就用不了了，可以额外导入一下依赖解决这个问题。[/scode]<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>4 注解开发依赖注入【重点】问题导入问题1：请描述@Autowired注解是如何进行自动装配的？问题2：请描述@Qualifier注解的作用4.1 使用@Autowired注解开启自动装配模式（按类型）@Service public class BookServiceImpl implements BookService { //@Autowired：注入引用类型，自动装配模式，默认按类型装配 @Autowired private BookDao bookDao; public void save() { System.out.println("book service save ..."); bookDao.save(); } }说明：不管是使用配置文件还是配置类，都必须进行对应的Spring注解包扫描才可以使用。@Autowired默认按照类型自动装配，如果IOC容器中同类的Bean有多个，那么默认按照变量名和Bean的名称匹配，建议使用@Qualifier注解指定要装配的bean名称[scode type="red" size=""]注意：自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据，因此无需提供setter方法。[/scode]4.2 使用@Qualifier注解指定要装配的bean名称目的：解决IOC容器中同类型Bean有多个装配哪一个的问题@Service public class BookServiceImpl implements BookService { //@Autowired：注入引用类型，自动装配模式，默认按类型装配 @Autowired //@Qualifier：自动装配bean时按bean名称装配 @Qualifier("bookDao") private BookDao bookDao; public void save() { System.out.println("book service save ..."); bookDao.save(); } }[scode type="red" size=""]注意：@Qualifier注解无法单独使用，必须配合@Autowired注解使用[/scode]4.3 使用@Value实现简单类型注入@Repository("bookDao") public class BookDaoImpl implements BookDao { //@Value：注入简单类型（无需提供set方法） @Value("${name}") private String name; public void save() { System.out.println("book dao save ..." + name); } }以上@Value注解中使用${name}从属性文件中读取name值，那么就需要在配置类或者配置文件中加载属性文件。@Configuration @ComponentScan("cn.fivk") //@PropertySource加载properties配置文件 @PropertySource({"classpath:jdbc.properties"}) //{}可以省略不写 public class SpringConfig { }[scode type="red" size=""]注意：@PropertySource()中加载多文件请使用数组格式配置，不允许使用通配符[/scode]5 注解开发管理第三方Bean【重点】问题导入导入自己定义的配置类有几种方式？【第一步】单独定义配置类public class JdbcConfig { //@Bean：表示当前方法的返回值是一个bean对象，添加到IOC容器中 @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("root"); return ds; } }【第二步】将独立的配置类加入核心配置方式1：@Import注解导入式@Configuration @ComponentScan("cn.fivk") //@Import:导入配置信息 @Import({JdbcConfig.class}) public class SpringConfig { }方式2：@ComponentScan扫描式@Configuration @ComponentScan({"cn.fivk.config","cn.fivk.service","cn.fivk.dao"}) //只要cn.fivk.config包扫到了就行，三个包可以合并写成cn.fivk public class SpringConfig { }6 注解开发为第三方Bean注入资源【重点】问题导入配置类中如何注入简单类型数据，如何注入引用类型数据？6.1 简单类型依赖注入public class JdbcConfig { //1.定义一个方法获得要管理的对象 @Value("com.mysql.jdbc.Driver") private String driver; @Value("jdbc:mysql://localhost:3306/spring_db") private String url; @Value("root") private String userName; @Value("root") private String password; //2.@Bean：表示当前方法的返回值是一个bean对象，添加到IOC容器中 @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } }说明：如果@Value()中使用了EL表达式读取properties属性文件中的内容，那么就需要加载properties属性文件。6.2 引用类型依赖注入//Spring会自动从IOC容器中找到BookDao对象赋值给参数bookDao变量，如果没有就会报错。 @Bean public DataSource dataSource(BookDao bookDao){ System.out.println(bookDao); DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; }说明：引用类型注入只需要为bean定义方法设置形参即可，容器会根据类型自动装配对象7 注解开发总结四、Spring整合其他技术【重点】1 Spring整合mybatis【重点】1.1 思路分析问题导入mybatis进行数据层操作的核心对象是谁？1.1.1 MyBatis程序核心对象分析1.1.2 整合MyBatis使用SqlSessionFactoryBean封装SqlSessionFactory需要的环境信息使用MapperScannerConfigurer加载Dao接口，创建代理对象保存到IOC容器中1.2 代码实现问题导入问题1：Spring整合mybatis的依赖叫什么？问题2：Spring整合mybatis需要管理配置哪两个Bean，这两个Bean作用分别是什么？【前置工作】在pom.xml中添加spring-context、druid、mybatis、mysql-connector-java等基础依赖。准备service和dao层基础代码public interface AccountService { void save(Account account); void delete(Integer id); void update(Account account); List<Account> findAll(); Account findById(Integer id); } @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; public void save(Account account) { accountDao.save(account); } public void update(Account account){ accountDao.update(account); } public void delete(Integer id) { accountDao.delete(id); } public Account findById(Integer id) { return accountDao.findById(id); } public List<Account> findAll() { return accountDao.findAll(); } } public interface AccountDao { @Insert("insert into tbl_account(name,money)values(#{name},#{money})") void save(Account account); @Delete("delete from tbl_account where id = #{id} ") void delete(Integer id); @Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ") void update(Account account); @Select("select * from tbl_account") List<Account> findAll(); @Select("select * from tbl_account where id = #{id} ") Account findById(Integer id); }【第一步】导入Spring整合Mybatis依赖<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>【第二步】创建JdbcConfig配置DataSource数据源jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false jdbc.username=root jdbc.password=rootpublic class JdbcConfig { @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 public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } }【第三步】创建MybatisConfig整合mybatispublic class MybatisConfig { //定义bean，SqlSessionFactoryBean，用于产生SqlSessionFactory对象 @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){ SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean(); ssfb.setTypeAliasesPackage("cn.fivk.domain"); ssfb.setDataSource(dataSource); return ssfb; } //定义bean，返回MapperScannerConfigurer对象 @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("cn.fivk.dao"); return msc; } }【第四步】创建SpringConfig主配置类进行包扫描和加载其他配置类@Configuration @ComponentScan("cn.fivk") //@PropertySource：加载类路径jdbc.properties文件 @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) public class SpringConfig { }【第五步】定义测试类进行测试public class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); AccountService accountService = ctx.getBean(AccountService.class); Account ac = accountService.findById(1); System.out.println(ac); } }2 Spring整合Junit单元测试【重点】问题导入Spring整合Junit的两个注解作用分别是什么？【第一步】导入整合的依赖坐标spring-test <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.9.RELEASE</version> </dependency>【第二步】使用Spring整合Junit专用的类加载器【第三步】加载配置文件或者配置类//【第二步】使用Spring整合Junit专用的类加载器 @RunWith(SpringJUnit4ClassRunner.class) //【第三步】加载配置文件或者配置类 @ContextConfiguration(classes = {SpringConfiguration.class}) //加载配置类 //@ContextConfiguration(locations={"classpath:applicationContext.xml"})//加载配置文件 public class AccountServiceTest { //支持自动装配注入bean @Autowired private AccountService accountService; @Test public void testFindById(){ System.out.println(accountService.findById(1)); } @Test public void testFindAll(){ System.out.println(accountService.findAll()); } }[scode type="red" size=""]注意：junit的依赖至少要是4.12版本,可以是4.13等版本,否则出现如下异常：[/scode]
学习目标能够说出Spring的体系结构能够编写IOC入门案例能够编写DI入门案例能够配置setter方式注入属性值能够配置构造方式注入属性值能够理解什么是自动装配一、Spring简介1 Spring介绍问题导入我们为什么要学习Spring框架？1.1 为什么要学Spring技术是JavaEE开发必备技能，企业开发技术选型命中率>90%专业角度**简化开发**，降低企业级开发的复杂性框架整合，高效整合其他技术，提高企业级应用开发与运行效率1.2 学什么简化开发IOC(反转控制)AOP(面向切面编程)事务处理框架整合MyBatisMyBatis-plusStrutsStruts2Hibernate……1.3 怎么学学习Spring框架设计思想学习基础操作，思考操作与思想间的联系学习案例，熟练应用操作的同时，体会思想2 初识Spring问题导入目前我们使用的是Spring几版本？2.1 Spring家族官网：https://spring.ioSpring发展到今天已经形成了一种开发的生态圈，Spring提供了若干个项目，每个项目用于完成特定的功能。2.2 Spring发展史3 Spring体系结构问题导入通过系统架构图，Spring能不能进行数据层开发？Spring能不能进行web层开发？3.1 Spring Framework系统架构图Spring Framework是Spring生态圈中最基础的项目，是其他项目的根基3.2 Spring Framework课程学习路线4 Spring核心概念问题导入问题1：目前我们的代码存在什么问题以及怎么解决这些问题？问题2：请描述什么是IOC，什么是DI？4.1 目前我们代码存在的问题代码书写现状耦合度偏高解决方案使用对象时，在程序中不要主动使用new产生对象，转换为由外部提供对象4.2 核心概念IOC（Inversion of Control）控制反转使用对象时，由主动new产生对象转换为由外部提供对象，此过程中对象创建控制权由程序转移到外部，此思想称为控制反转。通俗的讲就是“将new对象的权利交给Spring，我们从Spring中获取对象使用即可”Spring技术对IoC思想进行了实现Spring提供了一个容器，称为IOC容器，用来充当IoC思想中的“外部”IOC容器负责对象的创建、初始化等一系列工作，被创建或被管理的对象在IoC容器中统称为BeanDI（Dependency Injection）依赖注入在容器中建立bean与bean之间的依赖关系的整个过程，称为依赖注入。目标：充分解耦使用IoC容器管理bean（IOC)在IoC容器内将有依赖关系的bean进行关系绑定（DI）最终效果使用对象时不仅可以直接从IoC容器中获取，并且获取到的bean已经绑定了所有的依赖关系二、IOC和DI入门案例【重点】1 IOC入门案例【重点】问题导入\<bean>标签中id属性和class属性的作用是什么？1.1 门案例思路分析管理什么？(Service与Dao)如何将被管理的对象告知IOC容器？(配置文件)被管理的对象交给IOC容器，如何获取到IoC容器？(接口)IOC容器得到后，如何从容器中获取bean？(接口方法)使用Spring导入哪些坐标？(pom.xml)1.2 实现步骤【第一步】导入Spring坐标 【第二步】定义Spring管理的类（接口） 【第三步】创建Spring配置文件，配置对应类作为Spring管理的bean对象 【第四步】初始化IOC容器（Spring核心容器/Spring容器），通过容器获取bean对象1.3 实现代码【第一步】导入Spring坐标<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies>【第二步】定义Spring管理的类（接口）BookDao接口和BookDaoImpl实现类public interface BookDao { public void save(); } public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } }BookService接口和BookServiceImpl实现类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配置文件，配置对应类作为Spring管理的bean对象定义applicationContext.xml配置文件并配置BookServiceImpl<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bookService" class="cn.fivk.service.impl.BookServiceImpl"></bean> </beans>注意事项：bean定义时id属性在同一个上下文中(IOC容器中)不能重复【第四步】初始化IOC容器（Spring核心容器/Spring容器），通过容器获取Bean对象public class App { public static void main(String[] args) { //1.创建IoC容器对象，加载spring核心配置文件 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //2 从IOC容器中获取Bean对象(BookService对象) BookService bookService= (BookService)ctx.getBean("bookService"); //3 调用Bean对象(BookService对象)的方法 bookService.save(); } }1.4 运行结果2 DI入门案例【重点】问题导入\<property>标签中name属性和ref属性的作用是什么？2.1 DI入门案例思路分析基于IOC管理beanService中使用new形式创建的Dao对象是否保留？(否)Service中需要的Dao对象如何进入到Service中？(提供方法)Service与Dao间的关系如何描述？(配置)2.2 实现步骤【第一步】删除使用new的形式创建对象的代码 【第二步】提供依赖对象对应的setter方法 【第三步】配置service与dao之间的关系2.3 实现代码【第一步】删除使用new的形式创建对象的代码public class BookServiceImpl implements BookService { private BookDao bookDao; //【第一步】删除使用new的形式创建对象的代码 public void save() { System.out.println("book service save ..."); bookDao.save(); } }【第二步】提供依赖对象对应的setter方法public class BookServiceImpl implements BookService { private BookDao bookDao; public void save() { System.out.println("book service save ..."); bookDao.save(); } //【第二步】提供依赖对象对应的setter方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }【第三步】配置service与dao之间的关系在applicationContext.xml中配置<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bookDao" class="cn.fivk.dao.impl.BookDaoImpl"/> <bean id="bookService" class="cn.fivk.service.impl.BookServiceImpl"> <property name="bookDao" ref="bookDao"/> </bean> </beans>2.4 图解演示三、Bean的基础配置问题导入问题1：在\<bean>标签上如何配置别名？问题2：Bean的默认作用范围是什么？如何修改？1 Bean基础配置【重点】配置说明代码演示见《IOC入门案例》applicationContext.xml配置运行结果见《IOC入门案例》运行结果2 Bean别名配置配置说明代码演示打印结果3 Bean作用范围配置【重点】配置说明扩展：scope的取值不仅仅只有singleton和prototype，还有request、session、application、 websocket ，表示创建出的对象放置在web容器(tomcat)对应的位置。比如：request表示保存到request域中。代码演示打印结果最后给大家说明一下：在我们的实际开发当中，绝大部分的Bean是单例的，也就是说绝大部分Bean不需要配置scope属性。四、Bean的实例化问题导入Bean的实例化方式有几种？1 Bean是如何创建的【理解】bean本质上就是对象，创建bean使用构造方法完成2 实例化Bean的三种方式2.1 构造方法方式【重点】BookDaoImpl实现类public class BookDaoImpl implements BookDao { public BookDaoImpl() { System.out.println("book dao constructor is running ...."); } public void save() { System.out.println("book dao save ..."); } }applicationContext.xml配置 <bean id="bookDao" class="cn.fivk.dao.impl.BookDaoImpl"/>AppForInstanceBook测试类public class AppForInstanceBook { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); } }运行结果注意：无参构造方法如果不存在，将抛出异常BeanCreationException``2.2 静态工厂方式OrderDao接口和OrderDaoImpl实现类public interface OrderDao { public void save(); } public class OrderDaoImpl implements OrderDao { public void save() { System.out.println("order dao save ..."); } }OrderDaoFatory工厂类//静态工厂创建对象 public class OrderDaoFactory { public static OrderDao getOrderDao(){ System.out.println("factory setup...."); return new OrderDaoImpl(); } }applicationContext.xml配置 <bean id="orderDao" class="cn.fivk.factory.OrderDaoFactory" factory-method="getOrderDao"/>AppForInstanceOrder测试类public class AppForInstanceOrder { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); OrderDao orderDao = (OrderDao) ctx.getBean("orderDao"); orderDao.save(); } }运行结果2.3 实例工厂方式UserDao接口和UserDaoImpl实现类public interface UserDao { public void save(); } public class UserDaoImpl implements UserDao { public void save() { System.out.println("user dao save ..."); } }UserDaoFactory工厂类//实例工厂创建对象 public class UserDaoFactory { public UserDao getUserDao(){ return new UserDaoImpl(); } }applicationContext.xml配置 <bean id="userFactory" class="cn.fivk.factory.UserDaoFactory"/> <bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>AppForInstanceUser测试类public class AppForInstanceUser { public static void main(String[] args) { // //创建实例工厂对象 // UserDaoFactory userDaoFactory = new UserDaoFactory(); // //通过实例工厂对象创建对象 // UserDao userDao = userDaoFactory.getUserDao(); // userDao.save(); ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao) ctx.getBean("userDao"); userDao.save(); } }运行结果2.4 实现FactoryBean\<T>方式【扩展,了解】定义UserDaoFactoryBean实现FactoryBean\<UserDao>UserDaoFactoryBean中实例化什么类型的对象泛型就是该类型。//FactoryBean创建对象 public class UserDaoFactoryBean implements FactoryBean<UserDao> { //代替原始实例工厂中创建对象的方法 public UserDao getObject() throws Exception { return new UserDaoImpl(); } public Class<?> getObjectType() { return UserDao.class; } }applicationContext.xml配置 <bean id="userDao" class="cn.fivk.factory.UserDaoFactoryBean"/>使用之前的AppForInstanceUser测试类去运行看结果就行了。注意配置文件中id="userDao"是否重复。五、Bean的生命周期【了解】问题导入问题1：多例的Bean能够配置并执行销毁的方法？问题2：如何做才执行Bean销毁的方法？1 生命周期相关概念介绍生命周期：从创建到消亡的完整过程bean生命周期：bean从创建到销毁的整体过程bean生命周期控制：在bean创建后到销毁前做一些事情2 代码演示2.1 Bean生命周期控制提供生命周期控制方法public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } //表示bean初始化对应的操作 public void init(){ System.out.println("init..."); } //表示bean销毁前对应的操作 public void destory(){ System.out.println("destory..."); } }applicationContext.xml配置 <bean id="bookDao" class="cn.fivk.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>测试类public class AppForLifeCycle { public static void main( String[] args ) { //此处需要使用实现类类型，接口类型没有close方法 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); //关闭容器，执行销毁的方法 ctx.close(); } }2.2 Bean生命周期控制实现InitializingBean, DisposableBean接口public class BookServiceImpl implements BookService, InitializingBean, DisposableBean { private BookDao bookDao; public void setBookDao(BookDao bookDao) { System.out.println("set ....."); this.bookDao = bookDao; } public void save() { System.out.println("book service save ..."); bookDao.save(); } public void destroy() throws Exception { System.out.println("service destroy"); } public void afterPropertiesSet() throws Exception { System.out.println("service init"); } }测试类代码同《3.2.1 Bean生命周期控制》中的测试代码3 Bean销毁时机容器关闭前触发bean的销毁关闭容器方式：手工关闭容器ConfigurableApplicationContext接口close()操作注册关闭钩子，在虚拟机退出前先关闭容器再退出虚拟机ConfigurableApplicationContext接口registerShutdownHook()操作public class AppForLifeCycle { public static void main( String[] args ) { //此处需要使用实现类类型，接口类型没有close方法 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); //注册关闭钩子函数，在虚拟机退出之前回调此函数，关闭容器 ctx.registerShutdownHook(); //关闭容器 //ctx.close(); } }六、依赖注入(DI配置)1 依赖注入方式【重点】问题导入依赖注入有几种方式？1.1 依赖注入的两种方式setter注入简单类型**引用类型(很常用)**构造器注入简单类型引用类型1.2 setter方式注入问题导入setter方式注入使用什么子标签？引用类型简单类型1.3 构造方式注入问题导入构造方式注入使用什么子标签？引用类型简单类型参数适配【了解】1.4 依赖注入方式选择强制依赖使用构造器进行，使用setter注入有概率不进行注入导致null对象出现可选依赖使用setter注入进行，灵活性强Spring框架倡导使用构造器，第三方框架内部大多数采用构造器注入的形式进行数据初始化，相对严谨如果有必要可以两者同时使用，使用构造器注入完成强制依赖的注入，使用setter注入完成可选依赖的注入实际开发过程中还要根据实际情况分析，如果受控对象没有提供setter方法就必须使用构造器注入自己开发的模块推荐使用setter注入2 依赖自动装配【理解】问题导入如何配置按照类型自动装配？2.1 自动装配概念IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配自动装配方式按类型（常用）按名称按构造方法不启用自动装配2.2 自动装配类型依赖自动装配配置中使用bean标签autowire属性设置自动装配的类型<bean id="bookDao" class="cn.fivk.dao.impl.BookDaoImpl"/> <bean id="bookService" class="cn.fivk.service.impl.BookServiceImpl" autowire="byType"/>依赖自动装配特征自动装配用于引用类型依赖注入，不能对简单类型进行操作使用按类型装配时（byType）必须保障容器中相同类型的bean唯一，推荐使用使用按名称装配时（byName）必须保障容器中具有指定名称的bean，因变量名与配置耦合，不推荐使用自动装配优先级低于setter注入与构造器注入，同时出现时自动装配配置失效3 集合注入3.1 注入数组类型数据<property name="array"> <array> <value>100</value> <value>200</value> <value>300</value> </array> </property>3.2 注入List类型数据<property name="list"> <value>itcast</value> <value>itheima</value> <value>boxuegu</value> <value>chuanzhihui</value> </property>3.3 注入Set类型数据<property name="set"> <set> <value>itcast</value> <value>itheima</value> <value>boxuegu</value> <value>boxuegu</value> </set> </property>3.4 注入Map类型数据<property name="map"> <entry key="country" value="china"/> <entry key="province" value="henan"/> <entry key="city" value="kaifeng"/> </property>3.5 注入Properties类型数据<property name="properties"> <props> <prop key="country">china</prop> <prop key="province">henan</prop> <prop key="city">kaifeng</prop> </props> </property>说明：property标签表示setter方式注入，构造方式注入constructor-arg标签内部也可以写\<array>、\ 、\<set>、\ 、\<props>标签