1.简介

1.1.简介

Spring : 春天 —>给软件行业带来了春天
2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术depend-on
官网 : http://spring.io/
官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/
GitHub : https://github.com/spring-projects

  1. <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-webmvc</artifactId>
  5. <version>5.2.0.RELEASE</version>
  6. </dependency>
  7. <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
  8. <dependency>
  9. <groupId>org.springframework</groupId>
  10. <artifactId>spring-jdbc</artifactId>
  11. <version>5.2.0.RELEASE</version>
  12. </dependency>

1.2.优点

1、Spring是一个开源免费的框架 , 容器 .
2、Spring是一个轻量级的框架 , 非侵入式的 .
3、控制反转 IoC , 面向切面 Aop
4、对事物的支持 , 对框架的支持
5、Spring 特点

  • 方便解耦,简化开发
  • Aop 编程支持
  • 方便程序测试
  • 方便和其他框架进行整合
  • 方便进行事务操作
  • 降低 API 开发难度


一句话概括:
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。

1.3.组成

image.png
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 。
image.png
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

    1.4.扩展

    Spring Boot与Spring Cloud

  • Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;

  • Spring Cloud是基于Spring Boot实现的;
  • Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;
  • Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
  • SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。

image.png

2.IOC理论推导

IoC基础
新建一个空白的maven项目

2.1.分析实现

我们先用我们原来的方式写一段代码 .
1、先写一个UserDao接口

  1. public interface UserDao {
  2. public void getUser();
  3. }

2、再去写Dao的实现类

  1. public class UserDaoImpl implements UserDao {
  2. @Override
  3. public void getUser() {
  4. System.out.println("获取用户数据");
  5. }
  6. }

3、然后去写UserService的接口

  1. public interface UserService {
  2. public void getUser();
  3. }

4、最后写Service的实现类

  1. public class UserServiceImpl implements UserService {
  2. private UserDao userDao = new UserDaoImpl();
  3. @Override
  4. public void getUser() {
  5. userDao.getUser();
  6. }
  7. }

5、测试一下

  1. @Test
  2. public void test(){
  3. UserService service = new UserServiceImpl();
  4. service.getUser();
  5. }

这是我们原来的方式 , 开始大家也都是这么去写的对吧 . 那我们现在修改一下
把Userdao的实现类增加一个 .

  1. public class UserDaoMySqlImpl implements UserDao {
  2. @Override
  3. public void getUser() {
  4. System.out.println("MySql获取用户数据");
  5. }
  6. }

紧接着我们要去使用MySql的话 , 我们就需要去service实现类里面修改对应的实现

  1. public class UserServiceImpl implements UserService {
  2. private UserDao userDao = new UserDaoMySqlImpl();
  3. @Override
  4. public void getUser() {
  5. userDao.getUser();
  6. }
  7. }

在假设, 我们再增加一个Userdao的实现类 .

  1. public class UserDaoOracleImpl implements UserDao {
  2. @Override
  3. public void getUser() {
  4. System.out.println("Oracle获取用户数据");
  5. }
  6. }

那么我们要使用Oracle , 又需要去service实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了, 甚至反人类对吧 , 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身。
那我们如何去解决呢 ?
我们可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 我们去代码里修改下 .

  1. public class UserServiceImpl implements UserService {
  2. private UserDao userDao;
  3. // 利用set实现
  4. public void setUserDao(UserDao userDao) {
  5. this.userDao = userDao;
  6. }
  7. @Override
  8. public void getUser() {
  9. userDao.getUser();
  10. }
  11. }

现在去我们的测试类里 , 进行测试 ;

  1. @Test
  2. public void test(){
  3. UserServiceImpl service = new UserServiceImpl();
  4. service.setUserDao( new UserDaoMySqlImpl() );
  5. service.getUser();
  6. //那我们现在又想用Oracle去实现呢
  7. service.setUserDao( new UserDaoOracleImpl() );
  8. service.getUser();
  9. }

大家发现了区别没有 ? 可能很多人说没啥区别 . 但是同学们 , 他们已经发生了根本性的变化 , 很多地方都不一样了 . 仔细去思考一下 , 以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口。
这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !

2.2.IOC本质

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
image.png
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
image.png
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

3.HelloSpring

导入Jar包

注 : spring 需要导入commons-logging进行日志记录 . 我们利用maven , 他会自动下载对应的依赖项 .

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-webmvc</artifactId>
  4. <version>5.1.10.RELEASE</version>
  5. </dependency>

编写代码

1、编写一个Hello实体类

  1. public class Hello {
  2. private String name;
  3. public String getName() {
  4. return name;
  5. }
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. public void show(){
  10. System.out.println("Hello,"+ name );
  11. }
  12. }

2、编写我们的spring文件 , 这里我们命名为beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <!--bean就是java对象 , 由Spring创建和管理-->
  7. <bean id="hello" class="com.kuang.pojo.Hello">
  8. <property name="name" value="Spring"/>
  9. </bean>
  10. </beans>

3、我们可以去进行测试了 .

  1. @Test
  2. public void test(){
  3. //解析beans.xml文件 , 生成管理相应的Bean对象
  4. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  5. //getBean : 参数即为spring配置文件中bean的id .
  6. Hello hello = (Hello) context.getBean("hello");
  7. hello.show();
  8. }

思考

  • Hello 对象是谁创建的 ? hello 对象是由Spring创建的
  • Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的

这个过程就叫控制反转 :

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象

依赖注入 : 就是利用set方法来进行注入的
IOC是一种编程思想,由主动的编程变成被动的接收
可以通过newClassPathXmlApplicationContext去浏览一下底层源码 .

修改案例一

我们在案例一中, 新增一个Spring配置文件beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="MysqlImpl" class="com.kuang.dao.impl.UserDaoMySqlImpl"/>
  7. <bean id="OracleImpl" class="com.kuang.dao.impl.UserDaoOracleImpl"/>
  8. <bean id="ServiceImpl" class="com.kuang.service.impl.UserServiceImpl">
  9. <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写-->
  10. <!--引用另外一个bean , 不是用value 而是用 ref-->
  11. <property name="userDao" ref="OracleImpl"/>
  12. </bean>
  13. </beans>

测试!

  1. @Test
  2. public void test2(){
  3. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  4. UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
  5. serviceImpl.getUser();
  6. }

OK , 到了现在 , 我们彻底不用再程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !

4.IOC创建对象方式

4.1.通过无参构造方法来创建

1、User.java

  1. public class User {
  2. private String name;
  3. public User() {
  4. System.out.println("user无参构造方法");
  5. }
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. public void show(){
  10. System.out.println("name="+ name );
  11. }
  12. }

2、beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="user" class="com.kuang.pojo.User">
  7. <property name="name" value="kuangshen"/>
  8. </bean>
  9. </beans>

3、测试类

  1. @Test
  2. public void test(){
  3. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  4. //在执行getBean的时候, user已经创建好了 , 通过无参构造
  5. User user = (User) context.getBean("user");
  6. //调用对象的方法 .
  7. user.show();
  8. }

结果可以发现,在调用show方法之前,User对象已经通过无参构造初始化了!

4.2.通过有参构造方法来创建

1、UserT . java

  1. public class UserT {
  2. private String name;
  3. public UserT(String name) {
  4. this.name = name;
  5. }
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. public void show(){
  10. System.out.println("name="+ name );
  11. }
  12. }

2、beans.xml 有三种方式编写

  1. <!-- 第一种根据index参数下标设置 -->
  2. <bean id="userT" class="com.kuang.pojo.UserT">
  3. <!-- index指构造方法 , 下标从0开始 -->
  4. <constructor-arg index="0" value="kuangshen2"/>
  5. </bean>
  6. <!-- 第二种根据参数名字设置 -->
  7. <bean id="userT" class="com.kuang.pojo.UserT">
  8. <!-- name指参数名 -->
  9. <constructor-arg name="name" value="kuangshen2"/>
  10. </bean>
  11. <!-- 第三种根据参数类型设置 -->
  12. <bean id="userT" class="com.kuang.pojo.UserT">
  13. <constructor-arg type="java.lang.String" value="kuangshen2"/>
  14. </bean>

3、测试

  1. @Test
  2. public void testT(){
  3. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  4. UserT user = (UserT) context.getBean("userT");
  5. user.show();
  6. }

结论:在配置文件加载的时候。其中管理的对象都已经初始化了!

4.3.p名称空间注入(了解)

1、添加 p 名称空间在配置文件中

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:p="http://www.springframework.org/schema/p"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">

2、进行属性注入,在 bean 标签里面进行操作

  1. <!-- set 方法注入属性 -->
  2. <bean id="book" class="com.atguigu.spring5.Book" p:bname="九阳神功" p:bauthor="无名氏">
  3. </bean>

5.Spring配置

5.1.别名

alias 设置别名 , 为bean设置别名 , 可以设置多个别名

  1. <!--设置别名:在获取Bean的时候可以使用别名获取-->
  2. <alias name="userT" alias="userNew"/>

5.2.Bean的配置

  1. <!--bean就是java对象,由Spring创建和管理-->
  2. <!--
  3. id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
  4. 如果配置id,又配置了name,那么name是别名
  5. name可以设置多个别名,可以用逗号,分号,空格隔开
  6. 如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
  7. class是bean的全限定名=包名+类名
  8. -->
  9. <bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
  10. <property name="name" value="Spring"/>
  11. </bean>

5.3.import

团队的合作通过import来实现 .

  1. <import resource="{path}/beans.xml"/>

5.4.外部属性文件

1、直接配置数据库信息
(1)配置德鲁伊连接池
(2)引入德鲁伊连接池依赖 jar 包
image.png

  1. <!--直接配置连接池-->
  2. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  3. <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
  4. <property name="url" value="jdbc:mysql://localhost:3306/userDb"></property>
  5. <property name="username" value="root"></property>
  6. <property name="password" value="root"></property>
  7. </bean>

2、引入外部属性文件配置数据库连接池
(1)创建外部属性文件,properties 格式文件,写数据库信息
image.png
(2)把外部 properties 属性文件引入到 spring 配置文件中

  1. <beans xmlns:context="http://www.springframework.org/schema/context"
  2. xsi:schemaLocation="http://www.springframework.org/schema/context
  3. http://www.springframework.org/schema/context/spring-context.xsd">
  4. <!--引入外部属性文件-->
  5. <context:property-placeholder location="classpath:jdbc.properties"/>
  6. <!--配置连接池-->
  7. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  8. <property name="driverClassName" value="${prop.driverClass}"></property>
  9. <property name="url" value="${prop.url}"></property>
  10. <property name="username" value="${prop.userName}"></property>
  11. <property name="password" value="${prop.password}"></property>
  12. </bean>

6.依赖注入(DI)

Dependency Injection

  • 依赖注入(Dependency Injection,DI)
  • 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源
  • 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配

    6.1.构造器注入

    我们在之前的案例已经讲过了

    6.2.Set 注入 (重点)

    要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is
    测试pojo类 :
    Address.java

    1. public class Address {
    2. private String address;
    3. public String getAddress() {
    4. return address;
    5. }
    6. public void setAddress(String address) {
    7. this.address = address;
    8. }
    9. }

    Student.java

    1. package com.kuang.pojo;
    2. import java.util.List;
    3. import java.util.Map;
    4. import java.util.Properties;
    5. import java.util.Set;
    6. public class Student {
    7. private String name;
    8. private Address address;
    9. private String[] books;
    10. private List<String> hobbys;
    11. private Map<String,String> card;
    12. private Set<String> games;
    13. private String wife;
    14. private Properties info;
    15. public void setName(String name) {
    16. this.name = name;
    17. }
    18. public void setAddress(Address address) {
    19. this.address = address;
    20. }
    21. public void setBooks(String[] books) {
    22. this.books = books;
    23. }
    24. public void setHobbys(List<String> hobbys) {
    25. this.hobbys = hobbys;
    26. }
    27. public void setCard(Map<String, String> card) {
    28. this.card = card;
    29. }
    30. public void setGames(Set<String> games) {
    31. this.games = games;
    32. }
    33. public void setWife(String wife) {
    34. this.wife = wife;
    35. }
    36. public void setInfo(Properties info) {
    37. this.info = info;
    38. }
    39. public void show(){
    40. System.out.println("name="+ name
    41. + ",address="+ address.getAddress()
    42. + ",books="
    43. );
    44. for (String book:books){
    45. System.out.print("<<"+book+">>\t");
    46. }
    47. System.out.println("\n爱好:"+hobbys);
    48. System.out.println("card:"+card);
    49. System.out.println("games:"+games);
    50. System.out.println("wife:"+wife);
    51. System.out.println("info:"+info);
    52. }
    53. }

    6.3.扩展的注入

    1、常量注入

    1. <bean id="student" class="com.kuang.pojo.Student">
    2. <property name="name" value="小明"/>
    3. </bean>

    测试:

    1. @Test
    2. public void test01(){
    3. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    4. Student student = (Student) context.getBean("student");
    5. System.out.println(student.getName());
    6. }

    2、Bean注入
    外部bean
    注意点:这里的值是一个引用,ref

    1. <bean id="addr" class="com.kuang.pojo.Address">
    2. <property name="address" value="重庆"/>
    3. </bean>
    4. <bean id="student" class="com.kuang.pojo.Student">
    5. <property name="name" value="小明"/>
    6. <!--级联赋值-->
    7. <property name="address" ref="addr"/>
    8. <property name="addr.address" value="上海"></property><!--需要生成addr的get方法-->
    9. </bean>

    内部bean

    1. <bean id="emp" class="com.atguigu.spring5.bean.Emp">
    2. <!--设置两个普通属性-->
    3. <property name="ename" value="lucy"></property>
    4. <property name="gender" value="女"></property>
    5. <!--设置对象类型属性-->
    6. <property name="dept">
    7. <bean id="dept" class="com.atguigu.spring5.bean.Dept">
    8. <property name="dname" value="安保部"></property>
    9. </bean>
    10. </property>
    11. </bean>

    3、数组注入

    1. <bean id="student" class="com.kuang.pojo.Student">
    2. <property name="name" value="小明"/>
    3. <property name="address" ref="addr"/>
    4. <property name="books">
    5. <array>
    6. <value>西游记</value>
    7. <value>红楼梦</value>
    8. <value>水浒传</value>
    9. </array>
    10. </property>
    11. </bean>

    4、List注入

    1. <property name="hobbys">
    2. <list>
    3. <value>听歌</value>
    4. <value>看电影</value>
    5. <value>爬山</value>
    6. </list>
    7. </property>

    在集合里面设置对象类型值

    1. <!--创建多个 course 对象-->
    2. <bean id="course1" class="com.atguigu.spring5.collectiontype.Course">
    3. <property name="cname" value="Spring5 框架"></property>
    4. </bean>
    5. <bean id="course2" class="com.atguigu.spring5.collectiontype.Course">
    6. <property name="cname" value="MyBatis 框架"></property>
    7. </bean>
    8. <bean ...>
    9. <!--注入 list 集合类型,值是对象-->
    10. <property name="courseList">
    11. <list>
    12. <ref bean="course1"></ref>
    13. <ref bean="course2"></ref>
    14. </list>
    15. </property>
    16. </bean>

    把集合注入部分提取出来
    (1)在 spring 配置文件中引入名称空间 util

    1. xmlns:util="http://www.springframework.org/schema/util"
    2. xsi:schemaLocation="http://www.springframework.org/schema/util
    3. http://www.springframework.org/schema/util/spring-util.xsd"

    (2)使用 util 标签完成 list 集合注入提取

    1. <!--1 提取 list 集合类型属性注入-->
    2. <util:list id="bookList">
    3. <value>易筋经</value>
    4. <value>九阴真经</value>
    5. <value>九阳神功</value>
    6. </util:list>
    7. <!--2 提取 list 集合类型属性注入使用-->
    8. <bean id="book" class="com.atguigu.spring5.collectiontype.Book">
    9. <property name="list" ref="bookList"></property>
    10. </bean>

    5、Map注入

    1. <property name="card">
    2. <map>
    3. <entry key="中国邮政" value="456456456465456"/>
    4. <entry key="建设" value="1456682255511"/>
    5. </map>
    6. </property>

    6、set注入

    1. <property name="games">
    2. <set>
    3. <value>LOL</value>
    4. <value>BOB</value>
    5. <value>COC</value>
    6. </set>
    7. </property>

    7、字面量注入
    Null注入

    1. <property name="wife"><null/></property>

    属性值包含特殊符号

    1. <!-- 属性值包含特殊符号
    2. 1 把<>进行转义 &lt; &gt;
    3. 2 把带特殊符号内容写到 CDATA -->
    4. <property name="address">
    5. <value><![CDATA[<<南京>>]]></value>
    6. </property>

    8、Properties注入

    1. <property name="info">
    2. <props>
    3. <prop key="学号">20190604</prop>
    4. <prop key="性别"></prop>
    5. <prop key="姓名">小明</prop>
    6. </props>
    7. </property>

    测试结果:
    image.png
    9、p命名和c命名注入
    User.java :【注意:这里没有有参构造器!】

    1. public class User {
    2. private String name;
    3. private int age;
    4. public void setName(String name) {
    5. this.name = name;
    6. }
    7. public void setAge(int age) {
    8. this.age = age;
    9. }
    10. @Override
    11. public String toString() {
    12. return "User{" +
    13. "name='" + name + '\'' +
    14. ", age=" + age +
    15. '}';
    16. }
    17. }

    P命名空间注入 : 需要在头文件中加入约束文件

    1. 导入约束 : xmlns:p="http://www.springframework.org/schema/p"
    2. <!--P(属性: properties)命名空间 , 直接注入属性-->
    3. <bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>

    c 命名空间注入 : 需要在头文件中加入约束文件

    1. 导入约束 : xmlns:c="http://www.springframework.org/schema/c"
    2. <!--C(构造: Constructor)命名空间 , 使用构造器注入-->
    3. <bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>

    发现问题:爆红了,刚才我们没有写有参构造!
    解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入!
    测试代码:

    1. @Test
    2. public void test02(){
    3. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    4. User user = (User) context.getBean("user");
    5. System.out.println(user);
    6. }

    6.4.工厂Bean

    1、Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)
    2、普通 bean:在配置文件中定义 bean 类型就是返回类型
    3、工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样
    第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean
    第二步 实现接口里面的方法,在实现的方法中定义返回的 bean 类型

    1. public class MyBean implements FactoryBean<Course> {
    2. //定义返回 bean
    3. @Override
    4. public Course getObject() throws Exception {
    5. Course course = new Course();
    6. course.setCname("abc");
    7. return course;
    8. }
    9. @Override
    10. public Class<?> getObjectType() {
    11. return null;
    12. }
    13. @Override
    14. public boolean isSingleton() {
    15. return false;
    16. }
    17. }
    1. <bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean">
    2. </bean>
    1. @Test
    2. public void test3() {
    3. ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
    4. Course course = context.getBean("myBean", Course.class);
    5. System.out.println(course);
    6. }

    6.5.Bean的作用域

    在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象。
    image.png
    几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

    Singleton(单例模式)

    当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:

    1. <bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">

    测试:

    1. @Test
    2. public void test03(){
    3. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    4. User user = (User) context.getBean("user");
    5. User user2 = (User) context.getBean("user");
    6. System.out.println(user == user2);
    7. }

    Prototype(原型模式)

    当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:

    1. <bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
    2. 或者
    3. <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>

    Request

    当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

    1. <bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>

    针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。

    Session

    当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

    1. <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

    针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

    application

    全局web应用级别的作用于,也是在web环境中使用的,一个web应用程序对应一个bean实例,通常情况下和singleton效果类似的,不过也有不一样的地方,singleton是每个spring容器中只有一个bean实例,一般我们的程序只有一个spring容器,但是,一个应用程序中可以创建多个spring容器,不同的容器中可以存在同名的bean,但是sope=aplication的时候,不管应用中有多少个spring容器,这个应用中同名的bean只有一个。

    1. <bean id="" class="" scope="application" />

    自定义

    第1步:实现Scope接口 ```java package org.springframework.beans.factory.config;

import org.springframework.beans.factory.ObjectFactory; import org.springframework.lang.Nullable;

public interface Scope {

  1. /**
  2. * 返回当前作用域中name对应的bean对象
  3. * name:需要检索的bean的名称
  4. * objectFactory:如果name对应的bean在当前作用域中没有找到,那么可以调用这个ObjectFactory来创建这个对象
  5. **/
  6. Object get(String name, ObjectFactory<?> objectFactory);
  7. /**
  8. * 将name对应的bean从当前作用域中移除
  9. **/
  10. @Nullable
  11. Object remove(String name);
  12. /**
  13. * 用于注册销毁回调,如果想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象
  14. */
  15. void registerDestructionCallback(String name, Runnable callback);
  16. /**
  17. * 用于解析相应的上下文数据,比如request作用域将返回request中的属性。
  18. */
  19. @Nullable
  20. Object resolveContextualObject(String key);
  21. /**
  22. * 作用域的会话标识,比如session作用域将是sessionId
  23. */
  24. @Nullable
  25. String getConversationId();

}

  1. 2步:将自定义的scope注册到容器<br />需要调用org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope的方法,看一下这个方法的声明
  2. ```java
  3. /**
  4. * 向容器中注册自定义的Scope
  5. *scopeName:作用域名称
  6. * scope:作用域对象
  7. **/
  8. void registerScope(String scopeName, Scope scope);

第3步:使用自定义的作用域
定义bean的时候,指定bean的scope属性为自定义的作用域名称。

6.6.Bean生命周期

1、生命周期
从对象创建到对象销毁的过程
2、bean 生命周期
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(4)bean 可以使用了(对象获取到了)
(5)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
3、演示 bean 生命周期

  1. public class Orders {
  2. //无参数构造
  3. public Orders() {
  4. System.out.println("第一步 执行无参数构造创建 bean 实例");
  5. }
  6. private String oname;
  7. public void setOname(String oname) {
  8. this.oname = oname;
  9. System.out.println("第二步 调用 set 方法设置属性值");
  10. }
  11. //创建执行的初始化的方法
  12. public void initMethod() {
  13. System.out.println("第三步 执行初始化的方法");
  14. }
  15. //创建执行的销毁的方法
  16. public void destroyMethod() {
  17. System.out.println("第五步 执行销毁的方法");
  18. }
  19. }
  1. <bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
  2. <property name="oname" value="手机"></property>
  3. </bean>
  1. @Test
  2. public void testBean3() {
  3. // ApplicationContext context =
  4. // new ClassPathXmlApplicationContext("bean4.xml");
  5. ClassPathXmlApplicationContext context =
  6. new ClassPathXmlApplicationContext("bean4.xml");
  7. Orders orders = context.getBean("orders", Orders.class);
  8. System.out.println("第四步 获取创建 bean 实例对象");
  9. System.out.println(orders);
  10. //手动让 bean 实例销毁
  11. context.close();
  12. }

4、bean 的后置处理器,bean 生命周期有七步
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
(6)bean 可以使用了(对象获取到了)
(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
5、演示添加后置处理器效果
创建类,实现接口 BeanPostProcessor,创建后置处理器

  1. public class MyBeanPost implements BeanPostProcessor {
  2. @Override
  3. public Object postProcessBeforeInitialization(Object bean, String beanName)
  4. throws BeansException {
  5. System.out.println("在初始化之前执行的方法");
  6. return bean;
  7. }
  8. @Override
  9. public Object postProcessAfterInitialization(Object bean, String beanName)
  10. throws BeansException {
  11. System.out.println("在初始化之后执行的方法");
  12. return bean;
  13. }
  14. }

配置后置处理器

  1. <!--配置后置处理器-->
  2. <bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>

7.Bean的自动装配

自动装配说明

  • 自动装配是使用spring满足bean依赖的一种方法
  • spring会在应用上下文中为某个bean寻找其依赖的bean。

Spring中bean有三种装配机制,分别是:

  1. 在xml中显式配置;
  2. 在java中显式配置;
  3. 隐式的bean发现机制和自动装配。

这里我们主要讲第三种:自动化的装配bean。
Spring的自动装配需要从两个角度来实现,或者说是两个操作:

  1. 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
  2. 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;

组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。
推荐不使用自动装配xml配置 , 而使用注解

测试环境搭建

1、新建一个项目
2、新建两个实体类,Cat Dog 都有一个叫的方法

  1. public class Cat {
  2. public void shout() {
  3. System.out.println("miao~");
  4. }
  5. }
  6. public class Dog {
  7. public void shout() {
  8. System.out.println("wang~");
  9. }
  10. }

3、新建一个用户类 User

  1. public class User {
  2. private Cat cat;
  3. private Dog dog;
  4. private String str;
  5. }

4、编写Spring配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="dog" class="com.kuang.pojo.Dog"/>
  7. <bean id="cat" class="com.kuang.pojo.Cat"/>
  8. <bean id="user" class="com.kuang.pojo.User">
  9. <property name="cat" ref="cat"/>
  10. <property name="dog" ref="dog"/>
  11. <property name="str" value="qinjiang"/>
  12. </bean>
  13. </beans>

5、测试

  1. public class MyTest {
  2. @Test
  3. public void testMethodAutowire() {
  4. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  5. User user = (User) context.getBean("user");
  6. user.getCat().shout();
  7. user.getDog().shout();
  8. }
  9. }

结果正常输出,环境OK

7.1.byName

autowire byName (按名称自动装配)
由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。
采用自动装配将避免这些错误,并且使配置简单化。
测试:
1、修改bean配置,增加一个属性 autowire=“byName”

  1. <bean id="user" class="com.kuang.pojo.User" autowire="byName">
  2. <property name="str" value="qinjiang"/>
  3. </bean>

2、再次测试,结果依旧成功输出!
3、我们将 cat 的bean id修改为 catXXX
4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。
小结:
当一个bean节点带有 autowire byName的属性时。

  1. 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
  2. 去spring容器中寻找是否有此字符串名称id的对象。
  3. 如果有,就取出注入;如果没有,就报空指针异常。

    7.2.byType

    autowire byType (按类型自动装配)
    使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
    1. NoUniqueBeanDefinitionException
    测试:
    1、将user的bean配置修改一下 : autowire=“byType”
    2、测试,正常输出
    3、在注册一个cat 的bean对象! ```xml

  1. 4、测试,报错:NoUniqueBeanDefinitionException<br />5、删掉cat2,将catbean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。<br />这就是按照类型自动装配!
  2. <a name="pcw06"></a>
  3. ### 7.3.使用注解
  4. jdk1.5开始支持注解,spring2.5开始全面支持注解。<br />准备工作:利用注解的方式注入属性。<br />1、在spring配置文件中引入context文件头
  5. ```xml
  6. xmlns:context="http://www.springframework.org/schema/context"
  7. http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context.xsd

2、开启属性注解支持!

  1. <context:annotation-config/>

@Autowired

  • @Autowired是按类型自动转配的,不支持id匹配。
  • 需要导入 spring-aop的包!

测试:
1、将User类中的set方法去掉,使用@Autowired注解

  1. public class User {
  2. @Autowired
  3. private Cat cat;
  4. @Autowired
  5. private Dog dog;
  6. private String str;
  7. public Cat getCat() {
  8. return cat;
  9. }
  10. public Dog getDog() {
  11. return dog;
  12. }
  13. public String getStr() {
  14. return str;
  15. }
  16. }

2、此时配置文件内容

  1. <context:annotation-config/>
  2. <bean id="dog" class="com.kuang.pojo.Dog"/>
  3. <bean id="cat" class="com.kuang.pojo.Cat"/>
  4. <bean id="user" class="com.kuang.pojo.User"/>

3、测试,成功输出结果!
【小狂神科普时间】
@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。

  1. //如果允许对象为null,设置required = false,默认为true
  2. @Autowired(required = false)
  3. private Cat cat;

@Qualifier

  • @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
  • @Qualifier不能单独使用。

测试实验步骤:
1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!

  1. <bean id="dog1" class="com.kuang.pojo.Dog"/>
  2. <bean id="dog2" class="com.kuang.pojo.Dog"/>
  3. <bean id="cat1" class="com.kuang.pojo.Cat"/>
  4. <bean id="cat2" class="com.kuang.pojo.Cat"/>

2、没有加Qualifier测试,直接报错
3、在属性上添加Qualifier注解

  1. @Autowired
  2. @Qualifier(value = "cat2")
  3. private Cat cat;
  4. @Autowired
  5. @Qualifier(value = "dog2")
  6. private Dog dog;

测试,成功输出!

@Resource

  • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
  • 其次再进行默认的byName方式进行装配;
  • 如果以上都不成功,则按byType的方式自动装配。
  • 都不成功,则报异常。

实体类:

  1. public class User {
  2. //如果允许对象为null,设置required = false,默认为true
  3. @Resource(name = "cat2")
  4. private Cat cat;
  5. @Resource
  6. private Dog dog;
  7. private String str;
  8. }

beans.xml

  1. <bean id="dog" class="com.kuang.pojo.Dog"/>
  2. <bean id="cat1" class="com.kuang.pojo.Cat"/>
  3. <bean id="cat2" class="com.kuang.pojo.Cat"/>
  4. <bean id="user" class="com.kuang.pojo.User"/>

测试:结果OK
配置文件2:beans.xml , 删掉cat2

  1. <bean id="dog" class="com.kuang.pojo.Dog"/>
  2. <bean id="cat1" class="com.kuang.pojo.Cat"/>

实体类上只保留注解

  1. @Resource
  2. private Cat cat;
  3. @Resource
  4. private Dog dog;

结果:OK
结论:先进行byName查找,失败;再进行byType查找,成功。
小结
@Autowired与@Resource异同:
1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。

8.使用注解开发

说明

什么是注解

  • 注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值..)
  • 使用注解,注解作用在类上面,方法上面,属性上面
  • 使用注解目的:简化 xml 配置

Spring 针对 Bean 管理中创建对象提供注解

  • @Component
  • @Service
  • @Controller
  • @Repository
  • 上面四个注解功能是一样的,都可以用来创建 bean 实例
    在spring4之后,想要使用注解形式,必须得要引入aop的包
    image.png
    在配置文件当中,还得要引入一个context约束 ```xml <?xml version=”1.0” encoding=”UTF-8”?>

  1. <a name="J3aR8"></a>
  2. ### 8.1.Bean的实现
  3. 我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
  4. <a name="xSJ1p"></a>
  5. #### 1、配置扫描哪些包下的注解
  6. ```xml
  7. <!--指定注解扫描包
  8. 1.如果扫描多个包,多个包使用逗号隔开
  9. 2.扫描包上层目录
  10. -->
  11. <context:component-scan base-package="com.kuang.pojo"/>

开启组件扫描细节配置

  1. <!--示例 1
  2. use-default-filters="false" 表示现在不使用默认 filter,自己配置 filter
  3. context:include-filter ,设置扫描哪些内容,以下示例表示扫描带controller注解的类
  4. -->
  5. <context:component-scan base-package="com.atguigu" use-default-filters="false">
  6. <context:include-filter type="annotation"
  7. expression="org.springframework.stereotype.Controller"/>
  8. </context:component-scan>
  9. <!--示例 2
  10. 下面配置扫描包所有内容
  11. context:exclude-filter: 设置哪些内容不进行扫描
  12. -->
  13. <context:component-scan base-package="com.atguigu">
  14. <context:exclude-filter type="annotation"
  15. expression="org.springframework.stereotype.Controller"/>
  16. </context:component-scan>

2、在指定包下编写类,增加注解

  1. //在注解中value属性值可以不写,默认值是类名,首字母小写
  2. @Component("user")
  3. // 相当于配置文件中 <bean id="user" class="当前注解的类"/>
  4. public class User {
  5. public String name = "秦疆";
  6. }

3、测试

  1. @Test
  2. public void test(){
  3. ApplicationContext applicationContext =
  4. new ClassPathXmlApplicationContext("beans.xml");
  5. User user = (User) applicationContext.getBean("user");
  6. System.out.println(user.name);
  7. }

8.2.属性注入

使用注解注入属性
1、可以不用提供set方法,直接在直接名上添加@value(“值”)

  1. @Component("user")
  2. // 相当于配置文件中 <bean id="user" class="当前注解的类"/>
  3. public class User {
  4. @Value("秦疆")
  5. // 相当于配置文件中 <property name="name" value="秦疆"/>
  6. public String name;
  7. }

2、如果提供了set方法,在set方法上添加@value(“值”);

  1. @Component("user")
  2. public class User {
  3. public String name;
  4. @Value("秦疆")
  5. public void setName(String name) {
  6. this.name = name;
  7. }
  8. }

8.3.衍生注解

我们这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller:controller层
  • @Service:service层
  • @Repository:dao层

写上这些注解,就相当于将这个类交给Spring管理装配了!

8.4.自动装配注解

在Bean的自动装配已经讲过了,可以回顾!

8.5.作用域

@scope

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收

    1. @Controller("user")
    2. @Scope("prototype")
    3. public class User {
    4. @Value("秦疆")
    5. public String name;
    6. }

    8.6.小结

    XML与注解比较

  • XML可以适用任何场景 ,结构清晰,维护方便

  • 注解不是自己提供的类使用不了,开发简单方便

xml与注解整合开发 :推荐最佳实践

  • xml管理Bean
  • 注解完成属性注入
  • 使用过程中, 可以不用扫描,扫描是为了类上的注解

    1. <context:annotation-config/>

    作用:

  • 进行注解驱动注册,从而使注解生效

  • 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
  • 如果不扫描包,就需要手动配置bean
  • 如果不加注解驱动,则注入的值为null!

    9.基于Java类进行配置

    JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
    测试:
    1、编写一个实体类,Dog

    1. @Component //将这个类标注为Spring的一个组件,放到容器中!
    2. public class Dog {
    3. public String name = "dog";
    4. }

    2、新建一个config配置包,编写一个MyConfig配置类 ```java @Configuration //代表这是一个配置类 //@ComponentScan(basePackages = {“com.atguigu”}) public class MyConfig {

    @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id! public Dog dog(){

    1. return new Dog();

    }

}

  1. 3、测试
  2. ```java
  3. @Test
  4. public void test2(){
  5. ApplicationContext applicationContext =
  6. new AnnotationConfigApplicationContext(MyConfig.class);
  7. Dog dog = (Dog) applicationContext.getBean("dog");
  8. System.out.println(dog.name);
  9. }

4、成功输出结果!
导入其他配置如何做呢?
1、我们再编写一个配置类!

  1. @Configuration //代表这是一个配置类
  2. public class MyConfig2 {
  3. }

2、在之前的配置类中我们来选择导入这个配置类

  1. @Configuration
  2. @Import(MyConfig2.class) //导入合并其他配置类,类似于配置文件中的 inculde 标签
  3. public class MyConfig {
  4. @Bean
  5. public Dog dog(){
  6. return new Dog();
  7. }
  8. }

关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!

10.代理模式

为什么要学习代理模式,因为AOP的底层机制就是动态代理!
代理模式:

  • 静态代理
  • 动态代理

学习aop之前 , 我们要先了解一下代理模式!
image.png

10.1静态代理

静态代理角色分析

  • 抽象角色 : 一般使用接口或者抽象类来实现
  • 真实角色 : 被代理的角色
  • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
  • 客户 : 使用代理角色来进行一些操作 .

代码实现
Rent . java 即抽象角色

  1. //抽象角色:租房
  2. public interface Rent {
  3. public void rent();
  4. }

Host . java 即真实角色

  1. //真实角色: 房东,房东要出租房子
  2. public class Host implements Rent{
  3. public void rent() {
  4. System.out.println("房屋出租");
  5. }
  6. }

Proxy . java 即代理角色

  1. //代理角色:中介
  2. public class Proxy implements Rent {
  3. private Host host;
  4. public Proxy() { }
  5. public Proxy(Host host) {
  6. this.host = host;
  7. }
  8. //租房
  9. public void rent(){
  10. seeHouse();
  11. host.rent();
  12. fare();
  13. }
  14. //看房
  15. public void seeHouse(){
  16. System.out.println("带房客看房");
  17. }
  18. //收中介费
  19. public void fare(){
  20. System.out.println("收中介费");
  21. }
  22. }

Client . java 即客户

  1. //客户类,一般客户都会去找代理!
  2. public class Client {
  3. public static void main(String[] args) {
  4. //房东要租房
  5. Host host = new Host();
  6. //中介帮助房东
  7. Proxy proxy = new Proxy(host);
  8. //你去找中介!
  9. proxy.rent();
  10. }
  11. }

分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。
静态代理的好处:

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .

缺点 :

  • 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .

我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

10.2.静态代理再理解

同学们练习完毕后,我们再来举一个例子,巩固大家的学习!
练习步骤:
1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!

  1. //抽象角色:增删改查业务
  2. public interface UserService {
  3. void add();
  4. void delete();
  5. void update();
  6. void query();
  7. }

2、我们需要一个真实对象来完成这些增删改查操作

  1. //真实对象,完成增删改查操作的人
  2. public class UserServiceImpl implements UserService {
  3. public void add() {
  4. System.out.println("增加了一个用户");
  5. }
  6. public void delete() {
  7. System.out.println("删除了一个用户");
  8. }
  9. public void update() {
  10. System.out.println("更新了一个用户");
  11. }
  12. public void query() {
  13. System.out.println("查询了一个用户");
  14. }
  15. }

3、需求来了,现在我们需要增加一个日志功能,怎么实现!

  • 思路1 :在实现类上增加代码 【麻烦!】
  • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!

4、设置一个代理类来处理日志!代理角色

  1. //代理角色,在这里面增加日志的实现
  2. public class UserServiceProxy implements UserService {
  3. private UserServiceImpl userService;
  4. public void setUserService(UserServiceImpl userService) {
  5. this.userService = userService;
  6. }
  7. public void add() {
  8. log("add");
  9. userService.add();
  10. }
  11. public void delete() {
  12. log("delete");
  13. userService.delete();
  14. }
  15. public void update() {
  16. log("update");
  17. userService.update();
  18. }
  19. public void query() {
  20. log("query");
  21. userService.query();
  22. }
  23. public void log(String msg){
  24. System.out.println("执行了"+msg+"方法");
  25. }
  26. }

5、测试访问类:

  1. public class Client {
  2. public static void main(String[] args) {
  3. //真实业务
  4. UserServiceImpl userService = new UserServiceImpl();
  5. //代理类
  6. UserServiceProxy proxy = new UserServiceProxy();
  7. //使用代理类实现日志功能!
  8. proxy.setUserService(userService);
  9. proxy.add();
  10. }
  11. }

OK,到了现在代理模式大家应该都没有什么问题了,重点大家需要理解其中的思想;
我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想
聊聊AOP:纵向开发,横向开发
image.png

10.3动态代理

  • 动态代理的角色和静态代理的一样 .
  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
    • 基于接口的动态代理——JDK动态代理
    • 基于类的动态代理–cglib
    • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
    • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!、

JDK的动态代理需要了解两个类
核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看
【InvocationHandler:调用处理程序】
image.png

  1. Object invoke(Object proxy, 方法 method, Object[] args);
  2. //参数
  3. //proxy - 调用该方法的代理实例
  4. //method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
  5. //args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。

【Proxy : 代理】
image.png
image.png
image.png

  1. //生成代理类
  2. public Object getProxy(){
  3. return Proxy.newProxyInstance(this.getClass().getClassLoader(),
  4. rent.getClass().getInterfaces(),this);
  5. }

代码实现
抽象角色和真实角色和之前的一样!
Rent . java 即抽象角色

  1. //抽象角色:租房
  2. public interface Rent {
  3. public void rent();
  4. }

Host . java 即真实角色

  1. //真实角色: 房东,房东要出租房子
  2. public class Host implements Rent{
  3. public void rent() {
  4. System.out.println("房屋出租");
  5. }
  6. }

ProxyInvocationHandler. java 即代理角色

  1. public class ProxyInvocationHandler implements InvocationHandler {
  2. private Rent rent;
  3. public void setRent(Rent rent) {
  4. this.rent = rent;
  5. }
  6. //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
  7. public Object getProxy(){
  8. return Proxy.newProxyInstance(this.getClass().getClassLoader(),
  9. rent.getClass().getInterfaces(),this);
  10. }
  11. // proxy : 代理类 method : 代理类的调用处理程序的方法对象.
  12. // 处理代理实例上的方法调用并返回结果
  13. @Override
  14. public Object invoke(Object proxy, Method method, Object[] args) throwsThrowable {
  15. seeHouse();
  16. //核心:本质利用反射实现!
  17. Object result = method.invoke(rent, args);
  18. fare();
  19. return result;
  20. }
  21. //看房
  22. public void seeHouse(){
  23. System.out.println("带房客看房");
  24. }
  25. //收中介费
  26. public void fare(){
  27. System.out.println("收中介费");
  28. }
  29. }

Client . java

  1. //租客
  2. public class Client {
  3. public static void main(String[] args) {
  4. //真实角色
  5. Host host = new Host();
  6. //代理实例的调用处理程序
  7. ProxyInvocationHandler pih = new ProxyInvocationHandler();
  8. pih.setRent(host); //将真实角色放置进去!
  9. Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!
  10. proxy.rent();
  11. }
  12. }

核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!、

10.4深化理解

我们来使用动态代理实现代理我们后面写的UserService!
我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!

  1. public class ProxyInvocationHandler implements InvocationHandler {
  2. private Object target;
  3. public void setTarget(Object target) {
  4. this.target = target;
  5. }
  6. //生成代理类
  7. public Object getProxy(){
  8. return Proxy.newProxyInstance(this.getClass().getClassLoader(),
  9. target.getClass().getInterfaces(),this);
  10. }
  11. // proxy : 代理类
  12. // method : 代理类的调用处理程序的方法对象.
  13. public Object invoke(Object proxy, Method method, Object[] args) throwsThrowable {
  14. log(method.getName());
  15. Object result = method.invoke(target, args);
  16. return result;
  17. }
  18. public void log(String methodName){
  19. System.out.println("执行了"+methodName+"方法");
  20. }
  21. }

测试!

  1. public class Test {
  2. public static void main(String[] args) {
  3. //真实对象
  4. UserServiceImpl userService = new UserServiceImpl();
  5. //代理对象的调用处理程序
  6. ProxyInvocationHandler pih = new ProxyInvocationHandler();
  7. pih.setTarget(userService); //设置要代理的对象
  8. UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
  9. proxy.delete();
  10. }
  11. }

测试,增删改查,查看结果!

10.5动态代理的好处

静态代理有的它都有,静态代理没有的,它也有!

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .
  • 一个动态代理 , 一般代理某一类业务
  • 一个动态代理可以代理多个类,代理的是接口!

    11.AOP

    11.1.什么是AOP

    AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
    image.png

    11.2.Aop在Spring中的作用

    提供声明式事务;允许用户自定义切面
    以下名词需要了解下:

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …

  • 切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类。通知应用到切入点的过程。
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。实际增强的逻辑部分。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知执行的 “地点”的定义,实际被增强的方法。
  • 连接点(JointPoint):与切入点匹配的执行点,类里面哪些方法可以被增强。

image.png
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
image.png
即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .

11.3.使用Spring实现Aop

【重点】使用AOP织入,需要导入一个依赖包!

  1. <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
  2. <dependency>
  3. <groupId>org.aspectj</groupId>
  4. <artifactId>aspectjweaver</artifactId>
  5. <version>1.9.4</version>
  6. </dependency>

11.3.1.通过 Spring API 实现

第一种方式
首先编写我们的业务接口和实现类

  1. public interface UserService {
  2. public void add();
  3. public void delete();
  4. public void update();
  5. public void search();
  6. }
  7. public class UserServiceImpl implements UserService{
  8. @Override
  9. public void add() {
  10. System.out.println("增加用户");
  11. }
  12. @Override
  13. public void delete() {
  14. System.out.println("删除用户");
  15. }
  16. @Override
  17. public void update() {
  18. System.out.println("更新用户");
  19. }
  20. @Override
  21. public void search() {
  22. System.out.println("查询用户");
  23. }
  24. }

然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强

  1. public class Log implements MethodBeforeAdvice {
  2. //method : 要执行的目标对象的方法
  3. //objects : 被调用的方法的参数
  4. //Object : 目标对象
  5. @Override
  6. public void before(Method method, Object[] objects, Object o) throws Throwable {
  7. System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
  8. }
  9. }
  10. public class AfterLog implements AfterReturningAdvice {
  11. //returnValue 返回值
  12. //method被调用的方法
  13. //args 被调用的方法的对象的参数
  14. //target 被调用的目标对象
  15. @Override
  16. public void afterReturning(Object returnValue, Method method, Object[] args,Object target) throws Throwable {
  17. System.out.println("执行了" + target.getClass().getName()
  18. +"的"+method.getName()+"方法,"
  19. +"返回值:"+returnValue);
  20. }
  21. }

最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/aop
  8. http://www.springframework.org/schema/aop/spring-aop.xsd">
  9. <!--注册bean-->
  10. <bean id="userService" class="com.kuang.service.UserServiceImpl"/>
  11. <bean id="log" class="com.kuang.log.Log"/>
  12. <bean id="afterLog" class="com.kuang.log.AfterLog"/>
  13. <!--aop的配置-->
  14. <aop:config>
  15. <!--切入点 expression:表达式匹配要执行的方法-->
  16. <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
  17. <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
  18. <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
  19. <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
  20. </aop:config>
  21. </beans>

测试

  1. public class MyTest {
  2. @Test
  3. public void test(){
  4. ApplicationContext context = newClassPathXmlApplicationContext("beans.xml");
  5. UserService userService = (UserService) context.getBean("userService");
  6. userService.search();
  7. }
  8. }

Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .

11.3.2.自定义类来实现Aop

第二种方式
目标业务类不变依旧是userServiceImpl
第一步 : 写我们自己的一个切入类

  1. public class DiyPointcut {
  2. public void before(){
  3. System.out.println("---------方法执行前---------");
  4. }
  5. public void after(){
  6. System.out.println("---------方法执行后---------");
  7. }
  8. }

去spring中配置

  1. <!--第二种方式自定义实现-->
  2. <!--注册bean-->
  3. <bean id="diy" class="com.kuang.config.DiyPointcut"/>
  4. <!--aop的配置-->
  5. <aop:config>
  6. <!--第二种方式:使用AOP的标签实现-->
  7. <aop:aspect ref="diy">
  8. <aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
  9. <aop:before pointcut-ref="diyPonitcut" method="before"/>
  10. <aop:after pointcut-ref="diyPonitcut" method="after"/>
  11. </aop:aspect>
  12. </aop:config>

测试:

  1. public class MyTest {
  2. @Test
  3. public void test(){
  4. ApplicationContext context = newClassPathXmlApplicationContext("beans.xml");
  5. UserService userService = (UserService) context.getBean("userService");
  6. userService.add();
  7. }
  8. }

11.3.4.使用注解实现

第三种方式
第一步:编写一个注解实现的增强类

  1. package com.kuang.config;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.After;
  4. import org.aspectj.lang.annotation.Around;
  5. import org.aspectj.lang.annotation.Aspect;
  6. import org.aspectj.lang.annotation.Before;
  7. @Aspect
  8. public class AnnotationPointcut {
  9. @Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
  10. public void before(){
  11. System.out.println("---------方法执行前---------");
  12. }
  13. @After("execution(* com.kuang.service.UserServiceImpl.*(..))")
  14. public void after(){
  15. System.out.println("---------方法执行后---------");
  16. }
  17. @Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
  18. public void around(ProceedingJoinPoint jp) throws Throwable {
  19. System.out.println("环绕前");
  20. System.out.println("签名:"+jp.getSignature());
  21. //执行目标方法proceed
  22. Object proceed = jp.proceed();
  23. System.out.println("环绕后");
  24. System.out.println(proceed);
  25. }
  26. }

第二步:在Spring配置文件中,注册bean,并增加支持注解的配置

  1. <!--第三种方式:注解实现-->
  2. <bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
  3. <aop:aspectj-autoproxy/>

aop:aspectj-autoproxy:说明

通过aop命名空间的声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被隐藏起来了

有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

12.整合MyBatis

1、导入相关jar包
junit

  1. <dependency>
  2. <groupId>junit</groupId>
  3. <artifactId>junit</artifactId>
  4. <version>4.12</version>
  5. </dependency>

mybatis

  1. <dependency>
  2. <groupId>org.mybatis</groupId>
  3. <artifactId>mybatis</artifactId>
  4. <version>3.5.2</version>
  5. </dependency>

mysql-connector-java

  1. <dependency>
  2. <groupId>mysql</groupId>
  3. <artifactId>mysql-connector-java</artifactId>
  4. <version>5.1.47</version>
  5. </dependency>

spring相关

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-webmvc</artifactId>
  4. <version>5.1.10.RELEASE</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework</groupId>
  8. <artifactId>spring-jdbc</artifactId>
  9. <version>5.1.10.RELEASE</version>
  10. </dependency>

aspectJ AOP 织入器

  1. <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
  2. <dependency>
  3. <groupId>org.aspectj</groupId>
  4. <artifactId>aspectjweaver</artifactId>
  5. <version>1.9.4</version>
  6. </dependency>

mybatis-spring整合包 【重点】

  1. <dependency>
  2. <groupId>org.mybatis</groupId>
  3. <artifactId>mybatis-spring</artifactId>
  4. <version>2.0.2</version>
  5. </dependency>

配置Maven静态资源过滤问题!

  1. <build>
  2. <resources>
  3. <resource>
  4. <directory>src/main/java</directory>
  5. <includes>
  6. <include>**/*.properties</include>
  7. <include>**/*.xml</include>
  8. </includes>
  9. <filtering>true</filtering>
  10. </resource>
  11. </resources>
  12. </build>

2、编写配置文件
3、代码实现

12.1回忆MyBatis

编写pojo实体类

  1. package com.kuang.pojo;
  2. public class User {
  3. private int id; //id
  4. private String name; //姓名
  5. private String pwd; //密码
  6. }

实现mybatis的配置文件

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6. <typeAliases>
  7. <package name="com.kuang.pojo"/>
  8. </typeAliases>
  9. <environments default="development">
  10. <environment id="development">
  11. <transactionManager type="JDBC"/>
  12. <dataSource type="POOLED">
  13. <property name="driver" value="com.mysql.jdbc.Driver"/>
  14. <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
  15. <property name="username" value="root"/>
  16. <property name="password" value="123456"/>
  17. </dataSource>
  18. </environment>
  19. </environments>
  20. <mappers>
  21. <package name="com.kuang.dao"/>
  22. </mappers>
  23. </configuration>

UserDao接口编写

  1. public interface UserMapper {
  2. public List<User> selectUser();
  3. }

接口对应的Mapper映射文件

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.kuang.dao.UserMapper">
  6. <select id="selectUser" resultType="User">
  7. select * from user
  8. </select>
  9. </mapper>

测试类

  1. @Test
  2. public void selectUser() throws IOException {
  3. String resource = "mybatis-config.xml";
  4. InputStream inputStream = Resources.getResourceAsStream(resource);
  5. SqlSessionFactory sqlSessionFactory = newSqlSessionFactoryBuilder().build(inputStream);
  6. SqlSession sqlSession = sqlSessionFactory.openSession();
  7. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  8. List<User> userList = mapper.selectUser();
  9. for (User user: userList){
  10. System.out.println(user);
  11. }
  12. sqlSession.close();
  13. }

12.2.MyBatis-Spring学习

引入Spring之前需要了解mybatis-spring包中的一些重要类;
http://www.mybatis.org/spring/zh/index.html
image.png
什么是 MyBatis-Spring?
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。
知识基础
在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要
MyBatis-Spring 需要以下版本:

MyBatis-Spring MyBatis Spring 框架 Spring Batch Java
2.0 3.5+ 5.0+ 4.0+ Java 8+
1.3 3.4+ 3.2.2+ 2.1+ Java 6+

如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:

  1. <dependency>
  2. <groupId>org.mybatis</groupId>
  3. <artifactId>mybatis-spring</artifactId>
  4. <version>2.0.2</version>
  5. </dependency>

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。
在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

  1. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  2. <property name="dataSource" ref="dataSource" />
  3. </bean>

注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。
在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。
在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。
SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。
一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。
需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。
SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。
模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。
可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

  1. <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  2. <constructor-arg index="0" ref="sqlSessionFactory" />
  3. </bean>

现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:

  1. public class UserDaoImpl implements UserDao {
  2. private SqlSession sqlSession;
  3. public void setSqlSession(SqlSession sqlSession) {
  4. this.sqlSession = sqlSession;
  5. }
  6. public User getUser(String userId) {
  7. return sqlSession.getMapper...;
  8. }
  9. }

按下面这样,注入 SqlSessionTemplate:

  1. <bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
  2. <property name="sqlSession" ref="sqlSession" />
  3. </bean>

12.3.整合实现一

1、引入Spring配置文件beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">

2、配置数据源替换mybaits的数据源

  1. <!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
  2. <bean id="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  3. <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  4. <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
  5. <property name="username" value="root"/>
  6. <property name="password" value="123456"/>
  7. </bean>

3、配置SqlSessionFactory,关联MyBatis

  1. <!--配置SqlSessionFactory-->
  2. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  3. <property name="dataSource" ref="dataSource"/>
  4. <!--关联Mybatis-->
  5. <property name="configLocation" value="classpath:mybatis-config.xml"/>
  6. <property name="mapperLocations" value="classpath:com/kuang/dao/*.xml"/>
  7. </bean>

4、注册sqlSessionTemplate,关联sqlSessionFactory;

  1. <!--注册sqlSessionTemplate , 关联sqlSessionFactory-->
  2. <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  3. <!--利用构造器注入-->
  4. <constructor-arg index="0" ref="sqlSessionFactory"/>
  5. </bean>

5、增加Dao接口的实现类;私有化sqlSessionTemplate

  1. public class UserDaoImpl implements UserMapper {
  2. //sqlSession不用我们自己创建了,Spring来管理
  3. private SqlSessionTemplate sqlSession;
  4. public void setSqlSession(SqlSessionTemplate sqlSession) {
  5. this.sqlSession = sqlSession;
  6. }
  7. public List<User> selectUser() {
  8. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  9. return mapper.selectUser();
  10. }
  11. }

6、注册bean实现

  1. <bean id="userDao" class="com.kuang.dao.UserDaoImpl">
  2. <property name="sqlSession" ref="sqlSession"/>
  3. </bean>

7、测试

  1. @Test
  2. public void test2(){
  3. ApplicationContext context = newClassPathXmlApplicationContext("beans.xml");
  4. UserMapper mapper = (UserMapper) context.getBean("userDao");
  5. List<User> user = mapper.selectUser();
  6. System.out.println(user);
  7. }

结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6. <typeAliases>
  7. <package name="com.kuang.pojo"/>
  8. </typeAliases>
  9. </configuration>

12.4.整合实现二

mybatis-spring1.2.3版以上的才有这个 .
官方文档截图 :
dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看
image.png
测试:
1、将我们上面写的UserDaoImpl修改一下

  1. public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
  2. public List<User> selectUser() {
  3. UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
  4. return mapper.selectUser();
  5. }
  6. }

2、修改bean的配置

  1. <bean id="userDao" class="com.kuang.dao.UserDaoImpl">
  2. <property name="sqlSessionFactory" ref="sqlSessionFactory" />
  3. </bean>

3、测试

  1. @Test
  2. public void test2(){
  3. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  4. UserMapper mapper = (UserMapper) context.getBean("userDao");
  5. List<User> user = mapper.selectUser();
  6. System.out.println(user);
  7. }

总结 : 整合到spring以后可以完全不要mybatis的配置文件,除了这些方式可以实现整合之外,我们还可以使用注解来实现,这个等我们后面学习SpringBoot的时候还会测试整合!

13.JdbcTemplate

13.1.概念

Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作

13.2.准备工作

(1)引入相关 jar 包
image.png
(2)在 spring 配置文件配置数据库连接池

  1. <!-- 数据库连接池 -->
  2. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
  3. destroy-method="close">
  4. <property name="url" value="jdbc:mysql:///user_db" />
  5. <property name="username" value="root" />
  6. <property name="password" value="root" />
  7. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
  8. </bean>

(3)配置 JdbcTemplate 对象,注入 DataSource

  1. <!-- JdbcTemplate 对象 -->
  2. <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  3. <!--注入 dataSource-->
  4. <property name="dataSource" ref="dataSource"></property>
  5. </bean>

(4)创建 service 类,创建 dao 类,在 dao 注入 jdbcTemplate 对象

  1. <!-- 组件扫描 -->
  2. <context:component-scan base-package="com.atguigu"></context:component-scan>
  1. @Service
  2. public class BookService {
  3. //注入 dao
  4. @Autowired
  5. private BookDao bookDao;
  6. }
  1. @Repository
  2. public class BookDaoImpl implements BookDao {
  3. //注入 JdbcTemplate
  4. @Autowired
  5. private JdbcTemplate jdbcTemplate;
  6. }

13.3.JdbcTemplate 操作数据库

添加

1、对应数据库创建实体类
2、编写 service 和 dao
(1)在 dao 进行数据库添加操作
(2)调用 JdbcTemplate 对象里面 update 方法实现添加操作
image.png

  • 第一个参数:sql 语句
  • 第二个参数:可变参数,设置 sql 语句值

    1. @Repository
    2. public class BookDaoImpl implements BookDao {
    3. //注入 JdbcTemplate
    4. @Autowired
    5. private JdbcTemplate jdbcTemplate;
    6. //添加的方法
    7. @Override
    8. public void add(Book book) {
    9. //1 创建 sql 语句
    10. String sql = "insert into t_book values(?,?,?)";
    11. //2 调用方法实现
    12. Object[] args = {book.getUserId(), book.getUsername(),
    13. book.getUstatus()};
    14. int update = jdbcTemplate.update(sql,args);
    15. System.out.println(update);
    16. }
    17. }

    3、测试类

    1. @Test
    2. public void testJdbcTemplate() {
    3. ApplicationContext context =
    4. new ClassPathXmlApplicationContext("bean1.xml");
    5. BookService bookService = context.getBean("bookService",
    6. BookService.class);
    7. Book book = new Book();
    8. book.setUserId("1");
    9. book.setUsername("java");
    10. book.setUstatus("a");
    11. bookService.addBook(book);
    12. }

    修改

    1. @Override
    2. public void updateBook(Book book) {
    3. String sql = "update t_book set username=?,ustatus=? where user_id=?";
    4. Object[] args = {book.getUsername(), book.getUstatus(),book.getUserId()};
    5. int update = jdbcTemplate.update(sql, args);
    6. System.out.println(update);
    7. }

    删除

    1. @Override
    2. public void delete(String id) {
    3. String sql = "delete from t_book where user_id=?";
    4. int update = jdbcTemplate.update(sql, id);
    5. System.out.println(update);
    6. }

    查询返回某个值

    1、查询表里面有多少条记录,返回是某个值
    2、使用 JdbcTemplate 实现查询返回某个值代码
    image.png

  • 第一个参数:sql 语句

  • 第二个参数:返回类型 Class

    1. //查询表记录数
    2. @Override
    3. public int selectCount() {
    4. String sql = "select count(*) from t_book";
    5. Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
    6. return count;
    7. }

    查询返回对象

    1、场景:查询图书详情
    2、JdbcTemplate 实现查询返回对象
    image.png

  • 第一个参数:sql 语句

  • 第二个参数:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装
  • 第三个参数:sql 语句值

    1. //查询返回对象
    2. @Override
    3. public Book findBookInfo(String id) {
    4. String sql = "select * from t_book where user_id=?";
    5. //调用方法
    6. Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
    7. return book;
    8. }

    查询返回集合

    1、场景:查询图书列表分页…
    2、调用 JdbcTemplate 方法实现查询返回集合
    image.png

  • 第一个参数:sql 语句

  • 第二个参数:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装
  • 第三个参数:sql 语句值

    1. //查询返回集合
    2. @Override
    3. public List<Book> findAllBook() {
    4. String sql = "select * from t_book";
    5. //调用方法
    6. List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
    7. return bookList;
    8. }

    批量操作

    1、批量操作:操作表里面多条记录
    2、JdbcTemplate 实现批量添加操作
    image.png

  • 第一个参数:sql 语句

  • 第二个参数:List 集合,添加多条记录数据 ```java //批量添加 @Override public void batchAddBook(List batchArgs) { String sql = “insert into t_book values(?,?,?)”; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); }

//批量添加测试 List batchArgs = new ArrayList<>(); Object[] o1 = {“3”,”java”,”a”}; Object[] o2 = {“4”,”c++”,”b”}; Object[] o3 = {“5”,”MySQL”,”c”}; batchArgs.add(o1); batchArgs.add(o2); batchArgs.add(o3); //调用批量添加 bookService.batchAdd(batchArgs);

  1. **3JdbcTemplate 实现批量修改操作**
  2. ```java
  3. //批量修改
  4. @Override
  5. public void batchUpdateBook(List<Object[]> batchArgs) {
  6. String sql = "update t_book set username=?,ustatus=? where user_id=?";
  7. int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
  8. System.out.println(Arrays.toString(ints));
  9. }
  10. //批量修改
  11. List<Object[]> batchArgs = new ArrayList<>();
  12. Object[] o1 = {"java0909","a3","3"};
  13. Object[] o2 = {"c++1010","b4","4"};
  14. Object[] o3 = {"MySQL1111","c5","5"};
  15. batchArgs.add(o1);
  16. batchArgs.add(o2);
  17. batchArgs.add(o3);
  18. //调用方法实现批量修改
  19. bookService.batchUpdate(batchArgs);

4、JdbcTemplate 实现批量删除操作

  1. //批量删除
  2. @Override
  3. public void batchDeleteBook(List<Object[]> batchArgs) {
  4. String sql = "delete from t_book where user_id=?";
  5. int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
  6. System.out.println(Arrays.toString(ints));
  7. }
  8. //批量删除
  9. List<Object[]> batchArgs = new ArrayList<>();
  10. Object[] o1 = {"3"};
  11. Object[] o2 = {"4"};
  12. batchArgs.add(o1);
  13. batchArgs.add(o2);
  14. //调用方法实现批量删除
  15. bookService.batchDelete(batchArgs);

14.声明式事务

14.1.回顾事务

  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
  • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。

事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。
事务四个属性ACID

  1. 原子性(atomicity)

事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用

  1. 一致性(consistency)

一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中

  1. 隔离性(isolation)

可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏

  1. 持久性(durability)

事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中

测试

将上面的代码拷贝到一个新项目中
在之前的案例中,我们给userDao接口新增两个方法,删除和增加用户;

  1. //添加一个用户
  2. int addUser(User user);
  3. //根据id删除用户
  4. int deleteUser(int id);

mapper文件,我们故意把 deletes 写错,测试!

  1. <insert id="addUser" parameterType="com.kuang.pojo.User">
  2. insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
  3. </insert>
  4. <delete id="deleteUser" parameterType="int">
  5. deletes from user where id = #{id}
  6. </delete>

编写接口的实现类,在实现类中,我们去操作一波

  1. public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
  2. //增加一些操作
  3. public List<User> selectUser() {
  4. User user = new User(4,"小明","123456");
  5. UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
  6. mapper.addUser(user);
  7. mapper.deleteUser(4);
  8. return mapper.selectUser();
  9. }
  10. //新增
  11. public int addUser(User user) {
  12. UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
  13. return mapper.addUser(user);
  14. }
  15. //删除
  16. public int deleteUser(int id) {
  17. UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
  18. return mapper.deleteUser(id);
  19. }
  20. }

测试

  1. @Test
  2. public void test2(){
  3. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  4. UserMapper mapper = (UserMapper) context.getBean("userDao");
  5. List<User> user = mapper.selectUser();
  6. System.out.println(user);
  7. }

报错:sql异常,delete写错了
结果 :插入成功!
没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!
以前我们都需要自己手动管理事务,十分麻烦!
但是Spring给我们提供了事务管理,我们只需要配置即可;

14.2.Spring中的事务管理

Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
编程式事务管理

  • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
  • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

声明式事务管理

  • 一般情况下比编程式事务好用。
  • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

使用Spring管理事务,注意头文件的约束导入 : tx

  1. xmlns:tx="http://www.springframework.org/schema/tx"
  2. http://www.springframework.org/schema/tx
  3. http://www.springframework.org/schema/tx/spring-tx.xsd">

事务管理器

  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。

JDBC事务

  1. <bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  2. <property name="dataSource" ref="dataSource" />
  3. </bean>

配置好事务管理器后我们需要去配置事务的通知

  1. <!--配置事务通知-->
  2. <tx:advice id="txAdvice" transaction-manager="transactionManager">
  3. <tx:attributes>
  4. <!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
  5. <tx:method name="add" propagation="REQUIRED"/>
  6. <tx:method name="delete" propagation="REQUIRED"/>
  7. <tx:method name="update" propagation="REQUIRED"/>
  8. <tx:method name="search*" propagation="REQUIRED"/>
  9. <tx:method name="get" read-only="true"/>
  10. <tx:method name="*" propagation="REQUIRED"/>
  11. </tx:attributes>
  12. </tx:advice>

spring事务传播特性:
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!
配置AOP
导入aop的头文件!

  1. <!--配置aop织入事务-->
  2. <aop:config>
  3. <aop:pointcut id="txPointcut" expression="execution(* com.kuang.dao.*.*(..))"/>
  4. <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
  5. </aop:config>

进行测试
删掉刚才插入的数据,再次测试!

  1. @Test
  2. public void test2(){
  3. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  4. UserMapper mapper = (UserMapper) context.getBean("userDao");
  5. List<User> user = mapper.selectUser();
  6. System.out.println(user);
  7. }

14.3.事务隔离级别

事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
有三个读问题:脏读、不可重复读、虚(幻)读
脏读:一个未提交事务读取到另一个未提交事务的数据
image.png
不可重复读:一个未提交事务读取到另一提交事务修改数据
image.png
虚读:一个未提交事务读取到另一提交事务添加数据
解决:通过设置事务隔离级别,解决读问题
image.png
image.png
timeout:超时时间

  • 事务需要在一定时间内进行提交,如果不提交进行回滚
  • 默认值是 -1 ,设置时间以秒单位进行计算

readOnly:是否只读

  • 读:查询操作,写:添加修改删除操作
  • readOnly 默认值 false,表示可以查询,可以添加修改删除操作
  • 设置 readOnly 值是 true,设置成 true 之后,只能查询

rollbackFor:回滚

  • 设置出现哪些异常进行事务回滚

noRollbackFor:不回滚

  • 设置出现哪些异常不进行事务回滚

    14.4.XML 声明式事务管理

    在 spring 配置文件中进行配置
    第一步 配置事务管理器
    第二步 配置通知
    第三步 配置切入点和切面

    1. <!--1 创建事务管理器-->
    2. <bean id="transactionManager"
    3. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    4. <!--注入数据源-->
    5. <property name="dataSource" ref="dataSource"></property>
    6. </bean>
    7. <!--2 配置通知-->
    8. <tx:advice id="txadvice">
    9. <!--配置事务参数-->
    10. <tx:attributes>
    11. <!--指定哪种规则的方法上面添加事务-->
    12. <tx:method name="accountMoney" propagation="REQUIRED"/>
    13. <!--<tx:method name="account*"/>-->
    14. </tx:attributes>
    15. </tx:advice>
    16. <!--3 配置切入点和切面-->
    17. <aop:config>
    18. <!--配置切入点-->
    19. <aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))"/>
    20. <!--配置切面-->
    21. <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
    22. </aop:config>

    14.5.完全注解声明式事务管理

    创建配置类,使用配置类替代 xml 配置文件

    1. @Configuration //配置类
    2. @ComponentScan(basePackages = "com.atguigu") //组件扫描
    3. @EnableTransactionManagement //开启事务
    4. public class TxConfig {
    5. //创建数据库连接池
    6. @Bean
    7. public DruidDataSource getDruidDataSource() {
    8. DruidDataSource dataSource = new DruidDataSource();
    9. dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    10. dataSource.setUrl("jdbc:mysql:///user_db");
    11. dataSource.setUsername("root");
    12. dataSource.setPassword("root");
    13. return dataSource;
    14. }
    15. //创建 JdbcTemplate 对象
    16. @Bean
    17. public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
    18. //到 ioc 容器中根据类型找到 dataSource
    19. JdbcTemplate jdbcTemplate = new JdbcTemplate();
    20. //注入 dataSource
    21. jdbcTemplate.setDataSource(dataSource);
    22. return jdbcTemplate;
    23. }
    24. //创建事务管理器
    25. @Bean
    26. public DataSourceTransactionManager
    27. getDataSourceTransactionManager(DataSource dataSource) {
    28. DataSourceTransactionManager transactionManager = new
    29. DataSourceTransactionManager();
    30. transactionManager.setDataSource(dataSource);
    31. return transactionManager;
    32. }
    33. }

    15.Spring5 框架新功能

    整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方法在代码库中删除

    15.1.自带通用的日志封装

    (1)Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2
    (2)Spring5 框架整合 Log4j2
    第一步 引入 jar 包
    image.png
    第二步 创建 log4j2.xml 配置文件

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
    3. <!--Configuration 后面的 status 用于设置 log4j2 自身内部的信息输出,可以不设置,当设置成 trace 时,可以看到 log4j2 内部各种详细输出-->
    4. <configuration status="INFO">
    5. <!--先定义所有的 appender-->
    6. <appenders>
    7. <!--输出日志信息到控制台-->
    8. <console name="Console" target="SYSTEM_OUT">
    9. <!--控制日志输出的格式-->
    10. <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    11. </console>
    12. </appenders>
    13. <!--然后定义 logger,只有定义 logger 并引入的 appender,appender 才会生效-->
    14. <!--root:用于指定项目的根日志,如果没有单独指定 Logger,则会使用 root 作为默认的日志输出-->
    15. <loggers>
    16. <root level="info">
    17. <appender-ref ref="Console"/>
    18. </root>
    19. </loggers>
    20. </configuration>

    15.2.核心容器支持@Nullable 注解

    (1)@Nullable 注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以
    为空,参数值可以为空
    (2)注解用在方法上面,方法返回值可以为空
    image.png
    (3)注解使用在方法参数里面,方法参数可以为空
    image.png
    (4)注解使用在属性上面,属性值可以为空
    image.png

    15.3.核心容器支持函数式风格 GenericApplicationContext

    1. //函数式风格创建对象,交给 spring 进行管理
    2. @Test
    3. public void testGenericApplicationContext() {
    4. //1 创建 GenericApplicationContext 对象
    5. GenericApplicationContext context = new GenericApplicationContext();
    6. //2 调用 context 的方法对象注册
    7. context.refresh();
    8. context.registerBean("user1",User.class,() -> new User());
    9. //3 获取在 spring 注册的对象
    10. // User user = (User)context.getBean("com.atguigu.spring5.test.User");
    11. User user = (User)context.getBean("user1");
    12. System.out.println(user);
    13. }

    15.4.支持整合 JUnit5

    (1)整合 JUnit4
    第一步 引入 Spring 相关针对测试依赖
    image.png
    第二步 创建测试类,使用注解方式完成

    1. @RunWith(SpringJUnit4ClassRunner.class) //单元测试框架
    2. @ContextConfiguration("classpath:bean1.xml") //加载配置文件
    3. public class JTest4 {
    4. @Autowired
    5. private UserService userService;
    6. @Test
    7. public void test1() {
    8. userService.accountMoney();
    9. }
    10. }

    (2)Spring5 整合 JUnit5
    第一步 引入 JUnit5 的 jar 包
    image.png
    第二步 创建测试类,使用注解完成

    1. @ExtendWith(SpringExtension.class)
    2. @ContextConfiguration("classpath:bean1.xml")
    3. public class JTest5 {
    4. @Autowired
    5. private UserService userService;
    6. @Test
    7. public void test1() {
    8. userService.accountMoney();
    9. }
    10. }

    (3)使用一个复合注解替代上面两个注解完成整合

    1. @SpringJUnitConfig(locations = "classpath:bean1.xml")
    2. public class JTest5 {
    3. @Autowired
    4. private UserService userService;
    5. @Test
    6. public void test1() {
    7. userService.accountMoney();
    8. }
    9. }

    15.5.Webflux

    1、SpringWebflux 介绍
    (1)是 Spring5 添加新的模块,用于 web 开发的,功能和 SpringMVC 类似的,Webflux 使用当前一种比较流程响应式编程出现的框架。
    image.png
    (2)使用传统 web 框架,比如 SpringMVC,这些基于 Servlet 容器,Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的。
    (3)解释什么是异步非阻塞

  • 异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步

  • 阻塞和非阻塞针对被调用者,被调用者受到请求之后,做完请求任务之后才给出反馈就是阻塞,受到请求之后马上给出反馈然后再去做事情就是非阻塞

(4)Webflux 特点:

  • 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程
  • 函数式编程:Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求

(5)比较 SpringMVC
image.png

  • 两个框架都可以使用注解方式,都运行在 Tomet 等容器中
  • SpringMVC 采用命令式编程,Webflux 采用异步响应式编程

2、响应式编程(Java 实现)
(1)什么是响应式编程
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似”=B1+C1”的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
(2)Java8 及其之前版本
提供的观察者模式两个类 Observer 和 Observable

  1. public class ObserverDemo extends Observable {
  2. public static void main(String[] args) {
  3. ObserverDemo observer = new ObserverDemo();
  4. //添加观察者
  5. observer.addObserver((o,arg)->{
  6. System.out.println("发生变化");
  7. });
  8. observer.addObserver((o,arg)->{
  9. System.out.println("手动被观察者通知,准备改变");
  10. });
  11. observer.setChanged(); //数据变化
  12. observer.notifyObservers(); //通知
  13. }
  14. }

3、响应式编程(Reactor 实现)
(1)响应式编程操作中,Reactor 是满足 Reactive 规范框架
(2)Reactor 有两个核心类,Mono 和 Flux,这两个类实现接口 Publisher,提供丰富操作符。Flux 对象实现发布者,返回 N 个元素;Mono 实现发布者,返回 0 或者 1 个元素
(3)Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号:元素值,错误信号,完成信号,错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者
image.png
(4)代码演示 Flux 和 Mono

  1. <dependency>
  2. <groupId>io.projectreactor</groupId>
  3. <artifactId>reactor-core</artifactId>
  4. <version>3.1.5.RELEASE</version>
  5. </dependency>
  1. public static void main(String[] args) {
  2. //just 方法直接声明
  3. Flux.just(1,2,3,4);
  4. Mono.just(1);
  5. //其他的方法
  6. Integer[] array = {1,2,3,4};
  7. Flux.fromArray(array);
  8. List<Integer> list = Arrays.asList(array);
  9. Flux.fromIterable(list);
  10. Stream<Integer> stream = list.stream();
  11. Flux.fromStream(stream);
  12. }

(5)三种信号特点

  • 错误信号和完成信号都是终止信号,不能共存的
  • 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
  • 如果没有错误信号,没有完成信号,表示是无限数据流

(6)调用 just 或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的
image.png
(7)操作符
* 对数据流进行一道道操作,成为操作符,比如工厂流水线
第一 map 元素映射为新元素
image.png
第二 flatMap 元素映射为流
把每个元素转换流,把转换之后多个流合并大的流
image.png4、SpringWebflux 执行流程和核心 API
SpringWebflux 基于 Reactor,默认使用容器是 Netty,Netty 是高性能的 NIO 框架,异步非阻
塞的框架
(1)Netty
* BIO
image.png
NIO
image.png
(2)SpringWebflux 执行过程和 SpringMVC 相似的

  • SpringWebflux 核心控制器 DispatchHandler,实现接口 WebHandler
  • 接口 WebHandler 有一个方法

image.png
image.png
(3)SpringWebflux 里面 DispatcherHandler,负责请求的处理

  • HandlerMapping:请求查询到处理的方法
  • HandlerAdapter:真正负责请求处理
  • HandlerResultHandler:响应结果处理

(4)SpringWebflux 实现函数式编程,两个接口:RouterFunction(路由处理)和 HandlerFunction(处理函数)
5、SpringWebflux(基于注解编程模型)
SpringWebflux 实现方式有两种:注解编程模型和函数式编程模型
使用注解编程模型方式,和之前 SpringMVC 使用相似的,只需要把相关依赖配置到项目中,
SpringBoot 自动配置相关运行容器,默认情况下使用 Netty 服务器
第一步 创建 SpringBoot 工程,引入 Webflux 依赖
image.png
image.png
第二步 配置启动端口号
image.png
第三步 创建包和相关类

  • 实体类

image.png

  • 创建接口定义操作的方法

    1. //用户操作接口
    2. public interface UserService {
    3. //根据 id 查询用户
    4. Mono<User> getUserById(int id);
    5. //查询所有用户
    6. Flux<User> getAllUser();
    7. //添加用户
    8. Mono<Void> saveUserInfo(Mono<User> user);
    9. }
  • 接口实现类

    1. public class UserServiceImpl implements UserService {
    2. //创建 map 集合存储数据
    3. private final Map<Integer,User> users = new HashMap<>();
    4. public UserServiceImpl() {
    5. this.users.put(1,new User("lucy","nan",20));
    6. this.users.put(2,new User("mary","nv",30));
    7. this.users.put(3,new User("jack","nv",50));
    8. }
    9. //根据 id 查询
    10. @Override
    11. public Mono<User> getUserById(int id) {
    12. return Mono.justOrEmpty(this.users.get(id));
    13. }
    14. //查询多个用户
    15. @Override
    16. public Flux<User> getAllUser() {
    17. return Flux.fromIterable(this.users.values());
    18. }
    19. //添加用户
    20. @Override
    21. public Mono<Void> saveUserInfo(Mono<User> userMono) {
    22. return userMono.doOnNext(person -> {
    23. //向 map 集合里面放值
    24. int id = users.size()+1;
    25. users.put(id,person);
    26. }).thenEmpty(Mono.empty());
    27. }
    28. }
  • 创建 controller

    1. @RestController
    2. public class UserController {
    3. //注入 service
    4. @Autowired
    5. private UserService userService;
    6. //id 查询
    7. @GetMapping("/user/{id}")
    8. public Mono<User> geetUserId(@PathVariable int id) {
    9. return userService.getUserById(id);
    10. }
    11. //查询所有
    12. @GetMapping("/user")
    13. public Flux<User> getUsers() {
    14. return userService.getAllUser();
    15. }
    16. //添加
    17. @PostMapping("/saveuser")
    18. public Mono<Void> saveUser(@RequestBody User user) {
    19. Mono<User> userMono = Mono.just(user);
    20. return userService.saveUserInfo(userMono);
    21. }
    22. }
  • 说明

SpringMVC 方式实现,同步阻塞的方式,基于 SpringMVC+Servlet+Tomcat
SpringWebflux 方式实现,异步非阻塞 方式,基于 SpringWebflux+Reactor+Netty说明
6、SpringWebflux(基于函数式编程模型)
(1)在使用函数式编程模型操作时候,需要自己初始化服务器
(2)基于函数式编程模型时候,有两个核心接口:RouterFunction(实现路由功能,请求转发给对应的 handler)和 HandlerFunction(处理请求生成响应的函数)。核心任务定义两个函数式接口的实现并且启动需要的服务器。
( 3 ) SpringWebflux 请 求 和 响 应 不 再 是 ServletRequest 和 ServletResponse ,而是ServerRequest 和 ServerResponse
第一步 把注解编程模型工程复制一份 ,保留 entity 和 service 内容
第二步 创建 Handler(具体实现方法)

  1. public class UserHandler {
  2. private final UserService userService;
  3. public UserHandler(UserService userService) {
  4. this.userService = userService;
  5. }
  6. //根据 id 查询
  7. public Mono<ServerResponse> getUserById(ServerRequest request) {
  8. //获取 id 值
  9. int userId = Integer.valueOf(request.pathVariable("id"));
  10. //空值处理
  11. Mono<ServerResponse> notFound = ServerResponse.notFound().build();
  12. //调用 service 方法得到数据
  13. Mono<User> userMono = this.userService.getUserById(userId);
  14. //把 userMono 进行转换返回
  15. //使用 Reactor 操作符 flatMap
  16. return userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
  17. .body(fromObject(person)))
  18. .switchIfEmpty(notFound);
  19. }
  20. //查询所有
  21. public Mono<ServerResponse> getAllUsers() {
  22. //调用 service 得到结果
  23. Flux<User> users = this.userService.getAllUser();
  24. return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
  25. }
  26. //添加
  27. public Mono<ServerResponse> saveUser(ServerRequest request) {
  28. //得到 user 对象
  29. Mono<User> userMono = request.bodyToMono(User.class);
  30. return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
  31. }
  32. }

第三步 初始化服务器,编写 Router

  • 创建路由的方法

    1. //1 创建 Router 路由
    2. public RouterFunction<ServerResponse> routingFunction() {
    3. //创建 hanler 对象
    4. UserService userService = new UserServiceImpl();
    5. UserHandler handler = new UserHandler(userService);
    6. //设置路由
    7. return RouterFunctions.route(
    8. GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
    9. .andRoute(GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
    10. }
  • 创建服务器完成适配

    1. //2 创建服务器完成适配
    2. public void createReactorServer() {
    3. //路由和 handler 适配
    4. RouterFunction<ServerResponse> route = routingFunction();
    5. HttpHandler httpHandler = toHttpHandler(route);
    6. ReactorHttpHandlerAdapter adapter = new
    7. ReactorHttpHandlerAdapter(httpHandler);
    8. //创建服务器
    9. HttpServer httpServer = HttpServer.create();
    10. httpServer.handle(adapter).bindNow();
    11. }
  • 最终调用

    1. public static void main(String[] args) throws Exception{
    2. Server server = new Server();
    3. server.createReactorServer();
    4. System.out.println("enter to exit");
    5. System.in.read();
    6. }

    (4)使用 WebClient 调用

    1. public class Client {
    2. public static void main(String[] args) {
    3. //调用服务器地址
    4. WebClient webClient = WebClient.create("http://127.0.0.1:5794");
    5. //根据 id 查询
    6. String id = "1";
    7. User userresult = webClient.get().uri("/users/{id}", id)
    8. .accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class)
    9. .block();
    10. System.out.println(userresult.getName());
    11. //查询所有
    12. Flux<User> results = webClient.get().uri("/users")
    13. .accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
    14. results.map(stu -> stu.getName())
    15. .buffer().doOnNext(System.out::println).blockFirst();
    16. }
    17. }