0
点赞
收藏
分享

微信扫一扫

Spring5之IOC容器

全栈学习笔记 2022-04-14 阅读 72
springmaven

文章目录

前言

本文是尚硅谷Spring5框架视频笔记,所有例程均在IntelliJ IDEA中进行测试验证。

本文共13000余字,预计新手阅读时长>=30min,希望对同学们有所帮助。

一、Spring框架描述

  • Spring是轻量级的开源的JavaEE框架

  • Spring 可以解决企业应用开发的复杂性

  • Spring有两个核心部分:IOC和AOP
    (1)IOC控制反转,把创建对象过程交给Spring进行管理。

    (2)AOP面向切面,不修改源代码进行功能增强。

  • Spring 特点 :

    (1)方便解耦,简化开发

    (2)Aop 编程支持

    (3)方便程序测试

    (4)方便和其他框架进行整合

    (5)方便进行事务操作

    (6)降低 API 开发难度

二、IOC容器

(一) IOC容器(概念和原理)

  • IOC概念
    • 什么是IOC?
      • 控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理。
    • 使用IOC的目的:
      • 降低耦合度,也称解耦,减小修改所影响的范围。
  • IOC底层原理
    • XML解析,工厂模式,反射

(二) IOC容器(接口)

  • IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。
  • Spring提供两种IOC容器实现方式(两个接口)
    • BeanFactory:IOC容器的基本实现,是Spring内部的使用接口,不提供给开发人员进行使用。
      *(懒加载)加载配置文件的时候不会创建对象,在获取对象(使用)才去创建对象。

    • ApplicationContext:是BeanFactory的子接口,提供更多更强大的功能,一般由开发人员进行使用。
      *(预加载)加载配置文件的时候就会创建对象。

      • 包含的两个实现类:
      1. FileSystemXmlApplicationContext(填写XML的相对路径)
      2. ClassPathXmlApplicationContext(填写XML的绝对路径)

(三) IOC Bean管理

(1) 什么是Bean管理?

Bean管理是指两个操作: Spring创建对象Spring注入属性

Bean管理有两种操作方式:基于xml配置文件方式实现 和 基于注解方式实现

(2) 基于XML配置文件进行Bean管理

1. XML创建对象

  • 使用<bean></bean>标签创建对象,其中可以包含属性:
    • id:对象的唯一标识
    • class:类的相对路径
  • 创建对象时,默认使用无参构造函数
<bean id="user" class="User"></bean>

2. XML注入属性

DI:依赖注入,是IOC中的一种具体实现,需要在创建对象的基础上完成。
方法一:使用set方法进行注入

为类中的属性生成set和get方法

public class User {
    private String username;
    private String userage;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUserage() {
        return userage;
    }

    public void setUserage(String userage) {
        this.userage = userage;
    }
}

在XML文件中的<Bean>标签中使用<property>标签进行属性注入

	<!--<property>标签中的name属性表示对应类中的变量名,value属性表示变量值-->
    <bean id="user" class="User">
        <property name="username" value="王俊凯"></property>
        <property name="userage" value="18"></property>
    </bean>
方法二:用有参构造函数进行注入

为类生成有参构造函数

public class User {
    private String username;
    private String userage;

    public User(String username, String userage) {
        this.username = username;
        this.userage = userage;
    }
}

在XML文件中的<Bean>标签中使用<constructor-arg/>单标签进行属性注入

<!--<constructor-arg/>标签中有两个属性可以对应类中的变量,name和index,
	index由0开始,对应构造函数中的参数位置(一般不使用index)-->
<bean id="user" class="User">
        <constructor-arg name="username" value="王源"/>
        <constructor-arg name="userage" value="19"/>
    </bean>
方法三:用P名称空间进行注入(了解即可)

添加P名称空间在配置文件中
xmlns:p="http://www.springframework.org/schema/p"

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

在<bean>标签中添加p:变量名=“变量值” 属性

<bean id="user" class="User" p:username="易烊千玺" p:userage="20"></bean>

3. XML注入其他属性

(1) null值
    <bean id="user" class="User">
        <property name="userName"> <null/> </property>
    </bean>
(2) 特殊符号
    <bean id="book" class="User">
        <property name="bookname">
            <value><![CDATA[<<百年孤独>>]]></value>
        </property>
        <property name="bookname2" value="&lt;&lt;霍乱时期的爱情&gt;&gt;"></property>
    </bean>
(3) 注入属性----外部bean

在set方法注入的基础上,创建第二个类并在第一个类中生成set方法

    private Book book;
    public void setBook(Book book) {
        this.book = book;
    }

在XML中添加第二个类的<bean>标签,并添加<property>在第一个类中

	<!--<property>标签中的name属性表示对应类中的变量名,value属性表示变量值-->
	<!--第三条property里name的值对应User类中Book类的变量名,ref的值对应Book类中bean标签的id值-->
    <bean id="user" class="User">
        <property name="username" value="王俊凯"></property>
        <property name="userage" value="18"></property>
        <property name="book" ref="tbook"></property>
    </bean>
    <bean id="tbook" class="Book"></bean>
(4) 注入属性----内部bean

与外部bean的方法相同,只是将XML中的外部bean填写到property内部

    <bean id="user" class="User">
        <property name="username" value="王源"></property>
        <property name="userage" value="18"></property>
        <property name="book">
            <bean id="tbook" class="Book"></bean>
        </property>
    </bean>
(5) 注入属性----级联赋值

当第二个类中也有自己的变量时,在外部bean的基础上对其赋值

public class Book {
    private String bookname;

    public void setBookname(String bookname) {
        this.bookname = bookname;
    }
    public void update(){
        System.out.println(bookname);
    }
}

写法1

    <bean id="user" class="User">
        <property name="username" value="王源"></property>
        <property name="userage" value="18"></property>
        <property name="book" ref="tbook"></property>
    </bean>
    <bean id="tbook" class="Book">
        <property name="bookname" value="百年孤独"></property>
    </bean>

写法2

	<!--这种写法需要在User类里添加Book类的get方法才能获取到book对象-->
    <bean id="user" class="User">
        <property name="username" value="王源"></property>
        <property name="userage" value="18"></property>
        <property name="book" ref="tbook"></property>
        <property name="book.bookname" value="霍乱时期的爱情"></property>
    </bean>
    <bean id="tbook" class="Book"></bean>
(6) 注入属性----集合类型

集合的类型和在XML里对应的标签如下

	<!--除了map中使用entry表示键值对以外,其余的集合类型均用value标签设置值-->
    <bean id="tbook" class="Book">
        <property name="list">
            <list>
                <value>5</value>
                <value>6</value>
            </list>
        </property>
        <property name="maps">
            <map>
                <entry key="四大名著" value="水浒传"></entry>
                <entry key="外国名著" value="百年孤独"></entry>
            </map>
        </property>
        <property name="strings">
            <array>
                <value>西游记</value>
                <value>红楼梦</value>
            </array>
        </property>
    </bean>

集合的类型为自定义类型时,进行如下设置

<!--在使用类中定义property标签,并将对象作为值传入list中-->
<property name="list">
            <list>
                <ref bean="class1"></ref>
                <ref bean="class2"></ref>
            </list>
</property>
<!--在外部创建自定义类型的对象,并对其赋值-->
    <bean id="class1" class="Class">
        <property name="cname" value="Springboot"></property>
    </bean>
    <bean id="class2" class="Class">
        <property name="cname" value="Mybatis"></property>
    </bean>

使用util对集合类型进行抽取

    <util:list id="namelist">
        <value>王源</value>
        <value>王俊凯</value>
    </util:list>
    <bean id="user" class="User">
        <property name="list" ref="namelist"></property>
    </bean>

4. 工厂bean创建(FactoryBean)

创建一个类实现FactoryBean接口,并填写需要创建的类名

public class TestFactoryBean implements FactoryBean<Book> {

    @Override
    public Book getObject() throws Exception {
        Book book = new Book();
        return book;
    }

    @Override
    public java.lang.Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }
}

xml中创建工厂类的bean

<bean id="myBean1" class="TestFactoryBean"></bean>

最后在测试类中测试,注意对象类型填写的是实际创建的类,而非工厂类

    @Test
    public void testFactory(){
        ApplicationContext context = new ClassPathXmlApplicationContext("Bean2.xml");
        Book myBean = context.getBean("myBean1",Book.class);
        System.out.println(myBean);
    }

5. bean的作用域

  • 在Spring中,默认情况下bean是单实例的
		// 输出的两个user地址相同,证明是单实例
        ApplicationContext context = new FileSystemXmlApplicationContext("Bean1.xml");
        User user1 = context.getBean("user",User.class);
        User user2 = context.getBean("user",User.class);
        System.out.println(user1);
        System.out.println(user2);
  • 通过<bean>标签中的scope属性来设置该bean是单实例还是多实例
  • scope属性值:
    • singleton 默认值,表示单实例对象。加载配置文件时就会创建单实例对象。
    • prototype 表示多实例对象。在调用getBean方法时创建多实例对象。
      <bean id="user" class="User" scope="prototype"></bean>

6. bean的生命周期

(1)通过构造器创建 bean 实例(无参数构造)

(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)

(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization

(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)

(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization

(6)bean 可以使用了(对象获取到了)

(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

具体实现代码如下:

  • 其中初始化方法和销毁方法需要在xml中进行配置
public class Orders {
    private String name;
    public Orders() {
        System.out.println("第一步:执行无参构造方法创建bean实例");
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("第二步:调用set方法设置bean的属性值");
    }
    
    public void initMethod(){
        System.out.println("第四步:调用bean的初始化方法");
    }
    public void destroyMethod(){
        System.out.println("第七步:调用bean的销毁方法");
    }

    @Override
    public String toString() {
        return "Orders{" +
                "name='" + name + '\'' +
                '}';
    }
}
  • 后置处理器的使用需要在xml中进行配置
public class MyBeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第三步:将bean实例传给bean后置处理器的postProcessBeforeInitialization");
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第五步:将bean实例传给bean后置处理器的postProcessAfterInitialization");
        return bean;
    }
}
    <bean id="orders" class="Orders" init-method="initMethod" destroy-method="destroyMethod">
        <property name="name" value="hello"></property>
    </bean>
    <!--配置bean后置处理器,这样配置后整个xml里面的bean用的都是这个后置处理器-->
    <bean id="beanpost" class="MyBeanPost"></bean>

7. XML自动装配(较少使用)

  • 根据指定的装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入
  • 根据属性名称自动装配:要求 emp中属性的名称dept 和 bean标签的id值dept 一样,才能识别
<!--指定autowire属性值为byName-->
<bean id="emp" class="com.oymn.spring5.Emp" autowire="byName"></bean>
<bean id="dept" class="com.oymn.spring5.Dept"></bean>
  • 根据属性类型自动装配:要求同一个xml文件中不能有两个相同类型的bean,否则无法识别是哪一个
<!--指定autowire属性值为byType-->
<bean id="emp" class="com.oymn.spring5.Emp" autowire="byType"></bean>
<bean id="dept" class="com.oymn.spring5.Dept"></bean>

8. 外部属性文件操作bean

以配置数据库连接池为例:

  • 引入德鲁伊连接池jar包
  • 创建外部属性文件,properties格式文件,写数据库信息
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userDb
prop.userName=root
prop.password=root
  • 引入context名称空间,并通过context标签引入外部属性文件,使用“${}”来获取文件中对应的值
<?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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
 
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 
    <!--从本地location 引入外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <!--不是固定值:代表驱动名称 通过表示式从文件中-->
    <property name="driverClassName" value="${prop.driverClass}"></property>
    <!--不是固定值:数据库的地址 jdbc:mysql://localhost:3306 名字 userDb-->
    <property name="url" value="${prop.url}"></property>
    <!--不是固定值:登录用户名和密码-->
    <property name="username" value="${prop.userName}"></property>
    <property name="password" value="${prop.password}"></property>
    </bean>
</beans>

(3) 基于注解进行Bean管理

格式:@注解名称(属性名=属性值,属性名=属性值,……)

注解可以作用在类,属性,方法。

使用注解的目的:简化xml配置

1. 基于注解创建对象

spring提供了四种创建对象的注解:

@Component:普通注解
@Service:一般用于Service层
@Controller:一般用于web层
@Repository:一般用于Dao层
步骤一:引入依赖(Maven中的context依赖已包含子依赖aop)

 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>

步骤二:开启组件扫描

<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"
       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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启组件扫描,扫描base-package包下所有有注解的类并为其创建对象
        1.如果需要扫描多个包,多个包使用逗号隔开
        2.扫描包的上层目录
    -->
    <context:component-scan base-package="SpringAOP"></context:component-scan>
</beans>

步骤三:创建一个目标类

// 这里通过@Component注解来创建对象,括号中value的值等同于之前xml创建对象使用的id,为了后面使用时通过id来获取对象
// 括号中的内容也可以省略,默认是类名并且首字母小写
// 可以用其他三个注解
@Component(value = "userService")
public class UserService {
    public void open(){
        System.out.println("Hello aop");
    }
}

步骤四:在测试类中对目标类进行测试

public class TestUser {
    @Test
    public void testUserService(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Bean1.xml");
        UserService userService = applicationContext.getBean("userService",UserService.class);
        userService.open();
    }
}

2. 组件扫描配置细节

    <!--示例一
    use-default-filters="false" 表示现在不使用默认过滤器,自己配置过滤器
    context:include-filter 设置要扫描哪些内容
    org.springframework.stereotype.Component 扫描@Component注解
    (这是Component引入的路径)
    -->
    <context:component-scan base-package="SpringAOP" use-default-filters="false">
        <context:include-filter type="annotation" 
        						expression="org.springframework.stereotype.Component"/>
    </context:component-scan>
    <!--示例二
    表示扫描所有包
    context:exclude-filter 设置不扫描哪些内容
-->
    <context:component-scan base-package="SpringAOP">
        <context:exclude-filter type="annotation"
                                expression="org.springframework.stereotype.Component"/>
    </context:component-scan>

3. 基于注解进行属性注入

@AutoWired: 根据对象属性类型进行自动装配
@Qualifier: 根据对象属性名称进行注入
@Resource:可以根据对象属性类型和对象属性名称进行注入
@Value:普通类型属性值进行注入

@AutoWired和@Qualifier完整代码演示

@Repository
public class UserDaoImpl implements UserDao{
    @Override
    public void add() {
        System.out.println("Dao add!");
    }
}
@Service
// 此处可以单独使用@Autowired,但遇到一个接口有多个实现类时,
// 无法得知到底创建哪个实现类的对象,故需要在下方使用@Qualifier
// 来指定值(value的值就是创建对象注解里的值,默认类名首字母小写)
public class UserService {
    @Autowired
    @Qualifier(value = "userDaoImpl")
    private UserDao userDao;

    public void open(){
        System.out.println("Hello aop");
        userDao.add();
    }
}

@Resource和@Value部分代码演示

	// @Resource用法演示
    @Resource(name = "userDaoIml")
    private UserDao userDao;
	// @Value用法演示
	@Value(value = "abc")
	private String name;

4. 完全注解开发

步骤一:创建配置类,代替XML配置文件

@Configuration  // 作为配置类,替代xml配置文件
@ComponentScan(basePackages = "SpringAOP") // 填写原XML中base-package的内容
public class SpringConfig {
}

步骤二:在测试类中修改XML相关的对象

// 因无XML文件,故无法创建ClassPathXmlApplicationContext对象,
// 需要改成AnnotationConfigApplicationContext对象。
    @Test
    public void testUserService2(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService",UserService.class);
        userService.open();
    }

三、思考环节

  • 问题1:四个创建对象的注解为什么功能相同,但使用过滤器时却有关联?

经过测试,对组件扫描的过滤器进行配置时,若一个类使用@Service注释,另一个用@Repository注释,那么使用include-filter限制注释时,仅@Component可以运行成功,其余三个注释指定扫描后均无法运行;换用exclude-filter时无论限制不扫描哪个注释都无法运行成功。

  • 问题2:为什么使用Maven导入Spring依赖后,在XML中设置相对路径时出错?
    坑点
    在IDEA中创建Maven项目后,类默认的相对路径不是src,而是src/main/java
    同样的,XML的路径也不是src,必须将其放置在Maven项目预生成的src/main/resources目录下
举报

相关推荐

0 条评论