文章目录

    • 3.1 环境与profile
  • 3.2 条件化的bean

  • 3.3 处理自动装配的歧义性

  • 3.4 Bean的作用域

  • 3.5 运行时植注入

  • 3.6 小结

本章内容:

  • Spring profile

  • 条件化的bean声明

  • 自动装配与歧义性

  • bean的作用域

  • Spring表达式语言

在上一章中,我们看到了一些最为核心的bean装配技术。你可能会发现上一章学到的知识有很大的用处。但是,bean装配所涉及的领域并不仅仅局限于上一章  所学习到的内容。Spring提供了多种技巧,借助它们可以实现更为高级的bean装配功能。

在本章中,我们将会深入介绍一些这样的高级技术。本章中所介绍的技术也许你不会天天都用到,但这并不意味着它们的价值会因此而降低。

3.1 环境与profile


在开发软件的时候,有一个很大的挑战就是将应用程序从一个环境迁移到另外一个环境。开发阶段中,某些环境相关做法可能并不适合迁移到生产环境中,甚至即便迁移过去也无法正常工作。数据库配置、加密算法以及与外部系统的集成是跨环境部署时会发生变化的几个典型例子。

比如,考虑一下数据库配置。在开发环境中,我们可能会使用嵌入式数据库,并预先加载测试数据。

数据源的有三种连接配置,分别是

// 通过EmbeddedDatabaseBuilder会搭建一个嵌入式的Hypersonic的数据库

@Bean(destroyMethod = “shutdown”)

@Profile(“dev”)

public DataSource embeddedDataSource() {

return new EmbeddedDatabaseBuilder()

.setType(EmbeddedDatabaseType.H2)

.addScript(“classpath:schema.sql”)

.addScript(“classpath:test-data.sql”)

.build();

}

// 通过JNDI获取DataSource能够让容器决定该如何创建这个DataSource

@Bean

@Profile(“prod”)

public DataSource jndiDataSource() {

JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();

jndiObjectFactoryBean.setJndiName(“jdbc/myDS”);

jndiObjectFactoryBean.setResourceRef(true);

jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);

return (DataSource) jndiObjectFactoryBean.getObject();

}

// 还可以配置为Commons DBCP连接池,BasicDataSource可替换为阿里的DruidDataSource连接池

@Bean(destroyMethod = “close”)

@Profile(“qa”)

public DataSource datasource(){

BasicDataSource datasource = new BasicDataSource();

datasource.setUrl(“jdbc:h2:tcp://dbserver/~/test”);

datasource.setDriverClassName(“org.h2.Driver”);

datasource.setUsername(“sa”);

datasource.setPassword(“password”);

datasource.setInitialSize(20);

datasource.setMaxActive(30);

return dataSource;

}

Spring为环境相关的bean所提供的解决方案不是在构建的时候做出决定,而是等待运行时再来确定。Spring引入了bean的profile的功能,在每个数据库连接配置的bean上添加@Profile,指定这个bean属于哪一个profile。

Spring3.1需要将@Profile指定在配置类上,Spring3.2就可以指定在方法上了。

我们也可以在XML中通过<bean>元素的profile属性指定。例如:

<?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:jdbc=“http://www.springframework.org/schema/jdbc”

xmlns:jee=“http://www.springframework.org/schema/jee” xmlns:p=“http://www.springframework.org/schema/p”

xsi:schemaLocation="

http://www.springframework.org/schema/jee

http://www.springframework.org/schema/jee/spring-jee.xsd

http://www.springframework.org/schema/jdbc

http://www.springframework.org/schema/jdbc/spring-jdbc.xsd

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

<jdbc:embedded-database id=“dataSource” type=“H2”>

<jdbc:script location=“classpath:schema.sql” />

<jdbc:script location=“classpath:test-data.sql” />

</jdbc:embedded-database>

<jee:jndi-lookup id=“dataSource”

lazy-init=“true”

jndi-name=“jdbc/myDatabase”

resource-ref=“true”

proxy-interface=“javax.sql.DataSource” />

下一步就是激活某个profile

Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的,但如果没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。如果spring.profiles.active和spring.profiles.default均没有设置的话,那就没有激活的profile,因此只会创建那些没有定义在profile中的bean。

有多种方式来设置这两个属性:

  • 作为DispatcherServlet的初始化参数;

  • 作为Web应用的上下文参数;

  • 作为JNDI条目;

  • 作为环境变量;

  • 作为JVM的系统属性;

  • 在集成测试类上,使用@ActiveProfiles注解设置。

例如,在web应用中,设置spring.profiles.default的web.xml文件会如下所示:

<?xml version="1.0" encoding="UTF-8"?>

<web-app version=“2.5”

xmlns=“http://java.sun.com/xml/ns/javaee”

xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

contextConfigLocation

/WEB-INF/spring/root-context.xml

spring.profiles.default

dev

org.springframework.web.context.ContextLoaderListener

appServlet

org.springframework.web.servlet.DispatcherServlet

spring.profiles.default

dev

1

appServlet

/

3.2 条件化的bean


Spring4实现了条件化配置,需要引入@Conditional(可以用到带有@bean注解的方法上)注解。如果给定条件为true,则创建这个bean,反之,不创建。

例如:

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Conditional;

import org.springframework.context.annotation.Configuration;

@Configuration

public class MagicConfig {

@Bean

@Conditional(MagicExistsCondition.class) // 条件化创建bean

public MagicBean magicBean() {

return new MagicBean();

}

}

@Conditional中给定了一个Class,它指明了条件——本例中是MagicExistsCondition。@Conditional将会通过Condition接口进行条件对比:

public interface Condition {

boolean matches(ConditionContext ctxt AnnotatedTypeMetadata metadata);

}

接下来是MagicExistsCondition的实现类:

import org.springframework.context.annotation.Condition;

import org.springframework.context.annotation.ConditionContext;

import org.springframework.core.env.Environment;

import org.springframework.core.type.AnnotatedTypeMetadata;

public class MagicExistsCondition implements Condition {

@Override

public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

Environment env = context.getEnvironment();

// 根据环境中是否存在magic属性来决策是否创建MagicBean

return env.containsProperty(“magic”);

}

}

ConditionContext是一个接口,大致如下所示:

public interface ConditionContext {

BeanDefinitionRegistry getRegistry();

ConfigurableListableBeanFactory getBeanFactory();

Enviro

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

nment getEnvironment();

ResourceLoader getResourceLoader();

ClassLoader getClassLoader();

}

ConditionContext实现的考量因素可能会更多,通过ConditionContext,我们可以做到如下几点:

  • 借助getRegistry() 返回的BeanDefinitionRegistry检查bean定义;

  • 借助getBeanFactory() 返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;

  • 借助getEnvironment() 返回的Environment检查环境变量是否存在以及它的值是什么;

  • 读取并探查getResourceLoader() 返回的ResourceLoader所加载的资源。

  • 借助getClassLoader() 返回的ClassLoader加载并检查类是否存在。

AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有什么其他的注解,它也是一个接口,如下所示:

public interface AnnotatedTypeMeta {

boolean isAnnotated(String annotationType);

Map<String, Object> getAnnotationAttributes(String annotationType);

Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);

MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);

MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString);

}

借助isAnnotated()方法,能够判断带有@Bean注解的方法是不是还有其他特定的注解。

3.3 处理自动装配的歧义性


当自动装配bean时,遇到多个实现类的情况下,就出现了歧义,例如:

@Autowired

public void setDessert(Dessert dessert) {

this.dessert = dessert;

}

Dessert是一个接口,并且有三个类实现了这个接口,如下所示:

@Component

public class Cake implements Dessert { … }

@Component

public class Cookies implements Dessert { … }

@Component

public class IceCream implements Dessert { … }

三个实现均使用了@Component,在组件扫描时,能够创建它们的bean。但Spring试图自动装配setDessert()中的Dessert参数是,它并没有唯一、无歧义的可选值,Spring无法做出选择,则会抛出NoUniqueBeanDefinitionException的异常。

两种解决办法:

第一种方法:标示首选的bean

如下所示:

@Component

@Primary

public class IceCream implements Dessert { … }

或者,如果通过JavaConfig配置,如下:

@Bean

@Primary

public Dessert iceCream() {

return new IceCream();

}

或者,使用XML配置bean的话,如下:

需要注意的是:不能标示两个或更多的首选bean,这样会引来新的歧义。

第二种方法:限定自动装配的bean

如下所示:

@Autowired

@Qualifier(“iceCream”)

public void setDessert(Dessert dessert) {

this.dessert = dessert;

}

如果不想用默认的bean的名称,也可以创建自定义的限定符

@Component

@Qualifier(“cold”)

public class IceCream implements Dessert { … }

@Autowired

@Qualifier(“cold”)

public void setDessert(Dessert dessert) {

this.dessert = dessert;

}

或者使用JavaConfig配置

@Bean

@Qualifier(“cold”)

public Dessert iceCream() {

return new IceCream();

}

如果出现多个Qualifier,尝试为bean也标示多个不同的Qualifier来表明要注入的bean。

@Component

@Qualifier(“cold”)

@Qualifier(“creamy”)

public class IceCream implements Dessert { … }

@Component

@Qualifier(“cold”)

@Qualifier(“fruity”)

public class Popsicle implements Dessert { … }

@Autowired

@Qualifier(“cold”)

@Qualifier(“creamy”)

public void setDessert(Dessert dessert) {

this.dessert = dessert;

}

但有个问题,Java不允许在同一个条目上重复出现相同类型的注解,编译器会提示错误

解决办法是我们可以自定义注解:

@Targe({ElementType.CONSTRUCTOR, ElementType.FIELD,

ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Qualifier

public @interface Cold { }

@Targe({ElementType.CONSTRUCTOR, ElementType.FIELD,

ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Qualifier

public @interface Creamy { }

@Targe({ElementType.CONSTRUCTOR, ElementType.FIELD,

ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Qualifier

public @interface Fruity { }

重新标注IceCream

@Component

@Cold

@Creamy

public class IceCream implements Dessert { … }

@Component

@Cold

@Fruity

public class Popsicle implements Dessert { … }

注入setDessert() 方法

@Autowired

@Cold

@Creamy

public void setDessert(Dessert dessert) {

this.dessert = dessert;

}

3.4 Bean的作用域


默认情况下,Spring应用上下文所有bean都是作为以单例的形式创建的

Spring定义了多种作用域,可以基于这些作用域创建bean,包括:

  • 单例(Singleton):在整个应用中,只创建bean的一个实例。

  • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。

  • 会话(Session):在Web应用中,为每个会话创建一个bean实例。

  • 请求(Request):在Web应用中,为每个请求创建一个bean实例。

例如,如果你使用组件扫描,可以在bean的类上使用@Scope注解,将其声明为原型bean:

@Component

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

public class Notepad { … }

或者在JavaConfig上声明:

@Bean

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

public Notepad notepad {

return new Notepad();

}

或者在XML上声明:

在web应用中,如果能够实例化在会话和请求范围内共享bean,那将很有价值。例如:电子商务的购物车,会话作用域最为适合。

@Component

@Scope(value=WebApplicationContext.SCOPE_SESSION,

proxyMode=ScopedProxyMode.TARGET_CLASS)

public class ShoppingCart { … }

注入一个服务类

@Component

public class StoreService {

@Autowired

public void setShoppingCart (ShoppingCart shoppingCart) {

this.shoppingCart = shoppingCart;

}

}

因为StoreService是一个单例的bean,会在Spring应用上下文加载的时候创建。当它创建的时候,Spring会试图将ShoppingCart bean注入到setShoppingCart() 方法中。但是ShoppingCart bean是会话作用域的,此时不存在。直到某个用户进入系统,创建了会话之后,才会出现ShoppingCart实例。

另外,系统中将会有多个ShoppingCart实例:每个用户一个。我们并不想让Spring注入某个固定的ShoppingCart实例到StoreService中。我们希望的是当StoreService处理购物车功能时,它所用的ShoppingCart实例恰好是当前会话所对应的那一个。

Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理,如下图。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。

如果ShoppingCart是接口而不是类的话,就用ScopedProxyMode.TARGET_INTERFACES(用JDK的代理)。如果是类而不是接口,就必须使用CGLib来生成基于类的代理,所以要用ScopedProxyMode.TARGET_CLASS。

请求的作用域原理与会话作用域原理一样。

作用域代理能够延迟注入请求和会话作用域的bean

也可用XML配置

<aop:scoped-proxy />

<aop:scoped-proxy />是与@Scope注解的proxy属性功能相同的SpringXML配置元素。它会告诉Spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理。我们也可以将proxy-targe-class属性设置为false,进而要求生成基于接口的代理:

<bean id=“cart”

class=“com.myapp.ShoppingCart”

scope=“session” >

<aop:scoped-proxy proxy-targe-class=“false”/>

《Spring实战》读书笔记-第3章 高级装配,zookeeper原理图相关推荐

  1. 《Spring实战》读书笔记-第3章 高级装配

    <Spring实战>是学习Spring框架的一本非常经典的书籍,之前阅读了这本书,只是在书本上写写画画,最近整理了一下<Spring实战>的读书笔记,通过博客的方式进行记录分享 ...

  2. 《Spring实战》读书笔记-第3章 高级装配,字节跳动四面技术面

    当自动装配bean时,遇到多个实现类的情况下,就出现了歧义,例如: @Autowired public void setDessert(Dessert dessert) { this.dessert ...

  3. 《Spring实战》读书笔记-第3章 高级装配,最新Java大厂高频面试题

    <jee:jndi-lookup id="dataSource" lazy-init="true" jndi-name="jdbc/myData ...

  4. spring boot 503_Spring实战读书笔记第4章 面向切面的Spring

    本章内容: 面向切面编程的基本原理 通过POJO创建切面 使用@AspectJ注解 为AspectJ切面注入依赖 在软件开发中,散布于应用中多的功能被称为横切关注点(cross-cutting con ...

  5. Spring实战读书笔记 高级装配(1)

    一.条件化装配 1. 当你希望你的bean在特殊条件下才能装配时,比如在声明了特定的bean时,或者配置了特定的环境变量的时候.那么就可以使用 @Conditional注解,可以用在 @Bean注解下 ...

  6. APUE读书笔记-第14章-高级I/O

    14.1 引言 *高级I/O包括非阻塞I/O.记录锁.系统V流机制.I/O多路转换(select和poll函数).readv和writev函数以及存储映射I/O(mmap) 14.2 非阻塞I/O * ...

  7. hive实战读书笔记(第9章)Hive性能优化

    hive用户面临的一个比较大的问题是,用户需要等待较长的响应时间,与传统关系数据库查询的性能相比,hive响应速度慢的令人发指 本章介绍一套诊断改进hive查询性能的系统方法,通过这个过程,将单个hi ...

  8. jQuery实战读书笔记(第一章至第四章)

    2019独角兽企业重金招聘Python工程师标准>>> 第一章 jQuery 基础 1. 包装器 jQuery对包装器(Wrapper)或包装集(wrapped set)进行操作,即 ...

  9. maven实战--读书笔记之第一章和第二章

    第一章:Maven简介 1.本书为国内社区公认的专家徐晓斌所写,本书基于maven3.0所编写,maven是非常优秀的建模工具,maven最大化的消除了构建的重复,抽象了构建生命,他还有一个优点,帮助 ...

最新文章

  1. java log4j mysql_java – 配置log4j属性文件以存储在mysql数据库中
  2. Windows7 VMware虚拟机安装Apple Mac OSX v10.7 Lion
  3. Java 编程的动态性 第1 部分: 类和类装入--转载
  4. POJ - 3565 Ants(二分图最小权匹配+KM+思维)
  5. linux 文件路径操作
  6. 方法区jdk1.7,1.8版本的构造变化
  7. WDK10+VS2015 驱动环境搭建
  8. 面向对象设计的设计原则
  9. Transactional replication-如何跳过一个事务
  10. 取最大值_举一反三17——线段平方和的最小值与最大值
  11. PHP7函数大全(4553个函数)
  12. 拍视频用30帧还是60帧更好,帧率如何设置60帧的问题
  13. html白色背景遮罩,CSS 给图片或背景图片加颜色遮罩
  14. 4412开发板项目实战-云服务器智能家居
  15. App Store审核规则简要汇总
  16. Gald to meet you
  17. ssh和scp的使用
  18. Android自定义View(七)_Canvas之图片文字
  19. 软件外包的五点管理技巧
  20. 关于基尔霍夫定律拓扑小知识

热门文章

  1. 单闭环直流调速系统仿真
  2. Cakewalk Z3TA+ 2 v2.2.3.51 win-mac 波塑形合成器加预设合集
  3. 本地服务器搭建青龙面板(PC端)
  4. python朴素贝叶斯分类示例_Python实现的朴素贝叶斯分类器示例
  5. 一个做耽美漫画的内容网站源码解析过程,讲解他的框架和功能实现
  6. PythonJS宏 实现保留样式合并表格后拆分
  7. WPF开发之Prism详解【内附源码】
  8. 拖放本地图片到网页中
  9. 基于房屋数据的房价相关性分析(含python代码)
  10. ae插件form_AE插件排行!!