Spring之IOC(应用)

一、Spring IOC简介

Spring IOC(Inverse of Control,控制反转)是Spring框架的核心特性之一。在传统的应用程序中,对象的创建和它们之间的依赖关系通常由程序员手动管理(new Object())。而Spring IOC可以自动管理对象的创建和依赖注入(Dependency Injection,即DI),降低代码耦合。

注:Spring中的Bean,大家理解为Java中的Object(对象)即可,两者只是语境不同。

1.HelloWorld

1.创建Java类

public class HelloWorld {  
    public void sayHello(){
        System.out.println("helloworld");
    }
}

2.创建配置文件

在resources目录创建一个 Spring 配置文件 beans.xml

其中我们只需要关注<bean>标签即可

<?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">
    <!--
    配置HelloWorld所对应的bean,即将HelloWorld的对象交给Spring的IOC容器管理
    通过bean标签配置IOC容器所管理的bean
    属性:
        id:设置bean的唯一标识
        class:设置bean所对应类型的全类名
	-->
    <bean id="helloWorld" class="com.spring6.bean.HelloWorld"></bean>
</beans>

3.测试

public class HelloWorldTest {
    @Test
    public void testHelloWorld(){
        //Spring中用于读取配置文件信息的类
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        //getBean(String id)方法通过id找到配置文件中id相同的bean标签并创建实例对象
        HelloWorld helloworld = (HelloWorld) ac.getBean("helloWorld");
        helloworld.sayHello();
    }
}

从测试类中我们可以看出,对象并不是被new出来的,而是由Spring的getBean方法创建的(原理为反射)。

2.IOC容器在Spring中的实现

IOC容器是用来存放Bean的,Spring提供了其基本实现BeanFactory(Bean工厂),在开发中我们使用子接口ApplicationContext(见上文测试类)

二、基于XML管理Bean

1.获取bean

//1 根据id获取
Object getBean(String name);
//2 根据class获取
<T> T getBean(Class<T> requiredType);
//3 根据id和class获取
<T> T getBean(String name, Class<T> requiredType);

例:

ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        
User user1 = (User) ac.getBean("user");
User user2 = (User) ac.getBean(User.class);
User user3 = (User) ac.getBean("user", User.class);

注1:当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个

当IOC容器中一共配置了两个时会出现异常

<bean id="helloworld1" class="com.spring6.bean.HelloWorld"></bean>
<bean id="helloworld2" class="com.spring6.bean.HelloWorld"></bean>

注2:bean的默认作用域(scope)为单例,意味着默认情况下user1、user2、user3为同一对象

注3:接口实现 bean 唯一时,根据接口类型可以获取 bean(spring会自动找到其唯一实现类)

UserDao userDao1 = context.getBean(UserDaoImpl.class);
UserDao userDao2 = context.getBean(UserDao.class);  //√

//对比
UserDao userDao3 = new userDao();  //×

2.依赖注入

对象创建完成了,怎么给对象属性赋值

在Spring中,IOC通过依赖注入(DI)实现,有两种常见方式:set注入和构造注入

2.1 set注入

<bean id="student1" class="com.spring6.bean.Student">
    <property name="id" value="1001"></property>
    <property name="name" value="张三"></property>
    <property name="age" value="23"></property>
    <property name="sex" value="男"></property>
</bean>

property标签:表明通过组件类的setXxx()方法给组件对象设置属性
name属性:指定属性名xxx(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关
value属性:指定属性值

2.2 构造注入

<bean id="student2" class="com.spring6.bean.Student">
    // 根据构造器参数的顺序注入
    <constructor-arg value="1002"></constructor-arg>
    // 属性index表示构造器参数索引(从0开始)
    <constructor-arg index="1" value="李四"></constructor-arg>
    // 属性name表示参数名
    <constructor-arg name="age" value="33"></constructor-arg>
    <constructor-arg name="gender" value="女"></constructor-arg>
</bean>

2.3 特殊值处理

// age为null
<property name="age">
    <value>null</value>
</property>

// xml中'<'、'>'属于特殊字符,value="a < b"是错误的
<property name="expression">
    <!-- 使用CDATA节 -->
    <!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
    <!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
    <value><![CDATA[a < b]]></value>
</property>

3.为对象类型属性赋值

与基本数据类型不同,为对象类型属性赋值时需要使用ref属性而不是value属性

3.1 引入外部bean

// 配置Clazz类型的bean:
<bean id="clazz1" class="com.spring6.bean.Clazz">
    <property name="clazzId" value="1111"></property>
    <property name="clazzName" value="财源滚滚班"></property>
</bean>

//为Student中的clazz属性赋值:
<bean id="studentFour" class="com.spring6.bean.Student">
    <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
    <property name="clazz" ref="clazz1"></property>
</bean>

3.2 内部bean

内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性

<bean id="studentFour" class="com.spring6.bean.Student">
    <property name="clazz">
        // 不用设置ref属性(ref是用来引用外部bean的)
        <bean class="com.spring6.bean.Clazz">
            <property name="clazzId" value="2222"></property>
            <property name="clazzName" value="远大前程班"></property>
        </bean>
    </property>
</bean>

3.3 级联属性赋值

<bean id="studentFour" class="com.spring6.bean.Student">
    <property name="clazz" ref="clazz1"></property>
    <property name="clazz.clazzId" value="3333"></property>
    <property name="clazz.clazzName" value="最强王者班"></property>
</bean>

4.为其他类型属性赋值

4.1 数组

<bean id="studentFour" class="com.spring.bean6.Student">
    <property name="hobbies">
        <array>
            <value>看电影</value>
            <value>喝酒</value>
            <value>睡觉</value>
        </array>
    </property>
</bean>

4.2 List / Set集合

属性为:List / Set<Student>

<bean id="clazzTwo" class="com.spring6.bean.Clazz">
    <property name="students">
        // set集合只需将list改为set
        <list>
            <ref bean="studentOne"></ref>
            <ref bean="studentTwo"></ref>
            <ref bean="studentThree"></ref>
        </list>
    </property>
</bean>

4.3 Map集合

属性为:Map<String, Teacher>

<bean id="teacherOne" class="com.atguigu.spring6.bean.Teacher">
    <property name="teacherId" value="10010"></property>
    <property name="teacherName" value="大宝"></property>
</bean>

<bean id="teacherTwo" class="com.spring6.bean.Teacher">
    <property name="teacherId" value="10086"></property>
    <property name="teacherName" value="二宝"></property>
</bean>

<bean id="studentFour" class="com.spring6.bean.Student">
    <property name="teacherMap">
        <map>
            <entry>
                <key>
                    <value>10010</value>
                </key>
                <ref bean="teacherOne"></ref>
            </entry>
            // 简洁写法
            <entry key="10086" value-ref="teacherTwo"></entry>
        </map>
    </property>
</bean>

4.4 引用集合类型的Bean

<!--list集合类型的bean-->
<util:list id="students">
    <ref bean="studentOne"></ref>
    <ref bean="studentTwo"></ref>
</util:list>

<!--map集合类型的bean-->
<util:map id="teacherMap">
    <entry key="10010" value-ref="teacherOne"></entry>
    <entry key="10086" value-ref="teacherTwo"></entry>
</util:map>

<bean id="clazzTwo" class="com.atguigu.spring6.bean.Clazz">
    <property name="students" ref="students"></property>
</bean>

<bean id="studentFour" class="com.atguigu.spring6.bean.Student">
    <property name="teacherMap" ref="teacherMap"></property>
</bean>

注:使用util:list、util:map标签必须在配置文件中引入相应的命名空间

<?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: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">

5. p命名空间

引入p命名空间:

<?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:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       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">

引入p命名空间后可通过以下方式进行依赖注入:

<bean id="studentSix" class="com.spring6.bean.Student"
    p:id="1006" p:name="小明" p:clazz-ref="clazzOne" p:teacherMap-ref="teacherMap">
</bean>

6.自动装配

所谓自动装配即不需要我们在xml中为bean对象的属性赋值(只适用于对象属性),而是由IOC容器在xml配置文件中找到对应bean对象并注入依赖

自动装配是通过bean标签的autowire属性设置,有byType和byName两种

6.1 byType

<bean id="userController" class="com.spring6.autowire.controller.UserController" autowire="byType"></bean>
<bean id="userService" class="com.spring6.autowire.service.impl.UserServiceImpl" autowire="byType"></bean>
<bean id="userDao" class="com.spring6.autowire.dao.impl.UserDaoImpl"></bean>

根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值

若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null

若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException

6.2 byName

<bean id="userController" class="com.spring6.autowire.controller.UserController" autowire="byName"></bean>
<bean id="userService" class="com.spring6.autowire.service.impl.UserServiceImpl" autowire="byName"></bean>
<bean id="userServiceImpl" class="com.spring6.autowire.service.impl.UserServiceImpl" autowire="byName"></bean>
<bean id="userDao" class="com.spring6.autowire.dao.impl.UserDaoImpl"></bean>
<bean id="userDaoImpl" class="com.spring6.autowire.dao.impl.UserDaoImpl"></bean>

将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值

7.引入外部属性文件

外部jdbc.properties文件:

jdbc.user=root
jdbc.password=atguigu
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.driver=com.mysql.cj.jdbc.Driver

引入文件:

<?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">

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

    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

</beans>

三、Bean生命周期

1.作用域

在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:

如果是在WebApplicationContext环境下还会有request / session作用域,分别代表在一次请求 / 会话范围内有效

2.生命周期

  • bean对象创建(无参构造)
  • 依赖注入
  • 调用后置处理器(初始化之前)
  • bean对象初始化(需在配置bean时指定初始化方法)
  • 调用后置处理器(初始化之后)
  • bean对象就绪可以使用
  • 销毁(需在配置bean时指定销毁方法)
  • IOC容器关闭

Java类:

public class User {

    private Integer id;

    private String username;

    private String password;

    private Integer age;

    public User() {
        System.out.println("生命周期:1、创建对象");
    }

    public User(Integer id, String username, String password, Integer age) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        System.out.println("生命周期:2、依赖注入");
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void initMethod(){
        System.out.println("生命周期:3、初始化");
    }

    public void destroyMethod(){
        System.out.println("生命周期:5、销毁");
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                '}';
    }
}

配置bean:

<!-- 使用init-method属性指定初始化方法 -->
<!-- 使用destroy-method属性指定销毁方法 -->
<bean class="com.atguigu.spring6.bean.User" scope="prototype" init-method="initMethod" destroy-method="destroyMethod">
    <property name="id" value="1001"></property>
    <property name="username" value="admin"></property>
    <property name="password" value="123456"></property>
    <property name="age" value="23"></property>
</bean>

后置处理器(对所有Bean有效):

public class MyBeanProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("☆☆☆" + beanName + " = " + bean);
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("★★★" + beanName + " = " + bean);
        return bean;
    }
}

配置后置处理器:

<!-- bean的后置处理器要放入IOC容器才能生效 -->
<bean id="myBeanProcessor" class="com.atguigu.spring6.process.MyBeanProcessor"/>

四、基于注解管理Bean

Spring 从 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化 Spring 的 XML 配置

Spring 通过注解实现自动装配的步骤如下:

  1. 引入依赖
  2. 开启组件扫描
  3. 使用注解定义 Bean
  4. 依赖注入

1.引入依赖

<dependencies>
    <!--spring context依赖-->
    <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.3</version>
    </dependency>
</dependencies>

2.组件扫描

Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中

2.1 基础扫描

<?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-3.0.xsd
    http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    <!--开启组件扫描功能-->
    <context:component-scan base-package="com.spring6"></context:component-scan>
</beans>

2.2 指定排除扫描的组件

<context:component-scan base-package="com.spring6">
    <!-- context:exclude-filter标签:指定排除规则 -->
    <!-- 
 		type:设置排除或包含的依据
		type="annotation",根据注解排除,expression中设置要排除的注解的全类名
		type="assignable",根据类型排除,expression中设置要排除的类型的全类名
	-->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <!--<context:exclude-filter type="assignable" expression="com.spring6.controller.UserController"/>-->
</context:component-scan>

2.3 仅扫描指定组件

<context:component-scan base-package="com" use-default-filters="false">
    <!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
    <!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
    <!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	<!--<context:include-filter type="assignable" expression="com.spring6.controller.UserController"/>-->
</context:component-scan>

3.使用注解定义Bean

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean

@Component(value="user")  // <bean id="user" class="User"></bean>
// value属性可以省略,默认值为类名小写
public class User{
}

4.依赖注入

4.1 @Autowired

源码:

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

源码中有两处需要注意:

        1.该注解可以标注在:构造方法、方法、形参、属性、注解

        2.该注解有一个required属性,默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错

Autowired可以通过属性 / set方法 / 构造方法 / 形参注入

@Controller
public class UserController {
    @Autowired  // 属性注入,不需要构造方法和set方法(有默认无参构造)
    private UserService userService;

    public void out() {
        userService.out();
        System.out.println("Controller层执行结束。");
    }

}
@Controller
public class UserController {
    private UserService userService;

    @Autowired  // set注入
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void out() {
        userService.out();
        System.out.println("Controller层执行结束。");
    }

}
@Controller
public class UserController {
    private UserService userService;

    @Autowired  // 构造注入
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void out() {
        userService.out();
        System.out.println("Controller层执行结束。");
    }

}
@Controller
public class UserController {
    private UserService userService;

    // 形参注入
    public UserController(@Autowired UserService userService) {
        this.userService = userService;
    }

    public void out() {
        userService.out();
        System.out.println("Controller层执行结束。");
    }

}
@Service
public class UserServiceImpl implements UserService {
    // 只有一个构造方法时注解可以省略
    private UserDao userDao;

    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void out() {
        userDao.print();
        System.out.println("Service层执行结束");
    }
}
@Service
public class UserServiceImpl implements UserService {
    @Autowired  // 默认根据类型装配,如果一个类型有多个Bean实例则报错
    @Qualifier("userDaoImpl") // 添加注解指定bean的名字,根据名字装配
    private UserDao userDao;

    @Override
    public void out() {
        userDao.print();
        System.out.println("Service层执行结束");
    }
}

总结

  • @Autowired注解可以出现在:属性上、构造方法上、构造方法的参数上、setter方法上
  • 当带参数的构造方法只有一个,@Autowired注解可以省略
  • @Autowired注解默认根据类型注入。如果要根据名称注入的话,需要配合@Qualifier注解一起使用

4.2 @Resource

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

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

引入依赖:

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

根据name注入:

@Service
public class UserServiceImpl implements UserService {
    @Resource(name = "myUserDao")
    private UserDao myUserDao;

    @Override
    public void out() {
        myUserDao.print();
        System.out.println("Service层执行结束");
    }
}

name未知时以属性名为name查找:

@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserDao myUserDao;

    @Override
    public void out() {
        myUserDao.print();
        System.out.println("Service层执行结束");
    }
}

总结:

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

5.Spring全注解开发

全注解开发就是不再使用spring配置文件了,写一个配置类来代替配置文件

@Configuration
//@ComponentScan({"com.spring6.controller","com.spring6.service","com.spring6.dao"})
@ComponentScan("com.spring6")  // 开启注解扫描
public class Spring6Config {
}

测试类:

@Test
public void testAllAnnotation(){
    ApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
    UserController userController = context.getBean("userController", UserController.class);
    userController.out();
    logger.info("执行成功");
}
  • 11
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值