likes
comments
collection
share

spring通过注解实现IOC详解

作者站长头像
站长
· 阅读数 83

在上篇文章中,我们都是通过xml文件进行bean或者某些属性的赋值,其实还有另外一种注解的方式,在企业开发中使用的很多,在bean上添加注解,可以快速的将bean注册到ioc容器。

一、使用注解的方式注册bean到IOC容器中

1、首先我们先创建一个maven项目(后面熟悉了,可以直接创建spring项目)

1、new 一个project spring通过注解实现IOC详解 2、填写项目相关信息 spring通过注解实现IOC详解 3、修改maven配置信息 3-1、打开maven配置窗口 spring通过注解实现IOC详解 3-2、修改maven配置信息 spring通过注解实现IOC详解

2、配置spring maven依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.19.RELEASE</version>
</dependency>

spring通过注解实现IOC详解 可以看到spring的核心jar包已经全部导入。

3、我们安装MVC结构创建我们项目的目录结构

spring通过注解实现IOC详解 我们在 com.jony下分别创建了各个层的package。上篇文章中如果我们需要让spring实现这些类的注入,我们需要把这些类都在spring配置文件中进行bean的注入,那我们通过注解该如何实现呢?

4、配置spring的配置文件

我们在resource中新加spring的配置文件,并在xml中通过使用

<co4text:component-scan base-package="com.jony"></context:component-scan>

配置spring需要扫码的package路径,这样在这个package下的类就可以自动被spring扫描到了 spring通过注解实现IOC详解

5、核心注解

上面通过配置扫描的package可以扫描到java类,但是如果要注入的IOC容器中,还需要给类添加注解,平时用到的主要注解如下:

@Controller:控制器,推荐给controller层添加此注解 @Service:业务逻辑,推荐给业务逻辑层添加此注解 @Repository:仓库管理,推荐给数据访问层添加此注解 @Component:给不属于以上基层的组件添加此注解

注意: 1、我们虽然人为的给不同的层添加不同的注解,但是在spring看来,可以在任意层添加任意注解 spring底层是不会给具体的层次验证注解,这样写的目的只是为了提高可读性,最偷懒的方式 就是给所有想交由IOC容器管理的bean对象添加component注解 2、注解不可以加到interface上面,因为接口是不允许注册到IOC容器中,spring IOC容器在解析的时候会忽略到接口的注解

5-1、测试注解的效果

5-1-1、不添加注解测试

通过下图我们可以看到,虽然在xml中已经配置了,扫描com.jony的包,但是UserController.java 中并没有注解,这样在通过ioc取类对象的时候就会报错,告诉我们找不到。 spring通过注解实现IOC详解

5-1-2、添加注解

通过下图我们可以看到,加上@Controller之后,通过IOC我们就可以获取的类对象了。 spring通过注解实现IOC详解

5-1-3、给所有类添加注解

我们给service实现类,以及dao实现类分别加上@service和@Repository。通常情况下,我们不需要给entity加注解,如果需要可以添加@Component

spring通过注解实现IOC详解

注意:当使用注解注册组件和使用配置文件注册组件是一样的,但是要注意: 1、组件的id默认就是组件的类名首字符小写,如果非要改名字的话,直接在注解中添加即可(如我们的UserController在测试类中使用userController),如果需要自定义可以设置为:@Controller("ab")\

spring通过注解实现IOC详解 2、组件默认情况下都是单例的,如果需要配置多例模式的话,可以在注解下添加@Scope注解

二、定义扫描包时要包含的类和不要包含的类

当定义好基础的扫描包后,在某情况下可能要有选择性的配置是否要注册bean到IOC容器中,此时可以通过如下的方式进行配置。

2-1 设置不扫描

2-1-1、spring 配置文件如下

在 context:conponent-san中添加context:exclude-filter就可以进行排除设置,其中

  • type:表示指定过滤的规则 annotation:按照注解进行排除,标注了指定注解的组件不要,expression表示要过滤的注解 assignable:指定排除某个具体的类,按照类排除,expression表示不注册的具体类名 aspectj:后面讲aop的时候说明要使用的aspectj表达式,基本不用 custom:定义一个typeFilter,自己写代码决定哪些类被过滤掉,基本不用 regex:使用正则表达式过滤,基本不用
<!--定义扫描的包-->
<context:component-scan base-package="com.jony">
    <!--排除不需要过滤的注解-->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

上面的配置了@Controller不被扫描,那expression的值是如何取到的呢?看下面截图

spring通过注解实现IOC详解 光标定位到@Controller 右键到 copy Reference即可复制@Controller的完整限定名。

2-1-2、测试如下

通过排除@Controller就无法获得类对象了,如下 spring通过注解实现IOC详解

2-1-3、通过类名设置不被扫描

spring通过注解实现IOC详解

2-2、设置扫描

2-2-1、给UserController的注解去掉,然后我们运行测试代码

这样是无法获得类对象的,上面也测试过 spring通过注解实现IOC详解

2-2-2、通过设置spring配置文件,进行指定扫描

铜鼓下图我们可以看到,设置了context:include-filter就可以让spring进行指定类的扫描了,配置中的type和上面context:exclude-filter中的type设置一直,这边就不做具体演示了。 spring通过注解实现IOC详解

三、使用@AutoWrite进行自动注入

使用注解的方式实现自动注入需要使用@AutoWired注解。

spring通过注解实现IOC详解 通过上图我们就可以看到,通过AutoWrite,就可以把对象注入到另外一个bean中了。

注意:@AutoWrite默认根据ByType去匹配bean的,在Controller中通过@AutoWrite 注入的是UserService,然后我们的UserServiceImpl有实现了UserService接口,因此UserServiceImpl和UserService被spring认为是一个类型,所以此处实际注入的是UserServiceImpl类。

3-1、@AutoWrite的匹配方式

下图中,我们有两个service的实现类 spring通过注解实现IOC详解

3-1-1、UserController中注入service

package com.jony.controller;

import com.jony.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    public void getUser(){
        userService.getUser();
    }
}

这样实现我们userService的实现类就变成了两个

3-1-2、测试结果

通过下图,我们可以看到通过userService,进行匹配的时候发现了两个bean,user1ServiceImpl,userServiceImpl,又通过userService根据名词查找的时候,没有匹配到,则就会报错 spring通过注解实现IOC详解 以上问题,主要就是因为通过类型匹配到了两个,然后通过名字又没找到报错,所以解决方案就是我们修改一下注入的名称即可,如下

3-1-3、通过类型匹配到多个bean,通过名称无法找到ben报错解决

3-1-3-1、修改注入bean的名称

我们把之前userService,改为userServiceImpl,这样在通过类型匹配到user1ServiceImpl,userServiceImpl,然后就可以通过通过名称匹配到userServiceImpl,从而解决报错问题。 spring通过注解实现IOC详解

3-1-3-2、修改一开始注入bean的名称

可以在service的实现层,@Service添加一个别名,如下 spring通过注解实现IOC详解

3-1-3-3、通过@Qualifier指定bean名称(覆盖原来bean名称)

@Qualifier("userServiceImpl")
private UserService userService;

spring通过注解实现IOC详解 通过@Qualifier 设置名称之后,就会通过这个名称去找bean,即使这时候通过类型或者userService可以找到也不会起作用!

3-1-3-3-1、先去掉重复类型的类

删掉了之前User1ServiceImpl,这样根据类型查找的时候就可以匹配到UserServiceImpl,执行没问题 spring通过注解实现IOC详解

3-1-3-3-1、通过@Qualifier设置存在的bean
@Qualifier("userServiceImpl")

通过上面的配置就可以通过名称直接匹配到UserServiceImpl了 spring通过注解实现IOC详解

3-1-3-3-1、通过@Qualifier设置不存在的bean

设置了不存在的bean程序就会报错 spring通过注解实现IOC详解

3-1-3-4、通过@Primary设置主要注入bean

把之前删掉的User1ServiceImpl再次还原,我们既不设置@Service的名称,也不修改Controller注入的名称,同时也不使用@Qualifier的情况下,我们只需要通过@Primary设置通过类型匹配到多个bean中的某一个,将其设置为主要自动注入的类,就可以解决报错问题 spring通过注解实现IOC详解

3-1-3、总结

如果@AutoWrite根据类型去匹配的时候,如果匹配到了多个,那么就会通过名字去进行匹配,如果名字没有匹配到则会报错.我们就可以通过两种方式修改bean的别名或者添加@Qualifier设置bean名称来解决,或者通过设置@Primary设置主要自动注入bean来解决。

∗∗注意∗∗\color{red}{**注意**}注意:修改bean的名字@Controller @Service @Repository @component都可以修改bean的名字

3-2、AutoWrite的使用

我们进入@AutoWrite注解可以看到如下

spring通过注解实现IOC详解 @AutoWrite可以标注在构造函数、方法、参数、字段或者其他标注注解的类型。

3-3、@Resource和@AutoWrite的区别

在使用自动装配的时候,出了可以使用@AutoWired注解之外,还可以使用@Resource注解,大家需要知道这两个注解的区别。

1、@AutoWired:是spring中提供的注解,@Resource:是jdk中定义的注解,依靠的是java的标准

2、@AutoWired默认是按照类型进行装配,默认情况下要求依赖的对象必须存在,@Resource默认是按照名字进行匹配的,同时可以指定name属性。

3、@AutoWired只适合spring框架,而@Resource扩展性更好

四、通过@Value 设置依赖注入的属性

此处主要理解@Value的使用 @Value("xxx"):设置硬编码值 @Value("${}"):设置读取外部配置文件的值 @Value("#{}"):设置SpEL表达式

4-1、添加一个外部资源文件

spring通过注解实现IOC详解

4-2、设置spring配置文件读取资源文件

通过context:property-placeholder就可以读取外部资源文件了

spring通过注解实现IOC详解

4-3、我们修改User类,并添加@Value注解

package com.jony.entity;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class User {
    //通过@Value设置硬编码值
    @Value("张三")
    private String name;

    //通过${} 设置读取外部资源数据
    @Value("${user.age}")
    private Integer age;

    //通过#{} 设置SpEL表达式
    @Value("#{100*100}")
    private Integer money;

    @Value("#{role.name}")
    private String role;

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public Integer getMoney() {
        return money;
    }

    public void setMoney(Integer money) {
        this.money = money;
    }

    public Integer getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                ", age=" + age +
                ", money=" + money +
                ", role='" + role + ''' +
                '}';
    }
}

4-4、添加Role类

一定要添加@Component注解,否则在User中的@Value("#{role.name}")将会报错

package com.jony.entity;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Role {
    @Value("管理员")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

4-4 测试

spring通过注解实现IOC详解 通过上图可以看到,我们使用@Value 的三种方式已经成功将值注入的bean中。

五、泛型依赖注入

平时实际项目中,我们会把一些公共的方法放到一个base接口中,比如实现对数据库的基本CURD抽出去,然后在各自的服务中进行实现,下面对上面的代码就行修改,目录结构如下

spring通过注解实现IOC详解

5-1、修改UserService 接口为公共的service,设置公共的CRUD,我这就先只做getBean操作

package com.jony.service;

public interface BaseService<T> {
    T getBean();
}

5-2、修改UserServiceImpl

在实现BaseService的时候泛型传入User,最终getBean就是查询User

package com.jony.service.impl;

import com.jony.dao.UserDao;
import com.jony.entity.User;
import com.jony.service.BaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements BaseService<User> {

    @Autowired
    private UserDao userDao;
    @Override
    public User getBean() {
        System.out.println("UserServiceImpl");
        userDao.getBean();
        return null;
    }
}

5-3、修改RoleServiceImpl

在实现BaseService的时候泛型传入Role,最终getBean就是查询Role

package com.jony.service.impl;

import com.jony.dao.UserDao;
import com.jony.entity.Role;
import com.jony.service.BaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RoleServiceImpl implements BaseService<Role> {

    @Autowired
    private UserDao userDao;
    @Override
    public Role getBean() {
        System.out.println("RoleServiceImpl");
        userDao.getBean();
        return null;
    }
}

5-4、UserController测试

在注入BaseService的时候泛型指定User,这样执行的service实现就是UserServiceImpl了,这样也可以解决@AutoWrite通过类型查找多个bean报错的问题了

spring通过注解实现IOC详解

六、使用@DependOn 设置依赖优先加载顺序

6-1、我们先准备两个类

6-1-1、User.java

package com.jony.entity;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class User {
   //通过@Value设置硬编码值
   @Value("张三")
   private String name;

   //通过${} 设置读取外部资源数据
   @Value("${user.age}")
   private Integer age;

   //通过#{} 设置SpEL表达式
   @Value("#{100*100}")
   private Integer money;

   @Value("#{role.name}")
   private String role;

   public String getRole() {
       return role;
   }

   public void setRole(String role) {
       this.role = role;
   }

   public Integer getMoney() {
       return money;
   }

   public void setMoney(Integer money) {
       this.money = money;
   }

   public Integer getAge() {
       return age;
   }

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

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   @Override
   public String toString() {
       return "User{" +
               "name='" + name + ''' +
               ", age=" + age +
               ", money=" + money +
               ", role='" + role + ''' +
               '}';
   }
   public User(){
       System.out.println("User 加载了");
   }
}

6-1-2、Role.java

package com.jony.entity;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Role {
    @Value("管理员")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public Role(){
        System.out.println("Role 加载了");
    }
}

6-2、测试加载

通过给User和Role 两个类添加@Component,让spring 进行管理,然后启动的时候,默认加载顺序如下,那如果我们要让User先加载呢? spring通过注解实现IOC详解

6-3、设置优先加载顺序

我们可以在Role中设置User先加载,可以通过如下来进行设置

@DependsOn("user")

最终Role代码为:

package com.jony.entity;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

@Component
@DependsOn("user")
public class Role {
    @Value("管理员")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public Role(){
        System.out.println("Role 加载了");
    }
}

6-3-1、通过设置@DependOn查看结果

可以看到这次User先加载了 spring通过注解实现IOC详解

七、使用@Lazy懒加载

顾明思议,在bean上加上@Lazy,在一开始加载的时候,spring就不会把类加载到IOC中,当使用的时候再进行注入,下面就不做演示了。

八、使用@Scope 设置bean的作用域

默认是单例模式@Scope("singleton"),同一个类只会创建一次。如下图

spring通过注解实现IOC详解

8-1、通过设置@Scope("prototype")多例注入

package com.jony.entity;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@DependsOn("user")
@Scope("prototype")
public class Role {
    @Value("管理员")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public Role(){
        System.out.println("Role 加载了");
    }
}

8-2、测试结果

spring通过注解实现IOC详解

九、监控Bean的生命周期

9-1、通过@PostConstruct 监控bean的初始化

@PostConstruct
public void init(){
    System.out.println("User 初始化");
}

9-2、通过@PreDestroy 监控bean的销毁

@PreDestroy
public void desstroy(){
    System.out.println("user 销毁");
}

9-3 测试

通过下图结果,我们就可以看到bean的初始化和注销结果了 spring通过注解实现IOC详解

总结

1、本篇文章主要介绍了通过配置spring.xml,利用context:component-scan进行包扫描,达到我们要注入bean的第一步 2、使用@Controller/@Service/@Repository/@Component添加注解,告诉spring被注解的对象需要添加到IOC中。 3、通过context:exclude-filter排除不需要注入IOC容器的对象,以及使用context:include-filter添加需要注入IOC容器的对象,这两个配置中都有type属性,需要记住常用的annotation、assignable

  • type:表示指定过滤的规则 annotation:按照注解进行排除,标注了指定注解的组件不要,expression表示要过滤的注解 assignable:指定排除某个具体的类,按照类排除,expression表示不注册的具体类名 aspectj:后面讲aop的时候说明要使用的aspectj表达式,基本不用 custom:定义一个typeFilter,自己写代码决定哪些类被过滤掉,基本不用 regex:使用正则表达式过滤,基本不用 4、掌握@AutoWrite注解的方式,先通过byType查找,如果没找到或者找到多个,再通过byName查找对象。如果通过byType找到多个bean,可以通过修改bean的名称,精准注入,也可以通过修改bean的别名如@Service("userService"),同时还可以通过@Qualifier指定bean的名称,或者通过设置@primart设置有限加载的bean来解决报错问题。 5、知道@AutoWrite和@Resource的区别。两者一个是spring的一个是jdk的,然后一个是先通过byType一个是通过byName。 6、@Value的使用,可以通过直接赋值,或者${}读取外部资料文件,或者使用#{}使用SpEL表达式。 7、通过@AutoWrite可以注入泛型,解决底层重复的代码。 8、使用@DependsOn设置依赖优先加载的类。 9、使用@Lazy 设置懒加载 10、使用@scope设置bean的作用域,分别有singleton和prototype。 11、使用@PostConstruct监控bean的初始化,以及使用@PreDestroy监控bean的注销。
转载自:https://juejin.cn/post/7073439880998551566
评论
请登录