SpringFramework-IOC容器

容器

IoC容器概述

控制反转
这是一种编程思想,指将对象的实例化,初始化权限以及控制对象之间的依赖关系交予第三方容器

在spring项目中,我们将java对象交予spring进行管理

我们通过在配置文件(或利用注解)的方式为spring标记了所需要加入IoC容器的类信息;spring通过对我们所标注的信息进行扫描解析获得关于类的定义,至此就完成了控制反转的操作,当我们需要使用某个类时,spring就会帮助我们利用之前得到的Bean定义的信息来完成对象的实例化,初始化等步骤,返回一个可以直接使用的对象。

依赖注入
依赖注入实现了控制反转的思想

它指在spring创建对象的过程中,将对象依赖属性通过配置注入到实例中

我们可以通过set方法还有构造函数进行参数注入

基于XML管理Bean

实验环境

Spring项目配置文件

<!-- pom.xml配置文件 -->

<dependencies>
  <!-- spring context依赖-->
  <!-- 当引入Spring Context依赖之后,表示将Spring的基础依赖都引入了-->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>6.0.2</version>
  </dependency>
    <!--junit相关依赖  -->
    <!-- 可以使用@Test注解进行测试 -->
  <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.6.3</version>
  </dependency>

</dependencies>

IoC容器配置文件

<!-- application.xml配置文件 -->

<!-- 使用构造函数进行参数注入 -->
<!-- <bean id="user" class="com.atli.pojo.User">
    <constructor-arg name="name" value="li"></constructor-arg>
    <constructor-arg name="age" value="12"></constructor-arg>
</bean> -->

<!-- 使用set方法进行参数注入 -->
<bean id="user" class="com.atli.pojo.User">
    <property name="name" value="li"></property>
    <property name="age" value="12"></property>
</bean>


User类

package com.atli.pojo;

// 这里使用了lombok
@Data
public class User {
  private int age;
  private String name;
  // public User(String name, int age) {
  //   this.name = name;
  //   this.age = age;
  // }

  public void run(){
      System.out.println("run");
  }
}

测试类

public class UserTest {
    @Test
    public void test(){
        //加载spring配置文件,对象创建
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        //通过id获取对象
        User user = (User) context.getBean("user");
        // 通过类型获取对象
        // User user = (User) context.getBean("user");
        // 通过类型和id获取对象
        // User user = (User) context.getBean("user",User.class);

        //使用对象调用方法
        user.run();
        System.out.println(user.name);
        System.out.println(user.age);
    }
}

获取Bean

根据id获取
由于id属性指定了bean的唯一信息,故适应bean标签的id属性可以精确指定目标对象

根据类型获取对象
对于是唯一类型的bean而言,使用该方法也能准确定位到目标对象,但是当出现了多个同类型的bean,则无法利用类型进行唯一性对象确定(列如使用父类|接口对其多个子类|实现类,进行Bean获取,在配置bean时使用了不同id对同一类进行定义)

对于bean的获取,关键在于要能够唯一的定位到某个具体的bean,不能有歧义

依赖注入

基本类型属性

使用set方法注入
语法:<property name="" value="" />

使用构造器注入
语法:<constructor-arg name="" value="" />

使用该方法必须要在类中编写构造器,若有多个构造器则spring会根据参数列表进行注入

特殊值处理

(1)null值

<property name="name">
  <null></null>
</property>

(2)xml实体(可以看作是转义符)

  • xml也同java,c一样有一些保留符号,这些称为xml实体

  • 对于xml实体,我们可以用转义字符来表示

    &apos; 是一个撇号:'
    &amp; 是一个与字符:&
    &quot; 是一个引号:"
    &lt; 是一个小于号:<
    &gt; 是一个大于号:>
    
<!-- 这里我需要给expression属性赋值为'a<b' -->
<property name="expression" value="a $lt; b"></property>

(3)CDATA节
在CDATA标记下,所有的标记、实体引用都会被忽略,而被当作字符数据
语法:<![CDATA[忽略检查的文本]>

<!-- 这里以CDATA节的方法实现上一个例子 -->
<property name="expression" >
  <value><![CDATA[a<b]]></value>
</property>
特殊类型属性

前期准备

创建一个employee类作为department类的属性

// cat类,是employee类的map属性

@Data
public class Cat {
    private String name;
    public void speck(){
        System.out.println(this.name+" speck");
    }
}
// employee类,是Department类的list属性

@Data
public class Employee {
    private String name;
    private String[] hobbies;
    private Map cat;
}
// department类
@Data
public class Department {
    private String departmentName;
    private List<Employee> employees;
}
<!-- 配置文件 -->

<!-- 准备好两个cat类的bean作为employee的属性 -->
<bean id="catOne" class="com.atli.pojo.Cat">
<!-- 注入普通属性 -->
    <property name="name" value="xiaohua"></property>
</bean>
<bean id="catTow" class="com.atli.pojo.Cat">
<!-- 注入普通属性 -->
    <property name="name" value="xiaohei"></property>
</bean>

<!-- 将两个catBean组装成一个map,供employee使用 -->
<util:map id="cats">
    <entry key="1" value-ref="catOne"></entry>
    <entry key="2" value-ref="catTow"></entry>
</util:map>

<!-- 第一个employeeBean -->
<bean id="employeeone" class="com.atli.pojo.Employee">
    <property name="name" value="wang"></property>
    <property name="hobbies">
    <!-- 创建一个array并赋给hobbies -->
        <array>
            <value>"suijiao"</value>
            <value>"chifan"</value>
            <value>"dadoudou"</value>
        </array>
    </property>
    <!-- 将cats的map赋值给cat -->
    <property name="cat" ref="cats"></property>
</bean>

<!-- 第二个employeeBean -->
<bean id="employeeTow" class="com.atli.pojo.Employee">
    <property name="name" value="zhang"></property>
    <property name="hobbies">
        <array>
            <value>"xaing"</value>
            <value>"yu"</value>
            <value>"fan"</value>
        </array>
    </property>
    <property name="cat" ref="cat"></property>
</bean>

<!-- 将两个employeeBean组装成一个list -->
<util:list id="employees">
    <ref bean="employeeone"></ref>
    <ref bean="employeeTow"></ref>
</util:list>

<!-- departmentBean -->
<bean id="department" class="com.atli.pojo.Department">
    <property name="departmentName" value="kaifa"></property>
    <property name="employees" ref="employees"></property>
</bean>

// 测试

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("application-di.xml");
    // 获取Department实例
    Department department = context.getBean("department", Department.class);
    String departmentName = department.getDepartmentName();

    // 获取department中所有的Employee,并封装在employees的list中
    List<Employee> employees = department.getEmployees();

    // 遍历employees的list
    for (Employee employee : employees) {
        String employeeName = employee.getName();
        String[] hobbies = employee.getHobbies();

        // 将employee的cat map封装到名为cats的map中
        Map cats = employee.getCat();
        System.out.printf(departmentName+":"+employeeName+":"+hobbies);

        // 遍历cats中的每个key(编号),value(cat)
        cats.forEach((k, v) -> {
            System.out.print(k);

            //获取cats中的单个cat对象
            //注意,这里是不能直接将v当作是cat来对待,因为编译器无法确定类型。
            Cat cat = (Cat) v;

            // 调用cat的speck方法
            cat.speck();
        });
        System.out.println();
    }
}

对象类型
有三种方式,分别是引用外部bean、内部bean、级联属性赋值

  • 引用外部bean
    • 将属性类也通过<bean>标签声明到ioc容器中,再通过ref对该类进行引用
  • 内部bean
    • 直接将一个bean声明到另一个bean标签中
  • 级联属性赋值
    • 当将一个bean声明为另一个bean的属性时,可以直接用id.属性名进行赋值
<!-- 引用外部bean -->
<bean id="employee" class="com.atli.pojo.Employee">
    <property name="name" value="xiang"></property>
</bean>
<bean id="department" class="com.atli.pojo.Department">
    <property name="employee" ref="employee"></property>
</bean>

<!-- 内部bean -->
<!-- <bean id="department" class="com.atli.pojo.Department">
    <property name="employee" ref="employee"></property>
    <bean id="employee" class="com.atli.pojo.Employee">
      <property name="name" value="xiang"></property>
    </bean>
</bean> -->

<!-- 级联赋值 -->
<!-- <bean id="employee" class="com.atli.pojo.Employee">
    <property name="name" value="xiang"></property>
</bean>
<bean id="department" class="com.atli.pojo.Department">
    <property name="employee" ref="employee"></property>
    <property name="employee.name" ref="yu"></property>
</bean> -->

这里关于对象类型的引入,本质上是需要在目标对象中引入其他对象的引用,即使用ref指定id来绑定引用

数组类型

语法

<bean id="" class="">
  <property name="">
    <array>
      <value></value>
      <value></value>
      <value></value>
    </array>
  </property>
</bean>

List类型
这里要注意对于util命名空间的导入

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

语法

<util:list>
  <!-- 引用类型 -->
  <ref bean=""></ref>
  <!-- 数值类型 -->
  <!-- <value></value> -->
</util:list>

Map类型

这里同样的也需要util命名空间

语法

<util:map>
  <!-- value为直接值 -->
  <entry key="" value=""></entry>
  <!-- value为对象 -->
  <entry key="" value-ref=""></entry>
</util:map>

引用集合类型的bean
其实上述的list和map集合都是先创建了集合类型的bean再进行引入的

如果不创建集合bean又要对对应属性赋值的话,可以使用内部bean的写法

例子

<bean id="department" class="com.atli.pojo.Department">
    <property name="departmentName" value="kaifa"></property>
    <property name="employees" >
        <list>
            <ref bean="employeeone"></ref>
            <ref bean="employeeTow"></ref>
        </list>
    </property>
</bean>

p命名空间注入

这里我以上面的department的bean来举例

<!-- 加入p命名空间 -->
xmlns:p="http://www.springframework.org/schema/p"

<!-- <bean id="department" class="com.atli.pojo.Department">
    <property name="departmentName" value="kaifa"></property>
    <property name="employees" ref="employees"></property>
</bean> -->

<!-- 通过在bean标签内,使用p:属性名|属性名-ref = "直接值|bean",来简化配置 -->
<bean id="department" class="com.atli.pojo.Department"
p:departmentName="kaifa" p:employees-ref="employees">
</bean>

引入外部属性文件

这里以使用mysql和druid连接池为例

<!-- pom.xml依赖配置 -->

<!--MYSQL驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>

<!--druid连接池-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.31</version>
</dependency>
<!-- 外部属性文件(数据库配置) -->
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/studb
jdbc.driver=com.mysql.cj.jdbc.Driver
<!-- 引入context的命名空间 -->
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd


<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

<!-- 完成数据库信息注入 -->
<!-- 使用druid连接池需要new一个DruidDataSource对象,并提供数据库信息 -->
<!-- 而该类处于com.alibaba.druid.pool.DruidDataSource包下 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
  <property name="username" value="${jdbc.username}"></property>
  <property name="password" value="${jdbc.password}"></property>
  <property name="url" value="${jdbc.url}"></property>
  <property name="driverClassName" value="${jdbc.driver}"></property>
</bean>

bean作用域

bean的作用域实际上指的是bean实例的生命周期和可见范围
使用时只需要在bean标签中加上scope属性和对应值即可

singleton(单例):在整个应用中只存在一个bean实例,由Spring容器管理。默认作用域为singleton。

prototype(原型|多例):每次请求都会创建一个新的bean实例。

request(请求):每个HTTP请求都会创建一个新的bean实例,该作用域仅适用于WebApplicationContext环境。

session(会话):每个HTTP会话都会创建一个新的bean实例,该作用域同样仅适用于WebApplicationContext环境。

application(应用):整个Web应用中共享一个bean实例。

websocket(WebSocket):每个WebSocket连接创建一个新的bean实例,仅适用于WebSocket应用。

bean生命周期

  • bean对象创建(调用无参构造器)
  • 给bean对象设置属性
  • bean的后置处理器(初始化之前)
  • bean对象初始化(需在配置bean时指定初始化办法)
  • bean的后置处理器(初始化之后)
  • bean对象就绪可以使用
  • bean对象销毁(需要在配置bean时指定销毁方法)
  • IoC容器关闭
后置处理器

使用后置处理器需要继承BeanPostProcessor方法
当使用了后置处理器后,会对IoC容器中的所有Bean生效

public class MyBeanProcessor implements BeanPostProcessor{
  // 初始化前的后置处理器
  @Override
  public Object postProcessBeforeInitialization(Object bean,String beanName){
    // 需要进行的逻辑操作
    return bean;
  }
  // 初始化后的后置处理器
  @Override
  public Object postProcessAfterInitialization(Object bean,String beanName){
    // 需要进行的逻辑操作
    return bean;
  }
}
<!-- 将后置处理器装入IoC容器 -->
<bean id="myBeanProcessor" class = "com.atli.spring6.MyBeanProcessor">

FactoryBean

FactoryBean是Spring提供的整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean是得到的不是class属性中配置的这个类的对象而是getObject()方法的返回值。

// User类
public class User {
    public void add(){
        System.out.println("add");
    }
}
// UserFactory类
public class UserFactory implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        return new User();
    }
    @Override
    public Class<?> getObjectType() {
        return null;
    }
}
<!-- 配置文件 -->
<bean id="users" class="com.atli.spring6.UserFactory" ></bean>

基于xml自动装配

这里,我模拟三层架构,将dao注入到service,再将service注入到controller

// Dao
public class UserDao {
  public void work(){
    System.out.println("work");
  }
}
// service
public class UserService {
  private UserDao userDao;
  public void work(){
    userDao.work()
  }
}
// controller
public class UserController {
  private UserService userService;
  public void work(){
    userService.work();
  }
}
<!-- 配置文件 -->
<bean id="userDao" class="com.atli.dao.UserDao" autowire="byType"></bean>
<bean id="userService" class="com.atli.service.UserService" autowire="byType"></bean>
<bean id="userController" class="com.atli.controller.UserController" autowire="byType"></bean>

这里我使用autowire="byType"来进行指定,因为此处的这三个类的类型是唯一的,如果使用byName来指定则要注意名称的唯一性,并且id名要和java代码声明的变量名相同

基于注解管理Bean

实例1

这里模拟三层架构的调用

// Dao
@Repository
public class UserDao {
  public void work(){
    System.out.println("work");
  }
}
// service
@Service
public class UserService {
  @Autowired
  private UserDao userDao;
  public void work(){
    userDao.work()
  }
}
// controller
@Controller
public class UserController {
  @Autowired
  private UserService userService;
  public void work(){
    userService.work();
  }
}

例子2

使用配置类配置将Druid连接池对象存储到IoC容器

//标注当前类是配置类,替代application.xml    
@Configuration
//引入jdbc.properties文件
@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})
// 配置扫描路径
@ComponentScan(basePackages = {"com.atli.components"})
public class MyConfiguration {

  //如果第三方类进行IoC管理,无法直接使用@Component相关注解
  //解决方案: xml方式可以使用<bean标签
  //解决方案: 配置类方式,可以使用方法返回值+@Bean注解
  @Bean
  public DataSource createDataSource(@Value("${jdbc.user}") String username,
                                     @Value("${jdbc.password}")String password,
                                     @Value("${jdbc.url}")String url,
                                     @Value("${jdbc.driver}")String driverClassName){
      //使用Java代码实例化
      DruidDataSource dataSource = new DruidDataSource();
      dataSource.setUsername(username);
      dataSource.setPassword(password);
      dataSource.setUrl(url);
      dataSource.setDriverClassName(driverClassName);
      //返回结果即可
      return dataSource;
  }
}
// 配置类
//这是全注解开发不用xml
@Configuration//标记配置类
@ComponentScan("com.atli")//标记包扫描
public class SpringConfig {
}

bean的标记

spring提供了四种标记bean的注解,这些注解都基于@Component,用这些注解标记的bean就相当于在xml文件中配置了<bean>标签

在这些标记中可以添加value属性来指定bean的名称,默认为类名且首字母小写

  • @Component
    • 用于标记普通bean
  • @Repository
    • 用于标记Dao层bean
  • @Service
    • 用于标记Service层bean
  • @Controller
    • 用于标记Controller层bean

bean扫描

在xml文件中指定扫描配置可以指定包,也可以指定排除包中的某些类

开启关于注解的bean扫描需要context命名空间

<!-- 基本扫描 -->
<context:component-scan base-package="" />

<!-- 排除某个包 -->
<!-- type指定排除规则,expression指定表达式 -->
<context:exclude-filter type="" expression="" />

<!-- 扩展扫描组件 -->
<context:include-filter type="" expression="" />

@Autowired注解

使用该注解时,对于注入bean最好使用该bean的接口,而不是实现类,否则可能会出现无法确定注入某一个实例

使用该注解可以实现bean的自动装配,默认情况下是通过类名进行匹配

若存在多例,需要通过id名称进行装配则需要加上@Qualifier注解

该注解可以在成员变量,构造方法,普通方法形参,set方法使用

对于自动装配,实际上就是spring根据在ioc容器中的类型或者id来对进行标记的成员进行属性注入

@Resource注解

@Autowired类似,该注解默认情况下是通过id进行匹配

@Value注解

该注解用于读取外部配置文件的值

@Value("${key}")

完全注解开发(配置类)

通过在java程序中创建配置类可以做到完全注解开发

  • @Configuration指定一个类为配置类,可以添加配置注解,代替配置xml文件
  • @ComponentScan(basePackages={"包"})替代<context:component-scan >实现注解扫描
  • @PropertySource("classpath:配置文件地址")代替<context:property-placeholder>标签实现读取外部配置文件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值