Spring<02>IOC和DI注解开发

1. Spring数据源

1.1 数据源(连接池)的作用(理解)

  1. 普通的JDBC连接数据库每次向数据库建立连接的时候都将connection加载到内存,再验证用户名等信息,这样会消耗一定的时间,每次的数据库连接,使用完后再断开,这样的方式会消耗大量的资源和时间。同时上千人访问的话将占用很多系统资源,导致服务器崩溃。
  2. 数据库连接池其实就是一个为数据库连接建立的一个“缓存池”,预先在数据库连接池中放入一定数量的连接。当需要数据库连接时,从连接池中拿就是了,用完再放回。数据库连接池负责分配、管理、释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立。
  3. 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中(initialPoolSize)。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。
  4. 连接池的最大数据库连接数量限定了这个连接池占有的最大连接数(maxPoolSize)。当应用程序向连接池请求的连接数超过最大连接数时,这些请求将加入到等待队列中。
  5. 数据库连接池相对于无连接池的优点
    资源重用,避免频繁创建
    事先实例化数据源,初始化部分连接资源
    提高程序的性能
    实现某一应用最大可用数据库连接数的限制避免某一应用独占所有的数据库资源
    维护连接资源:使用连接资源时从数据源获取,使用完毕后将连接归还给数据源。这样统一的连接管理,避免数据库连接泄露。
  6. 常见的数据源(连接池):DBCP、C3P0、BoneCP、Druid等。

接下来学习数据源(第三方jar包)在Spring容器中如何实现Ioc的

学习思路:从手动创建到自动创建并注入

1.2 Spring使用数据源开发步骤(应用)*

① 导入数据源的坐标和数据库驱动坐标

② 创建数据源对象

③ 设置数据源的基本连接数据

④ 通过数据源获取连接资源和归还连接资源

1.3 手动创建数据源(理解)

1)手动创建c3p0、druid数据源并使用

① 导入c3p0、druid的坐标

<!-- 导入C3p0坐标 -->
<dependency>
	<groupId>c3p0</groupId>
	<artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>
<!-- 导入druid坐标 -->
<dependency>
	<groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>

导入MySql数据库驱动和junit单元测试坐标

<!-- 导入MySql数据库驱动坐标-->
<dependency>
	<groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.32</version>
</dependency>
<!-- 导入Junit单元测试坐标 -->
<dependency>
	<groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

②创建c3p0数据源对象

③设置数据源的基本连接数据

④ 通过数据源获取连接和归还连接

@Test
public void test1(){
    //②创建c3p0数据源对象
	ComboPooledDataSource dataSource = new ComboPooledDataSource();
    //③设置数据源的基本连接数据
    dataSource.setDriverClass("com.mysql.jdbc.Driver");
    dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/maven");
    dataSource.setUser("root");
    dataSource.setPassword("135246");
    
    //④通过数据源获取连接和归还连接
    Connection con = dataSource.getConnection();
    System.out.println(con);
	con.close();
}

控制台输出连接对象字符串形式地址值,表示已经成功创建连接池并获取了连接对象。

com.mchange.v2.c3p0.impl.NewProxyConnection@59a6e353

②创建druid数据源对象

③设置数据源的基本连接数据

④ 通过数据源获取连接和归还连接

@Test
public void test1(){
    //②创建druid数据源对象
    DruidDataSource dataSource = new DruidDataSource();

    //③ 设置数据源的基本连接数据
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql:///maven");
    dataSource.setUsername("root");
    dataSource.setPassword("135246");

    //④ 获取连接、使用并归还
    Connection con = dataSource.getConnection();
    System.out.println(con);
    con.close();
}

控制台输出连接对象字符串形式地址值,表示已经成功创建连接池并获取了连接对象。

com.mysql.jdbc.JDBC4Connection@7006c658

2)将数据源的基本连接信息抽取到配置文件以实现解耦(以c3p0为例)(应用

① 导入c3p0、druid的坐标 - 不变

导入MySql数据库驱动和junit单元测试坐标 - 不变

② 创建c3p0数据源对象 - 不变

@Test
public void test3(){
    //②创建c3p0数据源对象
	ComboPooledDataSource dataSource = new ComboPooledDataSource();
}

③ 提取数据源的基本连接信息到配置文件jdbc.properties(名字任意,格式为properties)

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/maven
jdbc.username=root
jdbc.password=135246

④ 使用java.util提供的ResourceBundle类读取properties文件内容,参数为文件基名

//使用java.util提供的ResourceBundle类读取properties文件内容,参数为文件基名
ResourceBundle rsb = ResourceBundle.getBundle("jdbc");

⑤ 从ResourceBundle对象中取得各项参数并设置进dataSource

//从ResourceBundle对象中取得各项参数并设置进dataSource
dataSource.setDriverClass(rsb.getString("jdbc.driver"));
dataSource.setJdbcUrl(rsb.getString("jdbc.url"));
dataSource.setUser(rsb.getString("jdbc.username"));
dataSource.setPassword(rsb.getString("jdbc.password"));

//获取连接,使用关闭连接
Connection conn = dataSource.getConnection();
System.out.println(conn);
conn.close();

以上就是在Spring中手动注入数据源的全部内容。接下来我们会学习如何自动注入。

1.4 Spring创建并自动注入数据源(Ioc)-c3p0(应用)

上面的C3P0、Druid连接池都是我们手动创建并使用的,根据创建的思路我们考虑到可以把创建交给Spring容器:

① DataSource有默认无参构造,而Spring实例化Bean的最常见方式就是使用Bean的无参构造

② DataSource使用set方法设置连接的基本信息,而Spring可以通过set方法进行字符串注入

这样就实现了数据源对象在Spring容器中的Ioc:

步骤如下:

① 在项目/模块pom.xml中导入Spring-context的坐标

<!-- 导入Spring-context坐标-->
<dependency>
	<groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

<!-- 导入MySql数据库驱动坐标-->
<dependency>
	<groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.32</version>
</dependency>
<!-- 导入Junit单元测试坐标 -->
<dependency>
	<groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

<!-- 导入C3p0坐标 -->
<dependency>
	<groupId>c3p0</groupId>
	<artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>

编写DataSource类并在类中添加属性及其set方法(已经存在,不需要我们编写)

② 在Spring核心配置文件(applicationContext.xml)中添加Bean标签,关联DataSource类。

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"></bean>

③ 在Spring核心配置文件的DataSource的Bean标签下添加属性,实现自动注入

property的name属性名称通过set方法截取;因为属性值为String,所以通过value属性设置。

<!-- 配置C3P0数据源并自动注入连接基本参数 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- property的name属性名称通过set方法截取;因为属性值为String,所以通过value属性设置 -->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/maven"></property>
        <property name="user" value="root"></property>
        <property name="password" value="135246"></property>
    </bean>

通过上述配置,在创建C3P0数据源对象时,连接的基本配置信息会自动注入进该数据源对象,可以直接获取连接操作数据库。

//将数据源对象创建权转交给Spring容器,并同时实现连接基本配置信息自动注入
@Test
public void test4() throws SQLException {
    //获取
    ApplicationContext ap = new ClassPathXmlApplicationContext(
        "applicationContext.xml");
    //由原来的手动newdataSource对象,变为通过ApplicationContext提供对象
    ComboPooledDataSource dataSource = (ComboPooledDataSource)ap.getBean(
        "dataSource");
    Connection conn = dataSource.getConnection();
    System.out.println(conn);
    conn.close();
}

1.5 抽取jdbc配置文件*(应用)

有关jdbc的连接基本配置信息,最好不要直接配置在Spring的配置文件中。这个时候就要把这些信息抽取出来,放在一个单独的配置文件中jdbc.properties,文件内容如下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/maven
username=root
password=135246

接下来要让jdbc.properties被Spring核心配置文件applicationContext.xml文件引用,步骤如下:

首先,需要在Spring核心配置文件中引入context命名空间和约束文件路径:

命名空间:xmlns:context=“http://www.springframework.org/schema/context”
约束路径:http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd

小技巧:复制并修改bean命名空间及约束文件路径即可

然后,引入properties文件,并修改DataSource注入基本参数的配置,具体如下:

<!-- 通过context命名空间的标签,导入jdbc.properties文件。classpath表示文件在类路径下 -->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 配置C3P0数据源并自动注入连接基本参数 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <!--
       property的name属性名称通过set方法截取;因为属性值为String,所以通过value属性设置
        通过${key}的方式可以从properties文件中读取key对应的值。
      -->
    <property name="driverClass" value="${jdbc.driver}"></property>
    <property name="jdbcUrl" value="${jdbc.url}"></property>
    <property name="user" value="${user}"></property>
    <property name="password" value="${password}"></property>
</bean>

最后,使用抽取前的java代码运行即可。

//将数据源对象创建权转交给Spring容器,并同时实现连接基本配置信息自动注入
@Test
public void test4() throws Exception {
    //获取
    ApplicationContext ap = new ClassPathXmlApplicationContext(
        "applicationContext.xml");
    //由原来的手动newdataSource对象,变为通过ApplicationContext提供对象
    DataSource dataSource = ap.getBean(
        DataSource.class);
    Connection conn = dataSource.getConnection();
    System.out.println(conn);
    conn.close();
}

*上述代码中,数据源的获取方式也是我们通过代码手动从Spring容器中获取的。生产环境中,会同时将持久层配置进Spring容器,并将DataSource自动注入进持久层中。大致思路如下:

①编写持久层代码,添加DataSource属性及其set方法

② 将持久层配置进Spring核心配置文件

③ 将DataSource配置进Spring核心配置文件,同时自动注入连接基本配置信息。最后将DataSource主动注入进持久层Bean

④ 接下来可以直接在持久层中使用数据源对象进行操作

2. Spring注解开发

Spring是轻代码重配置的框架,配置比较繁重,会影响开发效率。这个时候可以通过注解开发,注解代替xml配置文件可以简化配置,提高开发效率。

不仅仅是Spring框架,注解开发目前已经是一种趋势。

2.1 Spring原始注解

注解分为原始注解和新注解,主要是从逻辑上分的,并没有实际的影响。

原始注解出现的比较早,主要用来替代标签配置的。

注解

说明

@Component

使用在类上,用于实例化Bean,相当于标签

@Controller

使用在web层类上,用于实例化Bean。语义化的@Component

@Service

使用在service层类上,用于实例化Bean。语义化的@Component

@Repository

使用在dao层类上,用于实例化Bean。语义化的@Component

@Autowired

使用在对象引用字段上,用于根据当前字段类型依赖注入

@Qualifier

使用在对象引用字段上,结合@Autowired一起使用,用于根据名称进行依赖注入

@Resource

使用在对象引用字段上,相当于@Autowired+@Qualifier,按照名称进行依赖注入

@Value

使用在普通类型字段上,注入普通属性

@Scope

使用在类上,标准Bean的作用范围

@PostConstruct

使用在方法上,标注该方法是Bean的初始化方法

@PreDestory

使用在方法上,标注该方法时Bean的销毁方法

上述注解中比较常用的是除最后两个之外的所有注解。

注意:

使用注解进行开发时,不需要在applicationContext.xml配置各种Bean了,但是需要配置组件扫描。作用是指定哪个包及其子包下的Bean需要进行扫描,以便识别使用了注解配置的类、字段和方法。

2.2 Spring注解开发 - QuicStart

1)在 ApplicationContext.xml中中引入context命名空间和约束文件,并添加组件扫描

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

2)编写UserDaoImpl类,并使用@Component进行标注,告诉Spring容器实例化该类。

@Componet("value")		其中value相当于Bean标签的id值
package com.itheima.dao;

//该接口因为不会被配置进xml文档,所以不需要添加注解
public interface UserDao{
    public void save();
}

3)编写UserServiceImpl类,并使用@Component进行标注,告诉Spring容器实例化该类。

因为UserServiceImpl类中要依赖UserDaoImpl,所以需要在前者中添加一个对象的成员变量,并提供set方法。

同时需要在成员变量上添加注解标注实现依赖注入,可以添加@Autowired+@Qualifier注解。

@Autowired				自动注入被标记属性
@Qualifier("value")		其中,value相当于Bean标签property子标签中ref的值
package com.itheima.service;

//该接口因为不会被配置进xml文档,所以不需要添加注解
public interface UserService{
    public void save();
}
package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import sun.awt.SunHints;

//下面注解相当于在xml文档中进行了如下配置:
//<bean id="userService" class="com.itheima.service.impl.UserServiceImpl“/>

@Component("userService")
public class UserServiceImpl implements UserService {
    //下面注解相当于在 xml文档中进行了如下配置
    //<bean><property name="xxx" ref="userDao" /></bean>
    @Autowired
    @Qualifier("userDao")
    private UserDao userDao;

    public void setUserDao(UserDao userDao){
        this.userDao = userDao;
    }

    public void save(){
        userDao.save();
    }
}

4)在测试类或模拟Ctroller层中测试调用。

package com.itheima;

import com.itheima.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserServiceTest {

    @Test
    public void test5(){
        ApplicationContext apc = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = apc.getBean(UserService.class);
        userService.save();
    }
}

2.3 Spring常用原始注解

1)用来完成对象实例化的注解(常用)

@Component 当不知道该类属于软件分层的哪一层时,使用该注解,实例化Bean

@Controller 使用在web层类上,用于实例化Bean。语义化的@Component,推荐使用。

@Service 使用在Service层上,用于实例化Bean。语义化的@Component,推荐使用。

@Repository 使用在Dao层上,用于实例化Bean。语义化的@Component,推荐使用。

2)用来完成依赖注入的注解(常用)

使用注解方式实现依赖注入的时候,可以不为被注入的成员变量添加set方法;

使用xml配置方式实现依赖注入的时候,必须为被注入的成员变量添加set方法。

@Autowired 按照被标注的成员变量的数据类型在Spring容器中进行匹配,如果找到一样的,就把对应的对象赋值给被标注的成员变量。但如果在Spring容器中存在符合要求的多个bean,这个时候会无法识别使用哪个bean。

@Qualifier(“value”) 按照beanid值(名称)在容器中进行匹配注入,需和@Autowired一起使用。

@Resource(name=“name”) 相当于@Autowired+@Qualifier(“value”)。name为id的值。受高版本JDK特性影响,@Resources与JDK9版本不兼容,建议使用JDK8

3)注入普通数据类型数据的注解(常用)。

@Value(“value”) 为被标注的成员变量注入字符串value,但是使用频率不高。

这种写法结合spel来使用,写法如下:

@Value("${jdbc.driver}") 在Spring配置文件中导入Properties文件后,可以通过key(jdbc.driver)获得value值

4)bean对象作用范围的注解(常用)

@Scope(“value”) 用于表示Bean对象作用范围,使用频率较高。value的常用取值有prototype和singleton

5)其他注解(不常用)

@PostConstruct 被标注的成员方法,将成为对象的初始化方法

@PreDestroy 被标注的成员方法,将成为对象的销毁方法

2.4 Spring常用新注解

使用原始注解不能替代Spring配置文件中全部内容,新注解可以替代如下配置:

1)非自定义的Bean的配置:例如DataSource
2)加载Properties文件的配置:<context:property-placeholder path="classpath:xxx"/>
3)Spring注解组件扫描器配置:<context:component-scan base-package=""/>
4)引入其他xml配置文件:<import resource="applicationContext-xxx.xml">

针对以上4中情况,Spring提供新注解如下:

Spring新注解的替代思想:使用一个类(核心配置类)替代xml核心配置文件,在类上使用注解代替原来xml文件中的标签。

注解

说明

@Configuration

标注在特殊的指定类上,被标注的类就是Spring核心配置类

@ComponentScan(“包名”)

标注在特殊的指定类上,指定组件扫描范围,效果等同于<context:component-scan base-package=“包名”/>

@Bea(“value”)

标注在特殊指定类中的方法上,用于”标注“非自定的Bean,标注方式 有些特殊:Spring会将被标注方法的返回值存储在Spring容器中(不同于自定义Bean是标注在类上,实例化该类的实例;该标签是标注在方法上 ,实例对应的是方法的返回值,返回值被value接收,方法名任意)。效果等同于<bean id=“dataSource” class=“com.mchange.v2.c3p0.ComboPooledDataSource”">

@PropertySource

标注在特殊指定的类上或者其@import类上,将指定的properties文件加载进Spring容器。写法为@PropertySource(“classpath:xxx.properties”)。效果等同于<context:property-placeholder path=“calsspath:xxx”/>

@import

标注在特殊指定的类上,作用:将拆分出去的配置类引入到核心配置类内,写法为:@import({xxx.class, yyy.class})。 效果上等同于

零Spring配置文件代码如下:

1)创建Spring核心配置类SpringConfiguration.java,并添加对应注解

package com.itheima.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

//被该注释标注后表示该类为Spring核心配置类
@Configuration

//指定组件扫描范围,相当于<context:component-scan  base-package="包名"/>
@ComponentScan("com.itheima")

//引入拆分出去的配置类,相当于<import resource="xxx.xml"/>
@Import(DataSourceConfiguration.class)
public class SpringConfiguration{

}

2)新建jdbc.properties文件,将数据源连接基本参数配置其中:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/maven
user=root
password=135246

3)创建Spring核心配置类拆分出去的数据源配置类DataSourceConfiguration.java,并添加相应注解

package com.itheima.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;


//将Properties文件加载进Spring容器,
//相当于<context:property-placeholder path="calsspath:xxx"/>
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfiguration{
    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${user}")
    private String username;

    @Value("${password}")
    private String password;

    @Bean("dataSource")
    public DataSource getDataSource() throws Exception {
        //创建数据源
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        //为数据源设置连接基本参数
        dataSource.setDriverClass(driver);
        dataSource.setJdbcUrl(url);
        dataSource.setUser(username);
        dataSource.setPassword(password);

        //返回创建好的数据源
        return dataSource;
    }
}

4)在测试列中测试注解解析情况

@Test
public void test6(){
    //因为不再使用xml配置文件形式,所以应用上下文的创建对象要调整为新的
    ApplicationContext ap = new AnnotationConfigApplicationContext(SpringConfiguration.class);
    UserService userService  = ap.getBean(UserService.class);
    userService.save();
}

注:

UserService、UserServiceImpl、UserDao、UserDaoImpl文件代码于之前一致。

3. Spring整合Junit

在之前的代码中,每逢遇到使用Junit测试Spring代码,必有下面两句代码(或类似功能代码):

ApplicationContext ap = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = ap.getBean(UserService.class);

这两行代码的作用是获取Spring应用上下文,然后从上下文拿到我们想要使用的实例对象,如果不这样操作,就无法准确获取到需要的对象,会报空指针异常。

上述思路明显不符合Spring核心思想Ioc的思想,所以需要调整。

3.1 Spring整合Junit思路

按照Spring核心思想Ioc思想进行测试

让SpringJunit负责创建Spring容器,创建的时候传入Spring核心配置文件或配置类

将需要测试的Bean直接在测试类中注入

3.2 Spring集成Junit步骤

① 在模块pom.xml文件中导入Spring集成Junit(Spring-test)坐标

② 使用@Runwith注解替换原来的运行器

③ 使用@ContextConfiguration指定配置文件或配置类

④ 使用@Autowired注入需要测试的对象(测试类中有被测试类的对象作为成员变量)

⑤ 创建测试方法进行测试

3.4 Spring集成Junit代码实现

1)在pom.xml文件中导入Spring集成Junit(Spring-test)坐标,Spring集成Junit依赖于Junit,所以也要把后者的坐标引入

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>
<!-- Spring整合Junit需要依赖junit,所以要导包,并且要求Junit版本要在4.12或以上 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

2)使用@Runwith注解替换原来的运行器,标注在测试类上

@Runwith本质上也是一个运行器。eg:

@RunWith(JUnit4.class)就是指用JUnit4来运行

@RunWith(SpringJUnit4ClassRunner.class)就是让测试运行于Spring测试环境

@Runwith(SpringJunit4ClassRunner.class)
public class SpringJunitTest{
    
}

3)使用@ContextConfiguration指定配置文件或配置类,标注在测试类上

@ContextConfigurationSpring整合JUnit4测试时,使用该注解引入单个或多个配置文件

单个文件
@ContextConfiguration(Locations=“classpath:applicationContext.xml”)
@ContextConfiguration(classes = SimpleConfiguration.class)

多个文件时

@ContextConfiguration(locations = { “classpath:spring1.xml”, “classpath:spring2.xml” })

@Runwith(SpringJunit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml");
public class SpringJunitTest{
    
}

4)创建测试方法进行测试

被标注的成员变量引用,会在Spring容器中去查找可以匹配的对象,如果找到就注入。

@Runwith(SpringJunit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml");
public class SpringJunitTest{
    
    @Autowired
    private UserService userService;
}

5)使用@Autowired注入需要测试的对象,标注在成员变量(被注入的对象)上。

被标注的成员变量引用,会在Spring容器中去查找可以匹配的对象,如果找到就注入。

@Runwith(SpringJunit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml");
public class SpringJunitTest{
    
    @Autowired
    private UserService userService;
    
    @Test
    public void testUserService(){
        userService.save();
    }
}