原创

Spring笔记二(IOC-注解)

温馨提示:
本文最后更新于 2023年11月30日,已超过 519 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

一. 容器IOC

lOC是lnversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出低耦合、更优良的程序。
Spring 通过 IOC 容器来管理所有Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IOC容器管理的Java 对象称为 Spring Bean,它与使用关键字 new 创建的Java 对象没有任何区别。
lOC容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程

1.1控制反转(IOC)

  • 控制反转是一种思想
  • 控制反转是为了降低程序耦合度,提高程序扩展力
  • 控制反转,反转的是什么?
    • 将对象的创建交给第三方容器负责
    • 将对象和对象之间的关系维护交给第三方容器负责

1.2DI 依赖注入

DI(Dependency Injection):依赖注入,依赖注入实现了控制反转的思想。

依赖注入:

  • 指Spring创建对象的过程中,将对象依赖属性通过配置进行注入

依赖注入常见的实现方式包括两种:

  • 第一种:set注入
  • 第二种:构造注入

所以结论是:IOC 就是一种控制反转的思想, 而 DI 是对IoC的一种具体实现。

Bean管理:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。

1.3IoC容器在Spring的实现

Spring 的 IoC 容器就是 IoC思想的一个落地的产品实现。IoC容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建IoC 容器。Spring 提供了IoC 容器的两种实现方式:

①BeanFactory

这是 IoC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

②ApplicationContext

BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。

③ApplicationContext的主要实现类

类型名 简介
ClassPathXmlApplicationContext 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContext ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。
WebApplicationContext 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。

二. XML管理bean

2.1 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="user" class="com.wu.learn.xml.User"></bean>
    <bean id="user2" class="com.wu.learn.xml.User"></bean>
</beans>

2.2 bean的获取

2.2.1.通过id获取

        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        //通过id获取对象
        User user = (User) context.getBean("user");
        user.run();
        User user2 = (User) context.getBean("user2");
        user2.run();

2.2.2. 通过类型获取

        //通过类型获取对象
        User userClazz = context.getBean(User.class);
        userClazz.run();

通过类型获取实例时,要求容器中的对象只能存在一个,如果存在多个会报错,此时可以使用id和类型指定获取(感觉没有必要)

No qualifying bean of type 'com.wu.learn.xml.User' available: expected single matching bean but found 2: user,user2

2.2.3.通过id和类型获取

加入类型是为了保证得到的对象是自己想要的类型,如果类型不对,会提示错误

        //通过类型和id获取对象
        User userClazzId = context.getBean("user",User.class);
        userClazzId.run();

2.2.4.通过接口获取对象

创建UserDao 接口,UserDaoImpl实现UserDao接口,在xml中添加UserDaoImpl

public interface UserDao {

    void testDao();
}

public class UserDaoImpl implements UserDao{

    @Override
    public void testDao() {
        System.out.println("testdao..................");
    }
}
<?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="userDaoImpl" class="com.wu.learn.xml.UserDaoImpl"></bean>
</beans>

测试,正常获取

        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        UserDao bean = context.getBean(UserDao.class);
        bean.testDao();

新建一个PersonDaoImpl,在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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDaoImpl" class="com.wu.learn.xml.UserDaoImpl"></bean>
    <bean id="personImpl" class="com.wu.learn.xml.PersonImpl"></bean>
</beans>

使用以上测试代码报错提示

No qualifying bean of type 'com.wu.learn.xml.UserDao' available: expected single matching bean but found 2: userDaoImpl,personImpl

总结

当使用spring管理bean时,如果通过类型获取对象,需要保证容器中的对象都是单例。

2.3.依赖注入

类有属性,创建对象过程中,向属性设置值

Book对象

public class Book {

    private String bookName;

    private String author;

    public Book() {
        System.out.println("无参构造。。。。。。。。。。。。。。");
    }

    public Book(String bookName, String author) {
        System.out.println("youyouyou参构造。。。。。。。。。。。。。。");
        this.bookName = bookName;
        this.author = author;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookName='" + bookName + '\'' +
                ", author='" + author + '\'' +
                '}';
    }
}

2.3.1.setter注入

    <!--    setter注入-->
    <bean id="book" class="com.wu.learn.di.Book">
        <property name="bookName" value="Java"></property>
        <property name="author" value="王小虎"></property>
    </bean>

2.3.2.构造器注入

    <!--    构造器注入-->
    <bean id="bookCon" class="com.wu.learn.di.Book">
        <constructor-arg name="bookName" value="C++"></constructor-arg>
        <constructor-arg name="author" value="王大虎"></constructor-arg>
    </bean>

2.3.3.特殊值处理

  • 字面量赋值

    使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量

        <bean id="book" class="com.wu.learn.di.Book">
            <property name="bookName" value="Java"></property>
        </bean>
    
  • null赋值

        <bean id="book" class="com.wu.learn.di.Book">
            <property name="bookName">
                <null></null>
            </property>
        </bean>
    
  • xml实体

    特殊字符需要转义

    小于号在XML文档中用来定义标签的开始,不能随便使用

    解决方案一:使用XML实体来代替

        <bean id="book" class="com.wu.learn.di.Book">
            <property name="bookName" value="&lt;&gt;"></property>
        </bean>
    
  • CDATA节

    特殊字符可以写在<![CDATA[xxxx]]>中

    CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据

    XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析

        <bean id="book" class="com.wu.learn.di.Book">
            <property name="bookName" >
                <value><![CDATA[a>b]]></value>
            </property>
        </bean>
    

2.3.4. 对象类型赋值

班级类和学生类

public class ClassInfo {

    //班级名称
    private String className;

    public void classInfo(){
        System.out.println(className+"是一个十强班级");
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }
}

public class StudentInfo {

    //班级信息
    private ClassInfo classInfo;
    //学生姓名
    private String userName;
    //学生年龄
    private Integer userAge;

    public void study(){
        System.out.println(userName+"在"+classInfo.getClassName()+"认真学习");
        classInfo.classInfo();
    }

    public ClassInfo getClassInfo() {
        return classInfo;
    }

    public void setClassInfo(ClassInfo classInfo) {
        this.classInfo = classInfo;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getUserAge() {
        return userAge;
    }

    public void setUserAge(Integer userAge) {
        this.userAge = userAge;
    }
}

外部引用示例

    <!--外部引用示例-->
    <bean id="classInfo" class="com.wu.learn.di_oi.ClassInfo">
        <property name="className" value="高三一班"></property>
    </bean>
    <bean id="studentInfo" class="com.wu.learn.di_oi.StudentInfo">
        <property name="userName" value="张三"></property>
        <property name="userAge" value="18"></property>
        <property name="classInfo" ref="classInfo"></property>
    </bean>

内部引用示例

    <bean id="studentInfo2" class="com.wu.learn.di_oi.StudentInfo">
        <property name="userName" value="王小虎"></property>
        <property name="userAge" value="17"></property>
        <property name="classInfo">
            <bean id="classInfo" class="com.wu.learn.di_oi.ClassInfo">
                <property name="className" value="高三二班"></property>
            </bean>
        </property>
    </bean>

级联引用示例

    <bean id="studentInfo3" class="com.wu.learn.di_oi.StudentInfo">
        <property name="userName" value="李二狗"></property>
        <property name="userAge" value="19"></property>
        <property name="classInfo" ref="classInfo"></property>
        <property name="classInfo.className" value="高三三班"></property>
    </bean>

2.3.5. 数组、集合赋值

关键词 array list

    <bean id="dog" class="com.wu.learn.di_arr.LactationAnimal">
        <property name="lacName" value="小狗"></property>
    </bean>
    <bean id="milk" class="com.wu.learn.di_arr.LactationAnimal">
        <property name="lacName" value="小牛"></property>
    </bean>
    <bean id="animal" class="com.wu.learn.di_arr.Animal">
        <property name="name" value="小猫"></property>
        <!--数组注入-->
        <property name="brothers">
            <array>
                <value>老虎</value>
                <value>猎豹</value>
            </array>
        </property>
        <property name="lactationAnimal">
            <list>
                <bean id="milkA" class="com.wu.learn.di_arr.LactationAnimal">
                    <property name="lacName" value="小牛"></property>
                </bean>
                <bean id="milkB" class="com.wu.learn.di_arr.LactationAnimal">
                    <property name="lacName" value="小狗"></property>
                </bean>
<!--               <ref bean="dog"></ref>-->
<!--               <ref bean="milk"></ref>-->
            </list>
        </property>
    </bean>

2.3.6.使用util进行list map赋值

引入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">

第五步修改后的赋值

    <bean id="dogUtil" class="com.wu.learn.di_arr.Dog">
        <property name="listMap" ref="animalMap"></property>
        <property name="list" ref="animalList"></property>
    </bean>

    <util:list id="animalList">
        <bean id="cat" class="com.wu.learn.di_arr.Animal">
            <property name="name" value="小猫"></property>
        </bean>
        <bean class="com.wu.learn.di_arr.Animal">
            <property name="name" value="小狗"></property>
        </bean>
    </util:list>
    <util:map id="animalMap">
        <entry>
            <key>
                <value>buru</value>
            </key>
            <bean class="com.wu.learn.di_arr.Animal">
                <property name="name" value="小狗"></property>
            </bean>
        </entry>
    </util:map>

2.3.7.p命名空间

      在beans中引入 xmlns:p="http://www.springframework.org/schema/p"
    ....
    使用
    <bean id="dogP" class="com.wu.learn.di_arr.Dog"
    p:list-ref="animalList" p:listMap-ref="animalMap">
    </bean>

2.3.8.引入外部文件

1. 编写外部文件test.properties
userM.name="张三"
userM.age=18
userM.sex="男"
2. 实体类
@Data
public class UserOut {

    private String name;

    private Integer age;

    private String sex;
}
3. xml配置

引入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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--    配置引入的外部文件-->
    <context:property-placeholder location="classpath:test.properties" file-encoding="utf-8"></context:property-placeholder>

    <!--    使用${}-->
    <bean id="outUser" class="com.wu.learn.di_out.UserOut">
        <property name="name" value="${userM.name}"></property>
        <property name="age" value="${userM.age}"></property>
        <property name="sex" value="${userM.sex}"></property>
    </bean>

</beans>

2.3.9.scope配置

在写bean配置的时候,有一个scope参数,如果不填,默认是单例。可以修改为多实例。

  • singleton 单例模式
  • prototype 多实例模式
    <bean id="outUser" class="com.wu.learn.di_out.UserOut" scope="prototype">
        <property name="name" value="${userM.name}"></property>
        <property name="age" value="${userM.age}"></property>
        <property name="sex" value="${userM.sex}"></property>
    </bean>

2.3.10. bean的生命周期

  1. 创建对象(调用无参构造器)
  2. 给对象设置属性值
  3. bean后置处理器(初始化前)
  4. 对象初始化(调用指定初始化方法)
  5. bean后置处理器(初始化后)
  6. 对象创建完成
  7. 对象销毁

测试对象

public class UserTest {

    private String userName;


    public UserTest() {
        System.out.println("创建对象");
    }

    public void initMethod(){
        System.out.println("对象初始化");
    }

    public void destroyMethod(){
        System.out.println("对象销毁");
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        System.out.println("给对象设置属性值");
        this.userName = userName;
    }
}

自定义bean后置处理器(用于打印日志)

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {


    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean后置处理器(初始化前)");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean后置处理器(初始化后)");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }

}

xml配置

在xml指定对象的初始化方法和销毁方法。自定义bean后置处理器需要放到IOC容器中才会生效(针对所有的IOC容器中的实例对象都生效

    <bean id="userLife" class="com.wu.learn.di_life.UserTest" init-method="initMethod" destroy-method="destroyMethod">
        <property name="userName" value="张三"></property>
    </bean>

    <bean id="myProcessor" class="com.wu.learn.di_life.MyBeanPostProcessor"></bean>

2.3.11.factoryBean

实现FactoryBean接口,可以自定义Bean的创建过程。 应用于类似代理场景

public class UserTemp {

  private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}
import org.springframework.beans.factory.FactoryBean;

public class UserFactory implements FactoryBean<UserTemp> {

    @Override
    public UserTemp getObject() throws Exception {
        return new UserTemp();
    }

    @Override
    public Class<?> getObjectType() {
        return UserTemp.class;
    }
}

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="beanFactoryTest" class="com.wu.learn.di_factory.UserFactory"></bean>
</beans>

此时获取beanFactoryTest Bean,实际得到的是UserTemp对象

2.3.12.bean自动装配

关键词: autowire

  • 值为byType时,只允许单实例
  • 值为byName时,可生成多实例

controller 调用service

public class UserController {

    UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void testAutoWire() {
        System.out.println("controller执行");
        userService.testAutowire();
    }
}

public interface UserService {

    void testAutowire();
}

public class UserServiceImpl implements UserService{

    @Override
    public void testAutowire() {
        System.out.println("实现类执行了");
    }
}
<?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="userController" class="com.wu.learn.diaoto.UserController" autowire="byType"></bean>
    <bean id="userService" class="com.wu.learn.diaoto.UserServiceImpl"></bean>
</beans>

三 基于注解管理bean

3.1. 开启组件扫描

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <!--    开启组件扫描,扫描指定包及该包下所有加了相应注解的对象-->
    <context:component-scan base-package="com.mhld.manage">
        <!-- annotation   aspectj  assignable  custom  regex  -->
        <!--        <context:exclude-filter type="" expression=""/>-->
        <!--        <context:include-filter type="" expression=""/>-->
    </context:component-scan>
</beans>

组件扫描可以使用基础包扫描,然后指定包路径

也可以使用包含过滤扫描和不包含过滤扫描,包含以下五种扫描类型

  • annotation :指定注解

    <!--TestAno是自定义注解类-->
    <context:exclude-filter type="annotation" expression="com.mhld.manage.TestAno"/>
    
  • aspectj:切入点表达式

    需要引入aspect依赖 aspectjweaver

    <context:exclude-filter type="aspectj" expression="com.mhld.manage.animal..*"/>
    
  • assignable:指定类

    <context:exclude-filter type="assignable" expression="com.mhld.manage.TestUser"/>
    
  • custom:自定义过滤器

    先自定义一个过滤器,然后在bean中使用

    import org.springframework.core.type.classreading.MetadataReader;
    import org.springframework.core.type.classreading.MetadataReaderFactory;
    import org.springframework.core.type.filter.TypeFilter;
    
    public class MyTypeFilter implements TypeFilter {
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {
            System.out.println("进入到了自定义过滤器中");
            //自定义过滤器逻辑
            return false;
        }
    }
    
    <context:exclude-filter type="custom" expression="com.mhld.manage.filter.MyTypeFilter"/>
    
  • regex:正则表达式

    <context:exclude-filter type="regex" expression="正则表达式"/>
    

3.2.使用注解自定义bean

  • @Component :一般用于实体类
  • @Service:一般用于服务层
  • @Controller:一般用于控制层
  • @Repository:一般用于dao层

以上四个注解效果都一致,只是区分用途

3.3. autowire和resource

1. autowire

从controller层注入service为例。此时userService接口只有一个实现类

  • 属性注入:常用

    import com.mhld.manage.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    
    @Controller
    public class UserController {
    
        @Autowired
        private UserService userService;
    
    }
    
  • set方法注入: 不常用

    @Controller
    public class UserController {
    
        private UserService userService;
    
        @Autowired
        public void setUserService(UserService userService) {
            this.userService = userService;
        }
    }
    
  • 构造方法注入: 不常用

    @Controller
    public class UserController {
    
        private UserService userService;
    
        @Autowired
        public UserController(UserService userService) {
            this.userService = userService;
        }
    }
    
  • 形参注入: 不常用

    @Controller
    public class UserController {
    
        private UserService userService;
    
        public UserController(@Autowired UserService userService) {
            this.userService = userService;
        }
    }
    
  • 只有一个构造函数时,可以省略注解: 不常用

    @Controller
    public class UserController {
    
        private UserService userService;
    
        public UserController(UserService userService) {
            this.userService = userService;
        }
    }
    
  • 和qualifier注解配合使用

    autowire默认注入方式是通过类型(byType)注入,当需要注入的接口有多个实现类时,会提示找不到正确的注入对象

    此时配合@Qualifier注解,可以指定需要注入的对象类

    @Controller
    public class UserController {
    
        @Autowired
        @Qualifier(value = "userServiceImpl")
        private UserService userService;
    
    }
    
2.resource (个人常用)

@Resource注解也可以完成属性注入。那它和@Autowired注解有什么区别?

  • @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
  • @Autowired注解是Spring框架自己的。
  • @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
  • @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
  • @Resource注解用在属性上、setter方法上。
  • @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。

@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖。

<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
</dependency>

默认byName注入,没有指定name时把属性名当做name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个

四. 全注解开发

在基于注解管理bean时,还创建了一个xml文件开启组件扫描。此配置可通过配置注解@Configuration实现。

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

@Configuration
@ComponentScan(basePackages = "com.test.manage")
public class MyConfiguration {

}

测试时,通过注解配置进行上下文获取

import com.mhld.manage.config.MyConfiguration;
import com.mhld.manage.controller.UserController;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ConfigTest {
    @Test
    public void testConfig(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        UserController bean = context.getBean(UserController.class);
        System.out.println(bean);
    }
}
正文到此结束