关于java:最全的Spring依赖注入方式你都会了吗

10次阅读

共计 7644 个字符,预计需要花费 20 分钟才能阅读完成。

前言

Spring 正如其名字,给开发者带来了春天,Spring 是为解决企业级利用开发的复杂性而设计的一款框架,其设计理念就是:简化开发。

Spring 框架中最核心思想就是:

  • IOC(管制反转): 即转移创建对象的控制权,将创建对象的控制权从开发者转移到了 Spring 框架。
  • AOP(切面编程): 将公共行为(如记录日志,权限校验等)封装到可重用的模块中,而使本来的模块内只需关注本身的个性化行为。

本文,将次要介绍 Spring 中 IOC 的依赖注入,

管制反转 IOC

就 IOC 自身而言,其并不是什么新技术,只是一种思维理念。IOC 的外围就是原先创立一个对象,咱们须要本人间接通过 new 来创立,而 IOC 就相当于有人帮们创立好了对象,须要应用的时候间接去拿就行,IOC 次要有两种实现形式:

  • DL(Dependency Lookup):依赖查找。

这种就是说容器帮咱们创立好了对象,咱们须要应用的时候本人再被动去容器中查找,如:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/application-context.xml");
Object bean = applicationContext.getBean("object");
  • DI(Dependency Inject):依赖注入。

依赖注入相比拟依赖查找又是一种优化,也就是咱们不须要本人去查找,只须要通知容器以后须要注入的对象,容器就会主动将创立好的对象进行注入(赋值)。

依赖注入 DI

通过 xml 的注入形式咱们不做探讨,在这里次要探讨基于注解的注入形式,基于注解的惯例注入形式通常有三种:

  • 基于属性注入
  • 基于 setter 办法注入
  • 基于结构器注入

三种惯例注入形式

接下来就让咱们别离介绍一下三种惯例的注入形式。

属性注入

通过属性注入的形式十分罕用,这个应该是大家比拟相熟的一种形式:

@Service
public class UserService {
    @Autowired
    private Wolf1Bean wolf1Bean;// 通过属性注入
}

setter 办法注入

除了通过属性注入,通过 setter 办法也能够实现注入:

@Service
public class UserService {
    private Wolf3Bean wolf3Bean;
    
    @Autowired  // 通过 setter 办法实现注入
    public void setWolf3Bean(Wolf3Bean wolf3Bean) {this.wolf3Bean = wolf3Bean;}
}

结构器注入

当两个类属于强关联时,咱们也能够通过结构器的形式来实现注入:

@Service
public class UserService {
  private Wolf2Bean wolf2Bean;
    
     @Autowired // 通过结构器注入
    public UserService(Wolf2Bean wolf2Bean) {this.wolf2Bean = wolf2Bean;}
}

接口注入

在下面的三种惯例注入形式中,如果咱们想要注入一个接口,而以后接口又有多个实现类,那么这时候就会报错,因为 Spring 无奈晓得到底应该注入哪一个实现类。

比方咱们下面的三个类全副实现同一个接口 IWolf,那么这时候间接应用惯例的,不带任何注解元数据的注入形式来注入接口 IWolf。

@Autowired
private IWolf iWolf;

此时启动服务就会报错:

这个就是说原本应该注入一个类,然而 Spring 找到了三个,所以没法确认到底应该用哪一个。这个问题如何解决呢?

解决思路次要有以下 5 种:

通过配置文件和 @ConditionalOnProperty 注解实现

通过 @ConditionalOnProperty 注解能够联合配置文件来实现惟一注入。上面示例就是说如果配置文件中配置了 lonely.wolf=test1,那么就会将 Wolf1Bean 初始化到容器,此时因为其余实现类不满足条件,所以不会被初始化到 IOC 容器,所以就能够失常注入接口:

@Component
@ConditionalOnProperty(name = "lonely.wolf",havingValue = "test1")
public class Wolf1Bean implements IWolf{
}

当然,这种配置形式,编译器可能还是会提醒有多个 Bean,然而只有咱们确保每个实现类的条件不统一,就能够失常应用。

通过其余 @Condition 条件注解

除了下面的配置文件条件,还能够通过其余相似的条件注解,如:

  • @ConditionalOnBean:当存在某一个 Bean 时,初始化此类到容器。
  • @ConditionalOnClass:当存在某一个类时,初始化此类的容器。
  • @ConditionalOnMissingBean:当不存在某一个 Bean 时,初始化此类到容器。
  • @ConditionalOnMissingClass:当不存在某一个类时,初始化此类到容器。

相似这种实现形式也能够非常灵活的实现动态化配置。

不过下面介绍的这些办法仿佛每次都只能固定注入一个实现类,那么如果咱们就是想多个类同时注入,不同的场景能够动静切换而又不须要重启或者批改配置文件,又该如何实现呢?

通过 @Resource 注解动静获取

如果不想手动获取,咱们也能够通过 @Resource 注解的模式动静指定 BeanName 来获取:

@Component
public class InterfaceInject {@Resource(name = "wolf1Bean")
    private IWolf iWolf;
}

如上所示则只会注入 BeanName 为 wolf1Bean 的实现类。

通过汇合注入

除了指定 Bean 的形式注入,咱们也能够通过汇合的形式一次性注入接口的所有实现类:

@Component
public class InterfaceInject {
    @Autowired
    List<IWolf> list;

    @Autowired
    private Map<String,IWolf> map;
}

下面的两种模式都会将 IWolf 中所有的实现类注入汇合中。如果应用的是 List 汇合,那么咱们能够取出来再通过 instanceof 关键字来断定类型;而通过 Map 汇合注入的话,Spring 会将 Bean 的名称(默认类名首字母小写)作为 key 来存储,这样咱们就能够在须要的时候动静获取本人想要的实现类。

@Primary 注解实现默认注入

除了下面的几种形式,咱们还能够在其中某一个实现类上加上 @Primary 注解来示意当有多个 Bean 满足条件时,优先注入以后带有 @Primary 注解的 Bean:

@Component
@Primary
public class Wolf1Bean implements IWolf{
}

通过这种形式,Spring 就会默认注入 wolf1Bean,而同时咱们依然能够通过上下文手动获取其余实现类,因为其余实现类也存在容器中。

手动获取 Bean 的几种形式

在 Spring 我的项目中,手动获取 Bean 须要通过 ApplicationContext 对象,这时候能够通过以下 5 种形式进行获取:

间接注入

最简略的一种办法就是通过间接注入的形式获取 ApplicationContext 对象,而后就能够通过 ApplicationContext 对象获取 Bean:

@Component
public class InterfaceInject {
    @Autowired
    private ApplicationContext applicationContext;// 注入

    public Object getBean(){return applicationContext.getBean("wolf1Bean");// 获取 bean
    }
}

通过 ApplicationContextAware 接口获取

通过实现 ApplicationContextAware 接口来获取 ApplicationContext 对象,从而获取 Bean。须要留神的是,实现 ApplicationContextAware 接口的类也须要加上注解,以便交给 Spring 对立治理(这种形式也是我的项目中应用比拟多的一种形式):

@Component
public class SpringContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}

    /**
     * 通过名称获取 bean
     */
    public static <T>T getBeanByName(String beanName){return (T) applicationContext.getBean(beanName);
    }

    /**
     * 通过类型获取 bean
     */
    public static <T>T getBeanByType(Class<T> clazz){return (T) applicationContext.getBean(clazz);
    }
}

封装之后,咱们就能够间接调用对应的办法获取 Bean 了:

Wolf2Bean wolf2Bean = SpringContextUtil.getBeanByName("wolf2Bean");
Wolf3Bean wolf3Bean = SpringContextUtil.getBeanByType(Wolf3Bean.class);

通过 ApplicationObjectSupport 和 WebApplicationObjectSupport 获取

这两个对象中,WebApplicationObjectSupport 继承了 ApplicationObjectSupport,所以并无本质的区别。

同样的,上面这个工具类也须要减少注解,以便交由 Spring 进行对立治理:

@Component
public class SpringUtil extends /*WebApplicationObjectSupport*/ ApplicationObjectSupport {
    private static ApplicationContext applicationContext = null;

    public static <T>T getBean(String beanName){return (T) applicationContext.getBean(beanName);
    }

    @PostConstruct
    public void init(){applicationContext = super.getApplicationContext();
    }
}

有了工具类,在办法中就能够间接调用了:

@RestController
@RequestMapping("/hello")
@Qualifier
public class HelloController {@GetMapping("/bean3")
    public Object getBean3(){Wolf1Bean wolf1Bean = SpringUtil.getBean("wolf1Bean");
        return wolf1Bean.toString();}
}

通过 HttpServletRequest 获取

通过 HttpServletRequest 对象,再联合 Spring 本身提供的工具类 WebApplicationContextUtils 也能够获取到 ApplicationContext 对象,而 HttpServletRequest 对象能够被动获取(如下 getBean2 办法),也能够被动获取(如下 getBean1 办法):

@RestController
@RequestMapping("/hello")
@Qualifier
public class HelloController {@GetMapping("/bean1")
    public Object getBean1(HttpServletRequest request){
        // 间接通过办法中的 HttpServletRequest 对象
        ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
        Wolf1Bean wolf1Bean = (Wolf1Bean)applicationContext.getBean("wolf1Bean");

        return wolf1Bean.toString();}

    @GetMapping("/bean2")
    public Object getBean2(){HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// 手动获取 request 对象
        ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());

        Wolf2Bean wolf2Bean = (Wolf2Bean)applicationContext.getBean("wolf2Bean");
        return wolf2Bean.toString();}
}

其余形式获取

当然,除了下面提到的办法,咱们也能够应用最开始提到的 DL 中代码示例去手动 new 一个 ApplicationContext 对象,然而这样就意味着从新初始化了一次,所以是不倡议这么去做,然而在写单元测试的时候这种形式是比拟适宜的。

谈谈 @Autowrite 和 @Resource 以及 @Qualifier 注解的区别

下面咱们看到了,注入一个 Bean 能够通过 @Autowrite,也能够通过 @Resource 注解来注入,这两个注解有什么区别呢?

  • @Autowrite:通过类型去注入,能够用于结构器和参数注入。当咱们注入接口时,其所有的实现类都属于同一个类型,所以就没方法晓得抉择哪一个实现类来注入。
  • @Resource:默认通过名字注入,不能用于结构器和参数注入。如果通过名字找不到惟一的 Bean,则会通过类型去查找。如下能够通过指定 name 或者 type 来确定惟一的实现:
@Resource(name = "wolf2Bean",type = Wolf2Bean.class)
 private IWolf iWolf;

@Qualifier 注解是用来标识合格者,当 @Autowrite 和 @Qualifier 一起应用时,就相当于是通过名字来确定惟一:

@Qualifier("wolf1Bean")
@Autowired
private IWolf iWolf;

那可能有人就会说,我间接用 @Resource 就好了,何必用两个注解联合那么麻烦,这么一说仿佛显得 @Qualifier 注解有点多余?

@Qualifier 注解是多余的吗

咱们先看上面申明 Bean 的场景,这里通过一个办法来申明一个 Bean (MyElement),而且办法中的参数又有 Wolf1Bean 对象,那么这时候 Spring 会帮咱们主动注入 Wolf1Bean:

@Component
public class InterfaceInject2 {
    @Bean
    public MyElement test(Wolf1Bean wolf1Bean){return new MyElement();
    }
}

然而如果说咱们把下面的代码略微改一下,把参数改成一个接口,而接口又有多个实现类,这时候就会报错了:

@Component
public class InterfaceInject2 {
    @Bean
    public MyElement test(IWolf iWolf){// 此时因为 IWolf 接口有多个实现类,会报错
        return new MyElement();}
}

@Resource 注解又是不能用在参数中,所以这时候就须要应用 @Qualifier 注解来确认惟一实现了(比方在配置多数据源的时候就常常应用 @Qualifier 注解来实现):

@Component
public class InterfaceInject2 {
    @Bean
    public MyElement test(@Qualifier("wolf1Bean") IWolf iWolf){return new MyElement();
    }
}

总结

本文次要讲述了如何在 Spring 中应用灵便的形式来实现各种场景的注入形式,并且着重介绍了当一个接口有多个实现类时应该如何注入的问题,最初也介绍了罕用几个注入注解的区别,通过本文,置信大家对如何应用 Spring 中的依赖注入会更加的相熟。

正文完
 0