spring框架学习笔记02(Ioc+annotation)

目录

01,spring基于注解的 IOC 配置

1.1,提前预知

  • 在学习基于注解的 IoC 配置时,我们要知道注解配置和 xml 配置要实现的功能都是一样的,都是要降低程序间的耦合,只是配置的形式不一样
  • 关于是使用xml进行配置还是使用注解配置,看个人习惯把
  • 其实把xml和注解配合起来一起使用效果不错,可以尝试以一下

1.2,创建对象的常用注解

  • 我们在说的时候会对比着xml一起说,这样会理解更深刻

1.2.1,创建对象的注解@Component

  • 在xml中:<bean id="" class="">是用于创建bean对象的配置
  • 在注解中对应:@Component注解
作用:
把资源让 spring 来管理。相当于在 xml 中配置一个 bean。
属性:
value:指定 bean 的 id。如果不指定 value 属性,
默认 bean 的 id 是当前类的类名。首字母小写。
位置:类上

1.2.2,创建对象的注解(区分详细) @Controller @Service @Repository

  • 这三个注解都是@Component的衍生注解,功能和属性都和@Component注解一样,这三个注解存在的意义就是便于区分所创建的bean类型,
@Controller: 一般用于表现层的注解。
@Service: 一般用于业务层的注解。
@Repository: 一般用于持久层的注解。
位置:类上
细节:如果注解中有且只有一个属性要赋值时,
且名称是 value, value 在赋值是可以不写。
后面会再次解释这句话的意思

1.3,注入数据的常用注解

1.3.1, 注入数据的注解@Autowired

  • 在xml中:<property name="" ref=""><property name="" value="">用于给bean注入数据
  • 在注解中对应:@Autowired注解
作用:自动按照类型注入。
	如果容器中只有唯一的一个bean对象类型和要注入的变量类型匹配,就一定可以注入成功
	如果容器中有多个bean对象类型和要注入的变量类型匹配,首先按照数据类型找到所有匹配的bean对象,然后使用变量名称作为Id继续查找
	如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
位置:
	可以是变量上,也可以是方法上
细节:
	在使用注解注入时,set方法就不是必须的了。
  • 当容器中只有唯一的一个bean对象类型和要注入的变量类型匹配时,直接根据数据类型进行查找,找到之后将数据注入
  • 如果容器中有多个bean对象类型和要注入的变量类型匹配,首先按照数据类型找到所有匹配的bean对象,然后使用变量名称作为Id继续查找,这样做的话,当你需要给不同的bean对象注入数据时就需要通过修改变量名称来区分,
  • 使用Qualifier:标签就可以避免这种情况
    在这里插入图片描述

1.3.2,注入数据的注解@Qualifier

  • 对于多个bean时结合AutoWired注解一起使用,不方便
作用:在按照数据类型注入基础之上再按照变量名称注入。
注意:
	它在给类成员注入时不能单独使用要和AutoWired一起使用,一个用于查找数据类型,一个用于查找变量名称。
	但是在给方法参数注入时可以
属性:
	value:用于指定注入bean的id

1.3.3,注入数据的注解@Resource

作用:直接按照bean的id注入。它可以独立使用
属性:
	name:用于指定bean的id。

1.3.4,注入数据的注解@Value

  • 上面三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过XML来实现
  • 使用@Value注入基本类型和String类型
作用:用于注入基本类型和String类型的数据
属性:
	value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
	SpEL的写法:${表达式}

1.4,改变作用范围的注解@Scope

  • 在xml中:<bean id="" class="" scope="">用于改变作用的范围
  • 在注解中对应:@Scope注解
作用:指定 bean 的作用范围。
属性:
	value:指定范围的值。
取值: singleton prototype request session globalsession

1.5,和生命周期相关的注解 @PostConstruct和@PreDestroy(了解)

  • 在xml中: <bean id="" class="" init-method="" destroy-method="" />
  • 用于指定初始化方法的注解:@PostConstruct
  • 用于指定销毁方法的注解:@PreDestroy

1.6,关于 Spring 注解和 XML 的选择问题

  • 注解的优势:
    配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。
  • XML 的优势:
    修改时,不用改源码。不涉及重新编译和部署。
  • Spring 管理 Bean 方式的比较:
基于xml配置 基于注解配置
Bean定义 <bean id="..." class=" "> @Component @Repository @Service @Controller
Bean名称 通过id或name指定 @Component(“person”)
Bean注入 <property>或者配名称空间 @Autowired按类型注入 @Qualifier按名称注入
Bean作用范围 scope属性 @Scope
Bean生命周期 init-method destroy-method @PostConstruct初始化 @PreDestroy销毁
使用场景 Bean来自第三方,或者其他 Bean的实现类由自己维护

1.7,spring 管理对象细节

  • 基于注解的 spring IoC 配置中, bean 对象的特点和基于 XML 配置是一模一样的。

1.8,关于spring 如何使用纯注解配置的问题?

  • 前面提到,我们可以使用xml注解的方式来配置spring,此时xml文件中的内容为:
<!-- 1.告知spring框架在创建容器时扫描注解所扫描的包-->
<context:component-scan base-package="com.aismall"></context:component-scan>

<--2.数据源和 JdbcTemplate 的配置也需要靠注解来实现。-->
<!-- 配置 dbAssit -->
<bean id="dbAssit" class="com.aismall.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
	<property name="jdbcUrl" value="jdbc:mysql:///mySpring"></property>
	<property name="user" value="root"></property>
	<property name="password" value="12345678"></property>
</bean>

  • 如果上面的1和2也用注解来配置的话,我们就可以彻底的脱离xml来使用纯注解的方式来进行配置spring。
  • 到后面我会演示用注解的方式代替上面的1和2两步,那时你会发现,使用纯注解的方式进行配置,反而会更加麻烦
  • 我们选择哪种配置的原则是:怎么方便怎么来,而非追求某种技术。所以两种方式结合使用更加香!!!!!!

1.9,使用纯注解配置时需要引入的注解说明

1.9.1,用于指定一个类为配置类的注解@Configuration

作用:
	用于指定当前类是一个 spring 配置类, 当创建容器时会从该类上加载注解。 
	获取容器时需要使用AnnotationApplicationContext(@Configuration 注解的类.class)。
属性:
	value:用于指定配置类的字节码
  • 注意:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写
    例如:
@Configuration
public class SpringConfiguration {
}

1.9.2,指定初始化容器时要扫描的包的注解@ComponentScan

作用:
	用于指定 spring 在初始化容器时要扫描的包。 
	作用和在 spring 的 xml 配置文件中的:<context:component-scan base-package="com.itheima"/>是一样的。
属性:
	basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。

例如:

@Configuration
@ComponentScan("com.aismall")
public class SpringConfiguration {
}

1.9.3,配置数据源和 JdbcTemplate 对象的注解@Bean

作用:
	该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
属性:
	name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)
  • 注意: 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象,查找的方式和Autowired注解的作用是一样的
    例如:
public class JdbcConfig {
/**
* 创建一个数据源,并存入 spring 容器中
*/
public class JdbcConfig {
	@Bean(name="dataSource")
	public DataSource createDataSource() {
		try {
			ComboPooledDataSource ds = new ComboPooledDataSource();
			ds.setUser("root");
			ds.setPassword("1234");
			ds.setDriverClass("com.mysql.jdbc.Driver");
			ds.setJdbcUrl("jdbc:mysql:///mySpring");
			return ds;
		} catch (Exception e) {
		throw new RuntimeException(e);
		}
	}
	/**
	* 创建一个 DBAssit,并且也存入 spring 容器中
	*/
	@Bean(name="dbAssit")
	public DBAssit createDBAssit(DataSource dataSource) {
		return new DBAssit(dataSource);
		}
}

1.9.4,解决创建数据源的配置写死在类中的问题的注解@PropertySource

作用:
 用于加载.properties 文件中的配置。例如我们配置数据源时,
 可以把连接数据库的信息写到properties 配置文件中,就可以使用此
 解指定 properties 配置文件的位置。
属性:
	value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:

1.9.5,连接配置类的注解@Import

作用:
	用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。
	当然,写上也没问题
	当我们使用Import的注解之后,有Import注解的类就父配置类,
	而导入的都是子配置类
属性:
	value[]:用于指定其他配置类的字节码。

例如:

@Configuration
@ComponentScan(basePackages = "com.aismall.spring")
@Import({ JdbcConfig.class})
public class SpringConfiguration {
}

@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig{
}

1.9.6,如何通过注解获取容器?

ApplicationContext ac =new AnnotationConfigApplicationContext(SpringConfiguration.class);

02,案例:使用 spring 的IoC 实现账户的CRUD(纯注解方式)

在这里插入图片描述

2.1,创建一个Maven工程(spring)

  • 导入坐标
 <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

2.2,创建IAccountDao接口和AccountDaoImpl.java实现类

  • IAccountDao
/**
 * 账户的持久层实现类
 */
 //使用注解创建Bean
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
	//使用注解注入数据
    @Autowired
    private QueryRunner runner;

    @Override
    public List<Account> findAllAccount() {
        try{
            return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Account findAccountById(Integer accountId) {
        try{
            return runner.query("select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void saveAccount(Account account) {
        try{
            runner.update("insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateAccount(Account account) {
        try{
            runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteAccount(Integer accountId) {
        try{
            runner.update("delete from account where id=?",accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
  • AccountDaoImpl.java
/**
 * 账户的持久层接口
 */
public interface IAccountDao {

    /**
     * 查询所有
     */
    List<Account> findAllAccount();

    /**
     * 查询一个
     */
    Account findAccountById(Integer accountId);

    /**
     * 保存
     */
    void saveAccount(Account account);

    /**
     * 更新
     */
    void updateAccount(Account account);

    /**
     * 删除
     */
    void deleteAccount(Integer acccountId);
}

2.3,创建IAccountService接口和AccountServiceImplImpl.java实现类

  • IAccountService
/**
 * 账户的业务层接口
 */
public interface IAccountService {

    /**
     * 查询所有
     */
    List<Account> findAllAccount();

    /**
     * 查询一个
     */
    Account findAccountById(Integer accountId);

    /**
     * 保存
     * @param account
     */
    void saveAccount(Account account);

    /**
     * 更新
     */
    void updateAccount(Account account);

    /**
     * 删除
     */
    void deleteAccount(Integer acccountId);
}

  • AccountServiceImpl.java
/**
 * 账户的业务层实现类
 */
//使用注解创建Bean
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
    //使用注解注入数据
    @Autowired
    private IAccountDao accountDao;

    @Override
    public List<Account> findAllAccount() {
        return accountDao.findAllAccount();
    }

    @Override
    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);
    }

    @Override
    public void saveAccount(Account account) {
        accountDao.saveAccount(account);
    }

    @Override
    public void updateAccount(Account account) {
        accountDao.updateAccount(account);
    }

    @Override
    public void deleteAccount(Integer acccountId) {
        accountDao.deleteAccount(acccountId);
    }
}

2.4 ,创建两个配置类JdbcConfig.java和SpringConfiguration.java(重点在这里)

  • SpringConfiguration.java
  • 注意:带有Import注解的类为主配置类(父配置类),被导入的配置类为子配置类。
/*创建Bean对象,当配置类作为AnnotationConfigApplicationContext对象创建的参数时,
   该注解可以不写*/
//@Configuration

//指定要扫描的包
@ComponentScan("com.aismall")

//导入其他配置类
@Import(JdbcConfig.class)

//指定properties文件的位置
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
   
}
  • JdbcConfig.java
/**
 * 和spring连接数据库相关的配置类
 */
//配置文件注解
@Configuration
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;

    /**
     * 用于创建一个QueryRunner对象
     */
    /*创建一个QueryRunner对象*/
    //把当前方法的返回值作为bean对象存入spring的ioc容器中
    @Bean(name = "runner")
    //默认单例,我们需要多例
    @Scope("prototype")
    public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象:第一种方式(两种任选其一)
     */
    @Bean(name="ds2")
    public DataSource createDataSource(){

        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * 创建数据源对象:第二种方式
     */
    @Bean(name="ds1")
    public DataSource createDataSource1(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/mySpring");
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

2.6,实体类Account.java

  • 实体类属性为:
private Integer id;
private String name;
private Float money;

2.5,在Resource文件加下创建配置文件JdbcConfig.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mySpring
jdbc.username=root
jdbc.password=12345678

2.6,创建测试类AccountTest.java

public class AccountTest {
    @Test
    public void testFindAll() {
        //1.获取容器
        ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //2.获取业务层对象
        IAccountService as=ac.getBean("accountService",IAccountService.class);
        //3.执行方法
        List<Account> accounts = as.findAllAccount();
        for(Account account : accounts){
            System.out.println(account);
        }
    }

    @Test
    public void testFindOne() {
        //1.获取容器
        ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //2.获取业务层对象
        IAccountService as=ac.getBean("accountService",IAccountService.class);
        //3.执行方法
        Account account = as.findAccountById(1);
        System.out.println(account);
    }

    @Test
    public void testSave() {
        //1.获取容器
        ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //2.获取业务层对象
        IAccountService as=ac.getBean("accountService",IAccountService.class);
        Account account = new Account();
        account.setName("test anno");
        account.setMoney(12345f);
        //3.执行方法
        as.saveAccount(account);

    }

    @Test
    public void testUpdate() {
        //1.获取容器
        ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //2.获取业务层对象
        IAccountService as=ac.getBean("accountService",IAccountService.class);
        //3.执行方法
        Account account = as.findAccountById(4);
        account.setMoney(23456f);
        as.updateAccount(account);
    }

    @Test
    public void testDelete() {
        //1.获取容器
        ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //2.获取业务层对象
        IAccountService as=ac.getBean("accountService",IAccountService.class);
        //3.执行方法
        as.deleteAccount(4);
    }
}

03,Spring整合Junit

3.1,为什么要整合Junit?

  • 在测试类中,每个测试方法都有以下两行代码:
 //1.获取容器
ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.获取业务层对象
IAccountService as=ac.getBean("accountService",IAccountService.class);
  • 这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常,所以又不能轻易删掉。

3.2解决思路

3.2.1第一步:导入Spring的测试jar包

  • 依赖坐标
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
 </dependency>
  • 注意:导入 jar 包时,同时也需要导入一个 spring 中 aop 的 jar 包

3.2.2,第二步:使用@RunWith 注解替换原有运行器

@RunWith(SpringJUnit4ClassRunner.class)
public class AccountServiceTest {
}

3.2.3,第三步:使用@ContextConfiguration 指定 spring 配置文件的位置

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:bean.xml"})
public class AccountServiceTest {
}
@ContextConfiguration 注解:
locations 属性: 用于指定配置文件的位置。如果是类路径下,需要用 classpath表明
classes 属性:用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。

3.2.4, 第四步:使用@Autowired 给测试类中的变量注入数据

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:bean.xml"})
public class AccountServiceTest {
@Autowired
private IAccountService as ;
}

3.2.5,上面的AccountTest.java可修改为

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountTest {
    @Autowired
    private IAccountService as = null;
    @Test
    public void testFindAll() {
        //3.执行方法
        List<Account> accounts = as.findAllAccount();
        for(Account account : accounts){
            System.out.println(account);
        }
    }

    @Test
    public void testFindOne() {
        //3.执行方法
        Account account = as.findAccountById(1);
        System.out.println(account);
    }

    @Test
    public void testSave() {
        Account account = new Account();
        account.setName("test anno");
        account.setMoney(12345f);
        //3.执行方法
        as.saveAccount(account);

    }

    @Test
    public void testUpdate() {
        //3.执行方法
        Account account = as.findAccountById(4);
        account.setMoney(23456f);
        as.updateAccount(account);
    }

    @Test
    public void testDelete() {
        //3.执行方法
        as.deleteAccount(4);
    }
}

3.3,为什么不把测试类配到 xml 中

  • 在解释这个问题之前,先解除大家的疑虑,配到 XML 中能不能用呢?
    答案是肯定的,没问题,可以使用。
  • 那么为什么不采用配置到 xml 中的方式呢?
    第一:当我们在 xml 中配置了一个 bean, spring 加载配置文件创建容器时,就会创建对象。
    第二:测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。
  • 所以,基于以上两点,我们不应该把测试配置到 xml 文件中

04,案例:使用 spring 的 IoC 的实现账户的CRUD(注解+xml的方式)

在这里插入图片描述

  • 我们可以在基于纯注解的工程中进行改写,把config文件夹删除(也就是删除两个配置类),把两个配置类中的内容使用bean.xml进行配置
  • bean.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"
       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">

    <!--service和dao的配置使用注解的方式-->
    
    <!-- 告知spring在创建容器时要扫描的包 -->
    <context:component-scan base-package="com.aismall"></context:component-scan>
    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mySpring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="12345678"></property>
    </bean>
</beans>

猜你喜欢

转载自blog.csdn.net/weixin_45583303/article/details/106527707