Spring - 2( IOC、DI 配置管理 + 注解开发 + 注解开发配置管理 + Spring 整合 )

Spring - 2

IOC / DI 配置管理第三方 bean

数据源对象管理

实现 Druid 管理

  1. 导入依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>
  1. 配置第三方 bean
<?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">
    
	<!--管理DruidDataSource对象-->
    <bean 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>
</beans>
  1. 从 IOC 容器中获取对应的 bean 对象
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);
    }
}

实现 c3p0 管理

  1. 导入依赖
<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    <!-- 看mysql的版本 -->
</dependency>
  1. 配置第三方 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>
  • 数据连接池在配置属性的时候,除了可以注入数据库连接四要素外还可以配置很多其他的属性,具体都有哪些属性用到的时候再去查,一般配置基础的四个,其他都有自己的默认值
  • Druid 和 C3P0 在没有导入 mysql 驱动包的前提下,一个没报错一个报错,说明 Druid 在初始化的时候没有去加载驱动,而 C3P0 刚好相反
  • Druid 程序运行虽然没有报错,但是当调用 DruidDataSource 的 getConnection() 方法获取连接的时候,也会报找不到驱动类的错误
  1. 运行程序
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);
    }
}
  • 但是配置写到配置文件中不合适,应该单独拿出来

加载 properties 文件

  • 上述两个数据源中都使用到了一些固定的常量如数据库连接四要素,把这些值写在 Spring 的配置文件中不利于后期维护,需要将这些值提取到一个外部的 properties 配置文件中
  1. 在 resources 下创建一个 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
  1. 在 Spring 的配置文件中开 context 命名空间 ( 多了 5 个 context ),加载 properties 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!-- 加载jdbc.properties文件 -->
    <context:property-placeholder location="jdbc.properties"/>
    
    <!-- el表达式中的值是文件中的属性 -->
    <bean id="dataSource" 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>
</beans>
  • 但若是 property 标签的 name 指向值为 username 的话 ( jdbc.properties 也为 username ) 就可能用的不是 jdbc 文件里的值
    • 因为 <context:property-placeholder/> 标签会加载系统的环境变量,而且环境变量的值会被优先加载 ( 即 jdbc 里的 username 属性值被环境变量替代了 )
  • 解决:在 context:property-placeholder 标签的最后加上其他属性变为:<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
    • 设置为 NEVER,表示不加载系统属性
  1. 若是想加载多个 properties 文件:

    <!--方式一 -->
    <context:property-placeholder location="jdbc.properties,jdbc2.properties" system-properties-mode="NEVER"/>
    <!--方式二-->
    <context:property-placeholder location="*.properties" system-properties-mode="NEVER"/>
    <!--方式三 -->
    <context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
    <!--方式四-->
    <context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>
    
    • 方式一:仅加载多个
    • 方式二:加载所有以 properties 结尾的文件,但是不标准
    • 方式三:标准的写法,classpath: 代表的是从根路径下开始查找,但是只能查询当前项目的根路径
    • 方式四:不仅可以加载当前项目还可以加载当前项目所依赖的所有项目的根路径下的 properties 配置文件

核心容器 ( 补充 )

  • 这里所说的核心容器,大家可以把它简单的理解为 ApplicationContext

容器的创建方式

  • 加载类路径下的 XML 配置文件

    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
  • 加载文件系统下的 XML 配置文件 ( 知道就可 )

    ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\src\\main\\resources\\applicationContext.xml");
    
    

Bean 的三种获取方式

  • 就是之前案例中获取的方式,每次获取的时候都需要进行类型转换
  • 解决类型强转问题,但是参数又多加了一个,相对来说没有简化多少
  • 类似我们之前所学习依赖注入中的按类型注入,必须要确保 IOC 容器中该类型对应的 bean 对象只能有一个

容器类层次结构

  • 在 IDEA 中双击 shift 案件,输入 BeanFactory

  • 点击进入 BeanFactory 类,ctrl+h,就能查看到结构的层次关系

    image-20230821181837607

    • 容器类也是从无到有根据需要一层层叠加上来的,需要丰富功能了就加上子接口 ( 如关闭容器功能 ),重点理解设计思想

BeanFactory 的使用

public class AppForBeanFactory {
    public static void main(String[] args) {
        Resource resources = new ClassPathResource("applicationContext.xml");
        BeanFactory bf = new XmlBeanFactory(resources);
        BookDao bookDao = bf.getBean(BookDao.class);
        bookDao.save();
    }
}
  • 为了更好的看出 BeanFactoryApplicationContext 之间的区别,在 BookDaoImpl 添加如下构造函数:

    public class BookDaoImpl implements BookDao {
        public BookDaoImpl() {
            System.out.println("constructor");
        }
        public void save() {
            System.out.println("book dao save ..." );
        }
    }
    
  • 如果不去获取 bean 对象,打印会发现 bean 加载时机不一样

    // 不去获取 bean 对象
    // BeanFactory:
    Resource resources = new ClassPathResource("applicationContext.xml");
    BeanFactory bf = new XmlBeanFactory(resources);
    
    // ApplicationContext:
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
    • BeanFactory 是延迟加载,只有在获取 bean 对象的时候才会去创建 ( 构造器输出语句没打印,即表示构造方法没运行 )

    • ApplicationContext 是立即加载,容器加载的时候就会创建 bean 对象 ( 有打印 constructor )

    • ApplicationContext 要想成为延迟加载,只需要按照如下方式进行配置

      <!-- 加上lazy-init="true" -->
      <bean id="bookDao" class="com.qst.dao.impl.BookDaoImpl"  lazy-init="true"/>
      </beans>
      

容器相关总结

  • BeanFactory 是 IOC 容器的顶层接口,初始化 BeanFactory 对象时,加载的 bean 延迟加载
  • ApplicationContext 接口是 Spring 容器的核心接口,初始化时 bean 立即加载
  • ApplicationContext 接口提供基础的 bean 操作相关方法,通过其他接口扩展其功能
  • ApplicationContext 接口常用初始化类
    • ClassPathXmlApplicationContext ( 常用 )
    • FileSystemXmlApplicationContext

注解开发

IOC / DI 注解开发

  • 2.0 版开始支持注解
  • 2.5 版注解功能趋于完善
  • 3.0 版支持纯注解开发

环境配置

  • 创建一个 Maven 项目
  • pom.xml 添加 Spring 的依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
  • resources 下添加 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="com.qst.dao.impl.BookDaoImpl"/>
</beans>
  • 添加 BookDao、BookDaoImpl、BookService、BookServiceImpl 类
public interface BookDao {
    public void save();
}
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ..." );
    }
}
public interface BookService {
    public void save();
}

public class BookServiceImpl implements BookService {
    public void save() {
        System.out.println("book service save ...");
    }
}
  • 创建运行类 App
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
    }
}

注解开发定义 bean

  1. 删除原 XML 配置:

    <bean id="bookDao" class="com.qst.dao.impl.BookDaoImpl"/>
    
  2. Dao 上添加注解

    @Component("bookDao")
    public class BookDaoImpl implements BookDao {
        public void save() {
            System.out.println("book dao save ..." );
        }
    }
    
    • 注意:@Component 注解不可以添加在接口上,因为接口是无法创建对象的
  3. 配置 Spring 的注解包扫描

    <context:component-scan base-package="com.itheima"/>
    </beans>
    
    • component-scan

      • component:组件,Spring 将管理的 bean 视作自己的一个组件
      • scan:扫描
    • base-package 指定 Spring 框架扫描的包路径,它会扫描指定包及其子包中的所有类上的注解

      • 包路径越长 ( 即越详细 ),扫描的范围越小速度越快
      • 包路径越少,扫描的范围越大速度越慢
      • 一般扫描到项目的组织名称即 Maven 的 groupId 下 ( 如:com.qst ) 即可
  4. Service 上添加注解 ( 在 BookServiceImpl 类上也添加 @Component 交给 Spring 框架管理 )

    @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();
        }
    }
    
  5. 在 App 类中,从 IOC 容器中获取 BookServiceImpl 对应的 bean 对象,打印

    public class App {
        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);
        }
    }
    
  • 注:

    • BookServiceImpl 类没有起名称,所以在 App 中是按照类型来获取 bean 对象

    • @Component 注解如果不起名称,会有一个默认值就是当前类名首字母小写,所以也可以按照名称获取,如

      BookService bookService = (BookService)ctx.getBean("bookServiceImpl");
      System.out.println(bookService);
      
  • 对于 @Component 注解,还衍生出了其他三个注解 @Controller@Service@Repository,但这三个注解和 @Component 注解的作用是一样的

    • 而衍生出三个出来是方便我们后期在编写类的时候能很好的区分出这个类是属于表现层、业务层还是数据层的类,即:
    注解 主要作用于xx层bean定义
    @Controller 表现层
    @Service 业务层
    @Repository 数据层
    • @Component 虽然是通用的,但主要是在注解不属于各种归类的时候 ( 如工具类 ) 所用

纯注解开发模式

  • 一点配置文件都不写了
  1. 创建配置类 SpringConfig

    public class SpringConfig {
    }
    
  2. 标识该类为配置类

    • 在配置类上添加 @Configuration 注解,将其标识为一个配置类,替换 applicationContext.xml

      @Configuration
      public class SpringConfig {
      }
      
  3. 用注解替换包扫描配置

    • 在配置类上添加包扫描注解 @ComponentScan 替换 <context:component-scan base-package=""/>

      @Configuration 
      @ComponentScan("com.qst") 
      public class SpringConfig {
      }
      
      • @Configuration 就代表了上述注解开发定义 bean 中的配置文件中,除了扫描以外的所有语句
      • @ComponentScan 代替了配置文件的扫描功能
  4. 创建新的运行类 AppForAnnotation 并执行

    public class AppForAnnotation {
    
        public static void main(String[] args) {
            ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); // 换接口AnnotationConfigApplicationContext,加载的也就不是配置文件了,而是类
            
            BookDao bookDao = (BookDao) ctx.getBean("bookDao");
            System.out.println(bookDao);
            BookService bookService = ctx.getBean(BookService.class);
            System.out.println(bookService);
        }
    }
    
  5. 运行结果与上述注解开发定义 bean 一致

  • 注:

    • @Configuration 注解用于设定当前类为配置类

    • @ComponentScan 注解用于设定扫描路径,此注解只能添加一次,相加多个就用数组的格式

      @ComponentScan({com.qst.service","com.qst.dao"})
      
    • 读取 Spring 核心配置文件初始化容器对象切换为读取 Java 配置类初始化容器对象,换接口 AnnotationConfigApplicationContext

      //加载配置文件初始化容器
      ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
      
      //加载配置类初始化容器
      ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
      

注解开发 bean 作用范围与生命周期管理

Bean 的作用范围

  • 先运行 App 类,在控制台打印两个一摸一样的地址,说明默认情况下 bean 是单例

    BookDao bookDao1 = ctx.getBean(BookDao.class);
    BookDao bookDao2 = ctx.getBean(BookDao.class);
    System.out.println(bookDao1);
    System.out.println(bookDao2);
    
  • 要想将 BookDaoImpl 变成非单例,只需要在其类上添加 @scope 注解

    @Repository
    
    //@Scope设置bean的作用范围
    @Scope("prototype")
    public class BookDaoImpl implements BookDao {
    
        public void save() {
            System.out.println("book dao save ...");
        }
    }
    
    • 默认值 singleton ( 单例 ),可选值 prototype ( 非单例 )
  • 再次执行 App 类,打印结果就是两个不一样的地址了

Bean 的生命周期

  • 在 BookDaoImpl 中添加两个方法,initdestroy,方法名可以任意

    @Repository
    public class BookDaoImpl implements BookDao {
        public void save() {
            System.out.println("book dao save ...");
        }
        public void init() {
            System.out.println("init ...");
        }
        public void destroy() {
            System.out.println("destroy ...");
        }
    }
    
  • 如何对方法进行标识,哪个是初始化方法,哪个是销毁方法:只需要在对应的方法上添加 @PostConstruct@PreDestroy 注解即可

    • @PostConstruct 和 @PreDestroy 注解如果找不到,需要导入下面的 jar 包

      <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>javax.annotation-api</artifactId>
        <version>1.3.2</version>
      </dependency>
      
      • 从 JDK9 以后 jdk 中的 javax.annotation 包被移除了,这两个注解刚好就在这个包中
    @Repository
    public class BookDaoImpl implements BookDao {
        public void save() {
            System.out.println("book dao save ...");
        }
        @PostConstruct //在构造方法之后执行,替换 init-method
        public void init() {
            System.out.println("init ...");
        }
        
        @PreDestroy //在销毁方法之前执行,替换 destroy-method
        public void destroy() {
            System.out.println("destroy ...");
        }
    }
    
    • PostConstruct:构造方法之后 ( post- 前缀,表示 ...... 之后 )
    • PreDestroy:销毁方法之前 ( pre- 前缀,表示 ...... 之前 )
  • 要想看到两个方法执行,需要注意的是 destroy 只有在容器关闭的时候,才会执行,所以需要修改 App 的类

    public class App {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
            BookDao bookDao1 = ctx.getBean(BookDao.class);
            BookDao bookDao2 = ctx.getBean(BookDao.class);
            System.out.println(bookDao1);
            System.out.println(bookDao2);
            ctx.close(); //关闭容器
        }
    }
    
  • 运行查看打印结果,证明 init 和 destroy 方法都被执行了

注解开发依赖注入 - 自动注入

  • Spring 为了使用注解简化开发,并没有提供构造函数注入、setter 注入对应的注解,只提供了自动装配的注解实现
  1. 添加 spring 依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
  1. 添加一个配置类 SpringConfig
@Configuration
@ComponentScan("com.qst")
public class SpringConfig {
}
  1. 添加 BookDao、BookDaoImpl、BookService、BookServiceImpl 类
    • 在 BookServiceImpl 类的 bookDao 属性上添加 @Autowired 注解
public interface BookDao {
    public void save();
}
@Repository
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ..." );
    }
}


public interface BookService {
    public void save();
}
@Service
public class BookServiceImpl implements BookService {
    
    // 自动注入注解:
    @Autowired
    private BookDao bookDao;
    
//	public void setBookDao(BookDao bookDao) {
//        this.bookDao = bookDao;
//    }
    
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}
  1. 创建运行类 App
public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookService bookService = ctx.getBean(BookService.class);
        bookService.save();
    }
}
  • 注:

    • @Autowired 可以写在属性上,也可也写在 setter 方法上,最简单的处理方式是:写在属性上并将 setter 方法删除掉
    • 为什么 setter 方法可以删除:
      • 自动装配基于反射设计创建对象并通过暴力反射为私有属性进行设值
      • 普通反射只能获取 public 修饰的内容
      • 暴力反射除了获取 public 修饰的内容还可以获取 private 修改的内容
      • 所以此处无需提供 setter 方法
    • 自动装配建议使用无参构造器方法创建对象 ( 默认 ),否则无法造出对象
      • 如果不提供对应构造器方法,请提供唯一的构造方法
  • @Autowired 是按照类型注入,那么对应 BookDao 接口如果有多个实现类,比如添加了 BookDaoImpl2,此时按照类型注入就无法区分到底注入哪个对象

    • 所以要改为按照名称注入,要先给两个 Dao 类分别起个名称

      @Repository("bookDao")
      public class BookDaoImpl implements BookDao {
          public void save() {
              System.out.println("book dao save ..." );
          }
      }
      
      @Repository("bookDao2")
      public class BookDaoImpl2 implements BookDao {
          public void save() {
              System.out.println("book dao save ...2" );
          }
      }
      
      • 此时就可以注入成功,但这里是因为变量名叫 bookDao 而容器中也有一个 booDao,所以可以成功注入
      • 若是名称不同无对应,就要根据下面的方法:注解实现按照名称注入

注解实现按照名称注入

  • 当根据类型在容器中找到多个 bean,注入参数的属性名又和容器中 bean 的名称不一致,这个时候该如何解决,就需要使用到 @Qualifier 来指定注入哪个名称的 bean 对象,如:

    @Service
    public class BookServiceImpl implements BookService {
        // 两个注解一起使用
        @Autowired
        @Qualifier("bookDao1")
        private BookDao bookDao;
        
        public void save() {
            System.out.println("book service save ...");
            bookDao.save();
        }
    }
    

简单数据类型注入

  • 简单类型注入的是基本数据类型或者字符串类型,下面在 BookDaoImpl 类中添加一个 name 属性,用其进行简单类型注入

    • 数据类型换了,对应的注解也要跟着换,这次使用 @Value 注解,将值写入注解的参数中就行了
    @Repository("bookDao")
    public class BookDaoImpl implements BookDao {
        
        @Value("qst")
        private String name;
        
        public void save() {
            System.out.println("book dao save ..." + name);
        }
    }
    
    • 注意数据格式要匹配,如将 "abc" 注入给 int 值,这样程序就会报错
  • 其注解存在的意义在于下面的读取 properties 配置文件

注解读取 properties 配置文件

  • @Value 一般会被用在从 properties 配置文件中读取内容进行使用
  1. resource 下准备 properties 文件

    # jdbc.properties 中
    name=zhuyazhu
    
  2. 使用注解加载 properties 配置文件,在配置类上添加 @PropertySource 注解

    @Configuration
    @ComponentScan("com.qst")
    // 加载配置文件
    @PropertySource("jdbc.properties")
    public class SpringConfig {
    }
    
  3. 使用 @Value 读取配置文件中的内容

    @Repository("bookDao")
    public class BookDaoImpl implements BookDao {
        @Value("${name}")
        private String name;
        public void save() {
            System.out.println("book dao save ..." + name);
        }
    }
    
  • 注意:

    • 如果读取的 properties 配置文件有多个,可以使用 @PropertySource 的属性来指定多个 ( 大括号括起来 )

      @PropertySource({"jdbc.properties","xxx.properties"})
      
    • @PropertySource 注解属性中不支持在任何地方使用通配符 *,运行会报错

      @PropertySource({"*.properties"})
      
      • 报错:找不到文件,即其将星号当作了文件名
    • @PropertySource 注解属性中可以把 classpath: 加上,代表从当前项目的根路径找文件

      @PropertySource({"classpath:jdbc.properties"})
      

IOC / DI 注解开发管理第三方 bean

环境准备

  1. 依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
  2. 添加一个配置类 SpringConfig

    @Configuration
    public class SpringConfig {
    }
    
  3. 添加 BookDao、BookDaoImpl 类

    public interface BookDao {
        public void save();
    }
    @Repository
    public class BookDaoImpl implements BookDao {
        public void save() {
            System.out.println("book dao save ..." );
        }
    }
    
  4. 创建运行类 App

    public class App {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        }
    }
    

注解开发管理第三方 bean

  1. 导入对应的 jar 包

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.16</version>
    </dependency>
    
  2. 在配置类 SpringConfig 中添加一个方法,并在方法上添加 @Bean 注解

    • 注意该方法的返回值就是要创建的 Bean 对象类型
    • @Bean 注解的作用是将方法的返回值制作为 Spring 管理的一个 bean 对象
      • @Bean("dataSource") 命名,但写的不多
    @Configuration
    public class SpringConfig {
        
    	@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;
        }
    }
    
    • 注意:不能使用 DataSource ds = new DruidDataSource(),因为 DataSource 接口中没有对应的 setter 方法来设置属性
  3. 从 IOC 容器中获取对象并打印

    public class App {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
            DataSource dataSource = ctx.getBean(DataSource.class);
            System.out.println(dataSource);
        }
    }
    // 打印初始化成功
    
    • 至此使用 @Bean 来管理第三方 bean 的案例就已经完成
    • 如果有多个 bean 要被 Spring 管理,直接在配置类中多些几个方法,方法上都添加 @Bean 注解即可

引入外部配置类

  • 如果把所有的第三方 bean 都配置到 Spring 的配置类 SpringConfig 中,虽然可以,但是不利于代码阅读和分类管理,所有我们就按照类别将这些 bean 配置到不同的配置类中

  • 对于数据源的 bean,我们新建一个 JdbcConfig 配置类,并把数据源配置到该类下

    public class JdbcConfig {
    	@Bean
        public DataSource dataSource(){
            DruidDataSource ds = new DruidDataSource();
            ds.setDriverClassName("com.mysql.jdbc.Driver");
            ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
            ds.setUsername("root");
            ds.setPassword("root");
            return ds;
        }
    }
    
    • 要想办法让配置类能被 Spring 配置类加载到,并创建 DataSource 对象在 IOC 容器中

使用包扫描引入

  • 在 Spring 的配置类上添加包扫描

    @Configuration
    @ComponentScan("com.qst.config")
    public class SpringConfig {	
    }
    
  • 在 JdbcConfig 上添加配置注解

    • JdbcConfig 类要放入到 com.qst.config 包下,需要被 Spring 的配置类扫描到即可
    @Configuration
    public class JdbcConfig {
        // ......与先前相同,只是多加了一个注解
    }
    
  • 但此方法并不推荐使用

使用 @Import 引入

  • 这种方案可以不用加 @Configuration 注解,但是必须在 Spring 配置类上使用 @Import 注解手动引入需要加载的配置类
  1. 去除 JdbcConfig 类上的注解

    public class JdbcConfig {
    	@Bean
        public DataSource dataSource(){
            DruidDataSource ds = new DruidDataSource();
            ds.setDriverClassName("com.mysql.jdbc.Driver");
            ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
            ds.setUsername("root");
            ds.setPassword("root");
            return ds;
        }
    }
    
  2. 在 Spring 配置类中引入

    @Configuration
    //@ComponentScan("com.qst.config")
    // 注意多个用数组,但单个也可以数组形式,所以直接就都用数组形式即可
    @Import({JdbcConfig.class})
    public class SpringConfig {
    }
    
  • 注意:

    • 扫描注解可以移除

    • @Import 参数需要的是一个数组,可以引入多个配置类。

    • @Import 注解在配置类中只能写一次,下面的方式是不允许的

      // ......
      @Import(JdbcConfig.class)
      @Import(Xxx.class)
      // ......
      
  • 此方式可以精准看出都有哪些配置文件,而包扫描的方式看不出来

注解开发实现为第三方 bean 注入资源

  • 在使用 @Bean 创建 bean 对象的时候,方法在创建的过程中需要其他资源,这些资源会有两大类,分别是简单数据类型和引用数据类型

简单数据类型

  • 数据库的四要素不应该写死在代码中,应该是从 properties 配置文件中读取
  1. 类中提供四个属性,使用 @Value 注解引入值 ( 这里还是写在了代码里 )

    public class JdbcConfig {
        
        @Value("com.mysql.jdbc.Driver")
        private String driver;
        @Value("jdbc:mysql://localhost:3306/spring_db")
        private String url;
        @Value("root")
        private String userName;
        @Value("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;
        }
    }
    
  2. resources 目录下添加 jdbc.properties

  3. 配置文件中提供四个键值对分别是数据库的四要素

  4. 使用 @PropertySource 加载 jdbc.properties 配置文件

  5. 修改 @Value 注解属性的值,将其修改为 ${key},key 就是键值对中的键的值

  • 上面的 2、3、4、5 操作都与先前:“ 注解读取 properties 配置文件 “ 一节一样操作

引用数据类型

  • 假设在构建 DataSource 对象的时候,需要用到 BookDao 对象,该如何把 BookDao 对象注入进方法
  1. 在 SpringConfig 中扫描 BookDao

    • BookDaoImpl 类已用 @Repository 注解
    @Configuration
    @ComponentScan("com.qst.dao")
    @Import({JdbcConfig.class})
    public class SpringConfig {
    }
    
  2. 在 JdbcConfig 类的方法上添加参数

    @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 定义方法设置形参即可,容器会根据类型自动装配对象
    • 因为检测到了 @Bean 就认为要提供形参列表中的形参,然后就会在容器中自行找此 BookDao 类型的 bean 然后注入

Spring 整合

起初的 mybatis 基本操作

  1. 创建项目导入 jar 包

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
    </dependencies>
    
  2. 根据表创建模型类 ( 映射表的类对象 public class Account )

  3. 创建 Dao 接口、创建 Service 接口和实现类

    • 此处直接用注解方式,dao 无实现类,即使用自动代理 mapper 所做

      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);
      }
      
  4. 添加 jdbc.properties 文件

    • mysql5:driver=org.gjt.mm.mysql.Driver
    • mysql8:driver=com.mysql.cj.jdbc.Driver
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
    jdbc.username=root
    jdbc.password=root
    
  5. 添加 Mybatis 核心配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        
        <!--读取外部properties配置文件-->
        <properties resource="jdbc.properties"></properties>
        
        <!--别名扫描的包路径-->
        <typeAliases>
            <package name="com.qst.domain"/>
        </typeAliases>
        
        <!--数据源-->
        <environments default="mysql">
            <environment id="mysql">
                <transactionManager type="JDBC"></transactionManager>
                <dataSource type="POOLED">
                    <property name="driver" value="${jdbc.driver}"></property>
                    <property name="url" value="${jdbc.url}"></property>
                    <property name="username" value="${jdbc.username}"></property>
                    <property name="password" value="${jdbc.password}"></property>
                </dataSource>
            </environment>
        </environments>
        
        <!--映射文件扫描包路径-->
        <mappers>
            <package name="com.qst.dao"></package>
        </mappers>
    </configuration>
    
    • 其中取别名部分:

      给类取别名,mapper里就可以直接用User1了,别名都不区分大小写    
      <typeAlias type="com.qut.bean.Users" alias="User1" />
      
      或者不取别名,直接默认是 Users/users    
      <typeAlias type="com.qut.bean.Users" />
      
      还觉得不方便可以直接给包下面的所有的类都起别名    
      <package name="com.qut.bean"/>
      
  6. 编写应用程序

    public class App {
        public static void main(String[] args) throws IOException {
            // 1. 创建SqlSessionFactoryBuilder对象
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            // 2. 加载SqlMapConfig.xml配置文件
            InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml.bak");
            // 3. 创建SqlSessionFactory对象
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
            
            // 4. 获取SqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession();
            
            // 5. 执行SqlSession对象执行查询,获取结果User
            AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
    
            Account ac = accountDao.findById(1);
            System.out.println(ac);
    
            // 6. 释放资源
            sqlSession.close();
        }
    }
    

Spring 整合 mybatis

  • Spring 是来管理 bean 的,要想整合,就查看上述起初的基本操作中,哪些对象可以交给 Spring 来管理
  • Spring 要管理 MyBatis 中的 SqlSessionFactory 还有 Mapper 接口的扫描
  1. 项目中再导入整合需要的 jar 包

    <!-- 此处用druid数据源 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.16</version>
    </dependency>
    <dependency>
        <!--Spring操作数据库需要该jar包-->
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
        <!--
      Spring与Mybatis整合的jar包
      这个jar包mybatis在前面,是Mybatis提供的
     -->
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.3.0</version>
    </dependency>
    
    • 此处的 mybatis-spring 要与之前的 mybatis 依赖相对应,否则可能无法整合
  2. 创建 Spring 的主配置类

    //配置类注解
    @Configuration
    //包扫描,主要扫描的是项目中的AccountServiceImpl类
    @ComponentScan("com.qst")
    public class SpringConfig {
    }
    
  3. 创建数据源的配置类 JdbcConfig

    public 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;
        }
    }
    
  4. 主配置类中读 properties 并引入数据源配置类

    @Configuration
    @ComponentScan("com.qst")
    
    @PropertySource("classpath:jdbc.properties")
    @Import(JdbcConfig.class)
    
    public class SpringConfig {
    }
    
  5. 创建 Mybatis 配置类并配置 SqlSessionFactory

    // import javax.sql.DataSource;
    
    public class MybatisConfig {
    
        //定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象(若是用SqlSessionFactory的话会同xml中一样set....初始化好,比较麻烦,这里提供了更简便的)
    //   此处的参数dataSource已在JdbcConfig类中返回了DataSource类型的Bean
        @Bean
        public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
            SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
            //设置模型类的别名扫描,代替xml中的typeAliases部分
            ssfb.setTypeAliasesPackage("com.qst.learn.domain");
            //设置数据源,替代了xml中的dataSource部分
            ssfb.setDataSource(dataSource);
            return ssfb;
        }
    
        //定义bean,返回MapperScannerConfigurer对象
        //替代了xml中映射文件扫描包路径的<mappers>标签部分
        @Bean
        public MapperScannerConfigurer mapperScannerConfigurer(){
            MapperScannerConfigurer msc = new MapperScannerConfigurer();
            msc.setBasePackage("com.qst.learn.dao");
            return msc;
        }
    }
    
    • SqlSessionFactoryBean 是前面讲解 FactoryBean 的一个子类,在该类中将 SqlSessionFactory 的创建进行了封装,简化对象的创建,只需要将其需要的内容设置即可
    • 方法中有一个参数为 dataSource,当前 Spring 容器中已经创建了 Druid 数据源,类型刚好是 DataSource 类型 ( JdbcConfig 中的 @Bean ),此时在初始化 SqlSessionFactoryBean 这个对象的时候,发现需要使用 DataSource 对象,而容器中刚好有这么一个对象,就自动加载了 DruidDataSource 对象
      • @Bean 就是将方法的返回值制作为 Spring 管理的一个 bean 对象
    • MapperScannerConfigurer 会扫描这个包中的所有接口,把每个接口都执行一次 getMapper 方法,得到每个接口的 dao 对象 ,创建好的 dao 对象放入到 spring 的容器中
  6. 主配置类中引入 Mybatis 配置类

    //配置类注解
    @Configuration
    //包扫描,主要扫描的是项目中的AccountServiceImpl类
    @ComponentScan("com.qst.learn")
    
    @PropertySource("classpath:jdbc.properties")
    @Import({JdbcConfig.class, MybatisConfig.class})
    
    public class SpringConfig {
    }
    
  7. 编写运行类,在运行类中,从 IOC 容器中获取 Service 对象,调用方法获取结果

    public static void main(String[] args) throws IOException {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    
        AccountService accountService = ctx.getBean(AccountService.class);
    
        Account ac = accountService.findById(2);
        System.out.println(ac);
    }
    
  • 支持 Spring 与 Mybatis 的整合就已经完成了,其中主要用到的两个类分别是:

    • SqlSessionFactoryBean
    • MapperScannerConfigurer

Spring 整合 Junit

  1. 引入依赖

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
  2. 编写测试类,在 test\java 下创建一个 Test 类

    • 下面的类运行器和设置 Spring 环境对应的配置类一般都是固定不变
    //设置类运行器
    @RunWith(SpringJUnit4ClassRunner.class)
    //设置Spring环境对应的配置类
    @ContextConfiguration(classes = {SpringConfig.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());
        }
    }
    
  • 注意:
    • 单元测试,如果测试的是注解配置类,则使用 @ContextConfiguration(classes = 配置类.class)
    • 单元测试,如果测试的是配置文件,则使用 @ContextConfiguration(locations={配置文件名,...})
    • Junit 运行后是基于 Spring 环境运行的,所以 Spring 提供了一个专用的类运行器,这个务必要设置,这个类运行器就在 Spring 的测试专用包中提供的,导入的坐标就是这个东西 SpringJUnit4ClassRunner
    • 上面两个配置都是固定格式,当需要测试哪个 bean 时,使用自动装配加载对应的对象,下面的工作就和以前做 Junit 单元测试完全一样了
posted @ 2023-09-16 19:14  朱呀朱~  阅读(9)  评论(0编辑  收藏  举报