Spring中的自动装配是如何工作的?

586
我有点困惑 控制反转IoC)如何在Spring中工作。
假设我有一个名为UserServiceImpl的服务类,它实现了UserService接口。这应该如何进行 @Autowired
而在我的Controllers中,我应该如何实例化此服务的instance
我只需要执行以下操作吗?
UserService userService = new UserServiceImpl();
11个回答

796
首先,最重要的是,所有Spring Bean都是由容器管理的,它们“生活”在称为“应用程序上下文”的容器中。
其次,每个应用程序都有一个进入该上下文的入口。Web应用程序有一个Servlet,JSF使用el-resolver等。此外,还有一个地方,可以启动应用程序上下文并将所有Bean自动装配。在Web应用程序中,这可以是启动监听器。
自动装配是通过将一个Bean实例放置到另一个Bean实例的所需字段中来实现的。两个类都应该是Beans,即它们应该被定义为在应用程序上下文中运行。
在应用程序上下文中“生活”什么?这意味着上下文实例化对象,而不是你。也就是说-你永远不会创建new UserServiceImpl() -容器会找到每个注入点并在那里设置一个实例。
在你的控制器中,你只需要以下内容:
@Controller // Defines that this class is a spring bean
@RequestMapping("/users")
public class SomeController {

    // Tells the application context to inject an instance of UserService here
    @Autowired
    private UserService userService;

    @RequestMapping("/login")
    public void login(@RequestParam("username") String username,
           @RequestParam("password") String password) {

        // The UserServiceImpl is already injected and you can use it
        userService.login(username, password);

    }
}

一些说明:

  • 在你的applicationContext.xml文件中,应启用<context:component-scan>以便扫描类中的@Controller@Service等注释。
  • Spring-MVC应用程序的入口点是DispatcherServlet,但它对你来说是隐藏的,因此应用程序上下文的直接交互和引导发生在幕后。
  • UserServiceImpl还应被定义为bean,可以使用<bean id=".." class="..">或使用@Service注释。既然它是UserService的唯一实现者,它将被注入。
  • 除了@Autowired注释之外,Spring还可以使用可通过XML配置的自动装配。在这种情况下,所有具有与现有bean匹配的名称或类型的字段都自动注入一个bean。实际上,这是自动装配的最初想法 - 在没有任何配置的情况下注入具有依赖关系的字段。也可以使用其他注释,如@Inject@Resource

8
好的,UserServiceImpl被@Service注解标记,而UserService是接口。 - Bozho
21
默认范围为单例模式,因此您只会有一个Bean实例,在多个地方注入使用。如果您明确将范围定义为“原型”,那么可能会存在多个实例,具体是否延迟取决于配置。 - Bozho
5
非常感谢您的帖子,它让我对事情有了更清晰的认识。关于“因为它将是UserService的唯一实现者,所以会被注入。” - 如果有多个类实现了UserService,Spring如何知道应该使用哪个实现? - Shishigami
9
如果有一个被指定为“primary”的内容,它会使用它。否则,它会抛出一个异常。 - Bozho
3
不,userService仅被创建一次,它在单例作用域中。 - Bozho
显示剩余7条评论

71

这取决于您想要使用注解路线还是bean XML定义路线。

假设您将bean定义在applicationContext.xml中:

<beans ...>

    <bean id="userService" class="com.foo.UserServiceImpl"/>

    <bean id="fooController" class="com.foo.FooController"/>

</beans>

自动装配是在应用程序启动时发生的。因此,在 fooController 中,如果想要使用 UserServiceImpl 类,你可以按照以下方式进行注释:

public class FooController {

    // You could also annotate the setUserService method instead of this
    @Autowired
    private UserService userService;

    // rest of class goes here
}
当Spring看到@Autowired时,它会在applicationContext中寻找与属性匹配的类,并自动注入它。如果您有多个UserService bean,则必须明确指定应使用哪一个。
如果您执行以下操作:
UserService service = new UserServiceImpl();

除非您自己设置,否则它不会拾取@Autowired


3
applicationContext.xml中定义bean id有什么用?我们必须使用UserService类型定义userService变量。那么为什么要在xml文件中进行条目设置呢? - viper
@viper 他在谈论与之接口的问题,我相信。 - Madhu Nair

22

@Autowired是Spring 2.5引入的一个注解,仅用于注入。

例如:

class A {

    private int id;

    // With setter and getter method
}

class B {

    private String name;

    @Autowired // Here we are injecting instance of Class A into class B so that you can use 'a' for accessing A's instance variables and methods.
    A a;

    // With setter and getter method

    public void showDetail() {
        System.out.println("Value of id form A class" + a.getId(););
    }
}

12
这段代码无法编译,且通常是错误的。 @Autowired 并不意味着“你可以从类 A 中使用类 B 的所有函数(方法)和变量”。它的作用是将类 A 的实例注入到类 B 的实例中,这样你就可以从类 B 中调用 a.getId() - Dmitry Minkovsky
@dimadima所以如果他这样写System.out.println("Value of id form A class" + a.getId());而不是他实际上做的那样,那就更正确了。请回复,因为对我来说这个问题很直观,并且根据我的当前水平解释了自动装配。 - John Doe
@Autowired注解在Spring 2.5中被引入。http://docs.spring.io/spring-framework/docs/2.5.x/api/org/springframework/beans/factory/annotation/Autowired.html - SpringLearner
2
作为一个新手,为了更好地理解,@autowired会使用默认构造函数实例化A类吗?如果不是,如果我们使用autowired,如何在bean或service中实例化值。我猜如果它调用默认构造函数,为什么要首先使用自动装配,只需执行A a = new A()。请澄清一下? - Sameer
1
通过自动装配依赖项,您可以在单元测试中以及控制器、服务和Dao类中节省大量的样板代码,因为字段的实例化会自动进行。无需调用构造函数。 - kiltek
A类不是Spring bean。你在B类中如何自动装配它? - AbhishekB

11
@Autowired 在内部是如何工作的?
例子:
class EnglishGreeting {
   private Greeting greeting;
   //setter and getter
}

class Greeting {
   private String message;
   //setter and getter
}

如果不使用@Autowired注释,.xml文件将看起来很相似:

<bean id="englishGreeting" class="com.bean.EnglishGreeting">
   <property name="greeting" ref="greeting"/>
</bean>

<bean id="greeting" class="com.bean.Greeting">
   <property name="message" value="Hello World"/>
</bean>

如果你正在使用 @Autowired

class EnglishGreeting {
   @Autowired //so automatically based on the name it will identify the bean and inject.
   private Greeting greeting;
   //setter and getter
}

.如果没有使用@Autowired,那么.xml文件看起来会很相似:

<bean id="englishGreeting" class="com.bean.EnglishGreeting"></bean>

<bean id="greeting" class="com.bean.Greeting">
   <property name="message" value="Hello World"/>
</bean>

如果仍有疑问,请查看以下实时演示

@Autowired在内部是如何工作的?


6
您只需要在服务类 UserServiceImpl 上加上注解即可:
@Service("userService")

Spring容器会负责管理这个类的生命周期,因为它被注册为服务。

然后在您的控制器中,您可以使用自动装配(实例化)来调用并使用其功能:

@Autowired
UserService userService;

4

Spring依赖注入帮助您从类中消除耦合。不要像这样创建对象:

UserService userService = new UserServiceImpl();

在介绍 DI 后,您将会使用它:
@Autowired
private UserService userService;

为了实现这一点,您需要在 ServiceConfiguration 文件中创建您的服务的bean。之后,您需要将该 ServiceConfiguration 类导入到您的 WebApplicationConfiguration 类中,以便您可以像这样自动装配该bean到您的控制器中:
public class AccController {

    @Autowired
    private UserService userService;
} 

您可以在这里找到基于Java配置的POC示例:示例

3

使用 @Autowired,您可以有三种方法创建实例。

1. 通过在属性上标注 @Autowired

此注解可直接用于属性上,因此无需使用 getter 和 setter:

    @Component("userService")
    public class UserService {

        public String getName() {
            return "service name";
        }
    }

    @Component
    public class UserController {

        @Autowired
        UserService userService

    }

在上述例子中,Spring 在创建 UserController 时寻找并注入 userService@Autowired 注解也可用于 setter 方法。在下面的例子中,当注解用于 setter 方法时,在创建 UserController 时会使用 userService 实例来调用该 setter 方法:
public class UserController {

    private UserService userService;

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

3. 构造函数中的@Autowired

@Autowired注解也可以用在构造函数上。在下面的示例中,当注解用在构造函数上时,UserController被创建时,一个userService实例将作为参数注入到构造函数中:

public class UserController {

    private UserService userService;

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

只是补充一下,@Autowired在构造函数注入中并不是必须的。 - Kunal Varpe

1
简单来说,自动装配是自动连接链接,现在的问题是谁做这个以及哪种类型的链接。
答案是:容器做这个,支持辅助类型的连线,原始数据需要手动完成。
问题:容器如何知道什么类型的连线?
答案:我们定义它为按类型,按名称,构造函数。
问题:有没有一种方法不定义自动装配的类型?
答案:是的,通过一个注释 @Autowired 就可以实现。
问题:但系统如何知道我需要选择这种类型的辅助数据?
答案:您将在您的 spring.xml 文件中提供该数据或通过使用类的 sterotype 注释使容器自己为您创建对象。

1
标准方式:

@RestController
public class Main {
    UserService userService;

    public Main(){
        userService = new UserServiceImpl();
    }

    @GetMapping("/")
    public String index(){
        return userService.print("Example test");
    }
}

用户服务接口:
public interface UserService {
    String print(String text);
}

UserServiceImpl类:

public class UserServiceImpl implements UserService {
    @Override
    public String print(String text) {
        return text + " UserServiceImpl";
    }
}

输出:示例测试UserServiceImpl

这是紧密耦合的类的一个很好的例子,是不良设计的例子,测试会有问题(PowerMockito也不好)。

现在让我们来看一下SpringBoot依赖注入,它是松散耦合的一个好例子:

接口保持不变,

主类:

@RestController
public class Main {
    UserService userService;

    @Autowired
    public Main(UserService userService){
        this.userService = userService;
    }

    @GetMapping("/")
    public String index(){
        return userService.print("Example test");
    }
}

ServiceUserImpl类:

@Component
public class UserServiceImpl implements UserService {
    @Override
    public String print(String text) {
        return text + " UserServiceImpl";
    }
}

输出:示例测试 UserServiceImpl

现在编写测试变得更加容易:

@RunWith(MockitoJUnitRunner.class)
public class MainTest {
    @Mock
    UserService userService;

    @Test
    public void indexTest() {
        when(userService.print("Example test")).thenReturn("Example test UserServiceImpl");

        String result = new Main(userService).index();

        assertEquals(result, "Example test UserServiceImpl");
    }
}

我展示了构造函数上的@Autowired注解,但它也可以用于setter或field。


为什么要为主类创建一个构造函数?而不是自动装配声明? - Tarnished-Coder

0
记住,你必须在 Spring 配置文件中添加元素 <context:annotation-config/> 以启用 @Autowired 注解。这将注册 AutowiredAnnotationBeanPostProcessor,它会处理注解的处理。
然后,你可以使用字段注入方法来自动装配你的服务。
public class YourController{

 @Autowired
 private UserService userService; 

}

我从帖子Spring @autowired注解中发现了这个。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接