Skip to content

Spring教程 - 5 IoC容器 - 基于注解配置(1)

现在在开发的时候,使用 XML 来配置 Spring 使用的比较少了,现在大多数都是使用注解的方式,主要是方便。

但是 XML 的方式不是白学,实现的功能都是一样的,就是配置方式不同而已。

下面就来介绍使用如何注解来进行 Spring IoC 的容器的配置。

5.1 注解的使用

1 创建Bean类

下面直接实现在 Controller 中去调用 Service,在 Service 中去调用 Dao,这样的结构来实现注解装配。

  • 还是使用 Controller → Service(接口 + 实现类) → DAO(接口 + 实现类) 的分层架构,并通过接口 + 实现类的方式组织代码。

分层使代码结构更清晰,Controller负责响应用户请求,Service 处理处理业务逻辑,Dao负责操作数据库。


----------------dao层----------------

IUserDao.java

java
package com.foooor.hellospring.dao;

public interface IUserDao {
    /**
     * 获取用户
     */
    void getUser();
}
  • 接口不变。

UserDaoImpl.java

java
package com.foooor.hellospring.dao.impl;
import com.foooor.hellospring.dao.IUserDao;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements IUserDao {
    /**
     * 获取用户
     */
    public void getUser() {
        System.out.println("dao 获取用户信息");
    }
}
  • 实现 IUserDao 接口,并给类添加 @Repository 注解。

@Repository 表示的是持久层的 bean 注解。Spring 提供了多个注解,用于标注 Java 类,并在后续的运行中,将他们作为 Spring Bean 来管理。

Spring常用组件注解如下:

注解作用@Component 的关系
@Component通用注解,标记一个类交由 Spring 容器管理基础注解
@Controller用在控制层,也就是Controller层,将控制层的类标识为 Spring Bean@Component 的派生
@Service用在业务层上,也就是 Service 层,将业务层类标识为 Spring Bean@Component 的派生
@Repository用在数据访问层,也就是 Dao层,将 Dao 标识为 Spring Bean@Component 的派生
  • @Controller@Service@Repository 都是从 @Component 的派生来的,它们的功能基本是一样的,都是将类交由 Spring 来管理,所以在使用上没有区别,你可以在 ControllerService 上使用 @Component 注解也是可以的,但是为了 语义 和 特殊功能的使用,建议什么层使用什么注解,如果你不知道是哪一层的, 直接使用 @Component 注解就可以了。

继续创建 Service 和 Controller。

----------------service层----------------

IUserService.java

java
package com.foooor.hellospring.service;

public interface IUserService {
    /**
     * 获取用户信息
     */
    void getUser();
}
  • 普通的接口,没有变化。

UserServiceImpl.java

java
package com.foooor.hellospring.service.impl;
import com.foooor.hellospring.dao.IUserDao;
import com.foooor.hellospring.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private IUserDao userDao;

    /**
     * 获取用户
     */
    public void getUser() {
        System.out.println("service 获取用户信息");
        userDao.getUser();
    }
}
  • 首先实现 IUserService 接口,并给类添加 @Service 注解,标识是业务层的 Bean;
  • 需要依赖注入 userDao ,这里直接给 private IUserDao userDao; 添加 @Autowired 注解即可。

@Autowired 注解标识的属性,会按照类型自动进行装配,此时可以不需要 setter 和构造器,因为 Spring 会使用反射直接操作属性并进行注入。


----------------controller层----------------

同样的方式创建 Controller。

UserController.java

java
package com.foooor.hellospring.controller;
import com.foooor.hellospring.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    @Autowired
    private IUserService userService;

    /**
     * 获取用户信息
     */
    public void getUser() {
        System.out.println("controller 获取用户信息");
        userService.getUser();
    }
}
  • 首先给类添加 @Controller 注解;
  • 然后给 userService 添加 @Autowired 注解,进行依赖注入。

2 开启注解配置

Spring 默认是不开启 bean 注解装配的,因此需要配置开启注解装配。

首先也需要在 resources 目录下创建 bean.xml ,然后在其中使用 <context:component-scan> 配置要扫描哪个包下的 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
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置组件扫描 -->
    <context:component-scan base-package="com.foooor.hellospring" />

</beans>
  • 开启组件扫描,需要指定扫描的包,

  • 通过开启组件扫描,Spring 才会自动扫描添加了 @Component@Controller@Service@Repository 注解的类,并创建实例 bean 。

一般情况下都是使用比较顶层的包,作为扫描的包。如果有需要,想指定多个包,可以使用逗号 ,; 分隔:

xml
<context:component-scan base-package="com.foooor.hellospring,com.foooor.otherpackage,com.thirdparty.utils" />

或者使用通配符:

xml
<!-- 扫描 com.foooor 下所有子包,只会扫描下一级包,子子包不会扫描 -->
<context:component-scan base-package="com.foooor.*" />

<!-- 和base-package="com.foooor"效果相同,将扫描所有的子包  -->
<context:component-scan base-package="com.foooor..*" />

还可以排除指定的类或包:

xml
<context:component-scan base-package="com.example">
    <!-- 排除 com.foooor.test 包下的所有类,因为是regex正则表达式,所以需要使用\转义 -->
    <context:exclude-filter type="regex" expression="com\.foooor\.test\..*"/>
  
  	<!-- 排除 com.foooor.BaseService 类及其所有子类 -->
    <context:exclude-filter type="assignable" expression="com.foooor.BaseService"/>
    
    <!-- 或者排除某些注解的类,例如@Controller -->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
  • 用的不多,了解一下。

其实纯注解方式,这个 XML 也是可以省略的,使用配置类就可以了,后面5.5章节全注解开发会讲!


3 测试

现在程序已经可以了,Spring IoC 会自动扫描指定包下的 Bean,然后进行管理。

测试:

java
package com.foooor.hellospring;

import com.foooor.hellospring.controller.UserController;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 测试类
 */
public class UserTest {

    @Test
    public void testUserService() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        UserController userController = context.getBean(UserController.class);
        userController.getUser();
    }
}
  • 和之前的测试代码一样,已经可以输出了。
controller 获取用户信息
service 获取用户信息
dao 获取用户信息

5.2 bean的id

上面我们在使用 @Component@Controller@Service@Repository 注解的时候,并没有指定 bean 的 id,默认情况下 bean 的 id 就是类名的首字母小写

我们还可以手动指定 bean 的 id,如下:

java
@Controller("myController")  // 指定bean的id
public class UserController {
}
  • 上面手动指定 bean 的 id 为 userController@Component@Service@Repository 注解都支持这样的设置。

5.3 @Autowired注解

1 多种注入方式

在上面使用 @Autowired 注解是添加到属性上,其实 @Autowired 还可以在setter上、构造方法上、构造方法的形参上,都可以完成属性的注入。

举个栗子:

用在 setter 上:

java
private IUserService userService;

@Autowired
public void setUserService(IUserService userService) {
    this.userService = userService;
}

用在构造方法上:

java
private IUserService userService;

@Autowired
public UserController(IUserService userService) {
    this.userService = userService;
}

用在构造方法的形参上:

java
private IUserService userService;

public UserController(@Autowired IUserService userService) {
    this.userService = userService;
}

另外,如果类中只有一个有参数的构造方法,没有其他的有参或无参的构造方法了,那么构造方法上的 @Autowired 是可以省略的,知道就得了。


2 多个同类型bean

@Autowired 注解是通过类型来进行注入的,如果存在多个相同类型的实例,例如多个实现类实现同一个接口,那么在注入的时候就会报错。

如果确实有这样的需求,那么可以结合 @Qualifier 注解来指定注入哪个 bean。

举个栗子:

例如,我们的 IUserService.java 有两个实现类:

  • UserDatabaseServiceImpl.java
  • UserCacheServiceImpl.java

那么在注入的时候,可以通过 @Qualifier 注解指定注入哪个bean。

java
@Controller
public class UserController {

    @Autowired
    @Qualifier("userDatabaseServiceImpl")  // 指定注入UserServiceImpl实现类
    private IUserService userService;

    // ...
}
  • @Qualifier 指定的是实现类的 id,如果你没有指定 bean 的 id,那么就类名首字母小写,当然你可以手动指定。

3 required属性

@Autowired 注解有一个 required 属性,默认值为 true,表示在注入的时候,如果找不到注入的 bean,那么会报错。

我们可以设置 required 的值为 false,那么在注入的时候,如果bean 不存在,就不会注入,不会报错。

举个栗子:

java
@Controller
public class UserController {

    @Autowired(required = false)  // 如果IUserService没有实现类,那么也不会报错
    private IUserService userService;
  
    // ...
}

5.4 @Resource注解

除了使用 @Autowired 注解可以完成注入,我们还可以使用 @Resource 注解进行注入。

有什么区别呢?


1 来源不同

  • @Autowired

    Spring 提供的注解(org.springframework.beans.factory.annotation.Autowired),属于 Spring 特有功能。

  • @Resource

    JSR-250(Java 规范)中的注解(javax.annotation.Resource),属于标准 Java 规范,Spring 只是支持这个注解。另外需要注意:@Resource 注解在 JDK 的扩展包中,如果你的 JDK 版本小于JDK8,或者大于JDK11,那么还需要额外引入依赖。因为 JDK 11+javax.annotation 从 JDK 移除了,Spring 6 / Spring Boot 3 开始全面迁移到 Jakarta EE,需要用 jakarta.annotation.Resource

2 注入方式不同

  • @Autowired

    • 默认按 类型(byType) 装配,如果存在多个同类型的 Bean,则再结合 @Qualifier 或 Bean 名称 来确定。
    • 如果找不到合适的 Bean,会报错;可以通过 required = false 允许为 null
  • @Resource

    • 默认按 名称(byName) 装配,如果没有指定 name 属性,它会取属性名作为 Bean 名称去找。
    • 如果按名称没找到,则退回按类型(byType)查找,没找到则直接报错,不像 @Autowired 可以 required = false
    java
    @Resource
    private UserService userService; // 默认按属性名"userService"查找Bean,如果找不到,就按照类型查找,找不到报错
    
    @Resource(name="myUserService")
    private UserService userService; // 按name指定的"myUserService"查找bean

3 可配置属性

  • @Autowired

    • 只有一个属性:required(默认 true),可配合 @Qualifier("beanName") 使用。
  • @Resource

    • 有两个主要属性:name:指定 Bean 名称;type:指定 Bean 类型
    java
    package com.foooor.hellospring.controller;
    import com.foooor.hellospring.service.IUserService;
    import jakarta.annotation.Resource;
    import org.springframework.stereotype.Controller;
    
    @Controller
    public class UserController {
    
        @Resource(type=IUserService.class)  // 按类型查找bean,可以指定接口,或具体的实现类,找不到或找到多个会报错
        private IUserService userService; 
    
        // ...
    }

4 可作用对象

  • @Autowired

    • 可以用在属性上、setter方法上、构造方法上、构造方法的形参上。
  • @Resource

    • 可以用在属性上、setter方法上。
内容未完......