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

本文详细解析了Java面试中的核心概念,包括ConditionContext接口的使用、AnnotatedTypeMetadata的检查方法,以及Spring框架中的自动装配策略、Bean作用域(单例、原型、会话、请求)和运行时参数注入技术。同时介绍了如何通过SpEL和属性占位符进行动态配置。
摘要由CSDN通过智能技术生成

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
}

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

public interface ConditionContext {

BeanDefinitionRegistry getRegistry();

ConfigurableListableBeanFactory getBeanFactory();

Environment 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。

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

img

作用域代理能够延迟注入请求和会话作用域的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”/>

3.5 运行时植注入


我们之前在javaConfig配置中,配置了BlankDisc:

@Bean

public CompactDisc sgtPeppers() {

return new BlankDisc (

“Sgt. Pepper’s Lonely Hearts Club Band”,

“The Beatles”

);

}

这种硬编码实现了要求,但有时我们希望避免,而是想让这些值在运行时再确定。为了实现这些功能,Spring提供了两种在运行时求值的方式:

  • 属性占位符 (Property placeholder)。

  • Spring表达式语言(SpEL)。

在Spring中,最简单的方式就是声明属性源并通过Spring的Environment来检索属性。

package com.springinaction;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.PropertySource;

import org.springframework.core.env.Environment;

@Configuration

@ComponentScan(“com.springinaction”)

@PropertySource(“app.properties”)

public class AppConfig {

@Autowired

Environment environment;

@Bean

public BlankDisc disc(){

return new BlankDisc(

environment.getProperty(“disc.title”),

environment.getProperty(“disc.artist”));

}

}

在本例中,@PropertySource引用了类路径中一个名为app.properties的文件,如下所示:

disc.title=Sgt. Peppers Lonely Hearts Club Band

disc.artist=The Beatles

Environment中getProperty有四个重载方法:

String getProperty(String key);

String getProperty(String key, String defaultValue);

T getProperty(String key, Class type);

T getProperty(String key, Class type, T defaultValue);

第二个方法与第一个的差别就是有了默认值。

第三、四个方法不会将所有值视为String,可以转换为别的类型,如

int connectionCount = env.getProperty(“db.connection.count”, Integer.class, 30);

其他方法还有

// 如果key没有,则抛出IllegalStateException异常

String getRequiredProperty(String key);

// 检查key的value是否存在

boolean containsProperty(String key)

// 将属性解析为类

Class getPropertyAsClass(String key, Class type);

// 返回激活profile名称的数组

String[] getActiveProfiles();

// 返回默认profile名称的数组

String[] getDefaultProfiles()

// 如果environment支持给定profile的话,就返回true

boolean acceptsProfiles(String… profiles)

我们还可以用属性占位符来注入,占位符的形式为使用“${ … }”包装的属性名称。

<bean id=“sgtPeppers” class=“soundsystem.BlankDisc”

c:_title=“${disc.title}”

c:_artist=“${disc.artist}” />

如果我们依赖组件扫描和自动装配来创建初始化的话

public BlankDisc (

@Value(“disc.title”) String title,

@Value(“disc.artist”) String artist) {

this.title = title;

this.artist = artist;

}

为了使用占位符,我们必须要配置一个PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean。推荐后者。

如果在javaConfig配置文件中声明:

@Bean

public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){

return new PropertySourcesPlaceholderConfigurer();

}

如果在XML配置文件中声明:

<context: property-placeholder />

下面我们来看Spring表达式语言进行装配

SpEl表达式会在运行时计算得到值。SpEl拥有很多特性,包括:

  • 使用bean的ID来引用bean;

  • 调用方法和访问对象的属性;

  • 对值进行算术、关系和逻辑运算;

  • 正则表达式匹配;

  • 集合操作。

常用用法:

  1. SpEL表达式要放到“# { … }”, 如: #{1}

  2. ‘# {T(System).currentTimeMillis()}’ ,它的最终结果是计算表达式的那一刻当前时间的毫秒数。T () 表达式会将java.lang.System视为Java中对应的类型,因此可以调用其static修饰的currentTimeMillis()方法。

  3. SpEL表达式可以引用其他的bean或其他bean的属性。

例如,引用sgtPeppers的bean

‘# { sgtPeppers }’

例如,如下的表达式会计算得到ID为sgtPeppers的bean的artist属性:

‘# { sgtPeppers.artist }’

  1. 还可以通过systemProperties对象引用系统属性:

‘# { systemProperties[‘disc.title’] }’

  1. 表示字面值:

‘# { 3.1415926 } ’

‘# { 9.87E4 } ’

‘# { ‘Hello’ } ’

‘# { false }’

  1. 引用其他的bean的方法

‘# { artistSelector.selectArtist () }’

为了防止方法值为null,抛出异常,可以使用“?.”

‘# { artistSelector.selectArtist ()?.toUpperCase() }’

不是null,正常返回;如果是null,不执行后面的方法,直接返回null

  1. 如果要在SpEL中访问类作用域的方法和常量的话,要依赖T() 这个关键的运算符。

‘# { T(java.lang.Math).PI }’

‘# { T(java.lang.Math).random() }’

  1. 还可以将运算符用在表达式上,如:

‘# { 2 * T(java.lang.Math).PI * circle.radius }’

‘# { disc.title + ’ by ’ + disc.artist }’

  1. 比较数字相等的写法

‘# { counter.total == 100 }’

‘# { counter.total eq 100 }’

  1. 三元运算符

‘# { scoreboard.score > 1000 ? “Winner!” : “Loser” }’

‘# { disc.title ?: ‘Rattle and Hum’ } ’ // 如果disc.title的值为空,返回’Rattle and Hum’

  1. 支持正则表达式

‘# { admin.email matches ‘[a-zA-Z0-9.%±]+@[a-zA-Z0-9.]+.com’ }’

  1. 支持与集合和数组相关的表达式

‘# { jukebox.songs[4].title }’

‘# { jukebox.songs[T(java.lang.Math).random() * jukebox.songs.size()].title }’

‘# { ‘This is a test’ [3] }’ // 引用第4个字符 - “s”

  1. 支持查询运算符

例如你希望得到jukebox中artist属性为Aerosmith的所有歌曲:

‘# { jukebox.songs.?[artist eq ‘Aerosmith’] }’

查找列表中第一个artist属性为Aerosmith的歌曲:

‘# { jukebox.songs.1 }’

查找列表中最后一个artist属性为Aerosmith的歌曲:

‘# { jukebox.songs.$[artist eq ‘Aerosmith’] }’

  1. 支持投影运算符

假设我们不想要歌曲对象的集合,而是所有歌曲名称的集合。如下表达式会将title属性投影到一个新的String类型的集合中:

‘# { jukebox.songs.![title]}’

获取Aerosmith所有歌曲的title

‘# { jukebox.songs.?[artist eq ‘Aerosmith’].![title] }’

3.6 小结

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

最新整理面试题
在这里插入图片描述

上述的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题

最新整理电子书

在这里插入图片描述

最新整理大厂面试文档

在这里插入图片描述

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
le }’

‘# { ‘This is a test’ [3] }’ // 引用第4个字符 - “s”

  1. 支持查询运算符

例如你希望得到jukebox中artist属性为Aerosmith的所有歌曲:

‘# { jukebox.songs.?[artist eq ‘Aerosmith’] }’

查找列表中第一个artist属性为Aerosmith的歌曲:

‘# { jukebox.songs.2 }’

查找列表中最后一个artist属性为Aerosmith的歌曲:

‘# { jukebox.songs.$[artist eq ‘Aerosmith’] }’

  1. 支持投影运算符

假设我们不想要歌曲对象的集合,而是所有歌曲名称的集合。如下表达式会将title属性投影到一个新的String类型的集合中:

‘# { jukebox.songs.![title]}’

获取Aerosmith所有歌曲的title

‘# { jukebox.songs.?[artist eq ‘Aerosmith’].![title] }’

3.6 小结

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

最新整理面试题
[外链图片转存中…(img-8mYvRnzS-1714774465861)]

上述的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题

最新整理电子书

[外链图片转存中…(img-qaBi5Jny-1714774465862)]

最新整理大厂面试文档

[外链图片转存中…(img-sioxSmFH-1714774465862)]

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!


  1. artist eq ‘Aerosmith’ ↩︎

  2. artist eq ‘Aerosmith’ ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值