Spring学习笔记
2023-01-10 14:40:11 20 举报
Spring学习笔记
作者其他创作
大纲/内容
第一部分Spring核心
装配bean
Spring配置的可选方案
XML显示配置
具体怎么配置省略
导入和混合配置
在JavaConfig中引用XML配置
使用@ImportResource注解
javaconfig两个配置类组合
方法1 在AConfig中用@Import注解导入BConfig
方法2创建一个更高级别的CConfig,在CConfig这个类中使用@Import
将两个配置类组合在一起
@Import({AConfig.class,BConfig.class})
将两个配置类组合在一起
@Import({AConfig.class,BConfig.class})
在XML配置中引用JavaConfig
可以用<import>元素将Java配置(Java配置作为<bean>)导入到XML配置
在Java中显示配置
步骤一创建配置类
创建JavaConfig类的关键在于为其添加@Configuration注
解,@Configuration注解表明这个类是一个配置类,该类应该包含在
Spring应用上下文中如何创建bean的细节。
解,@Configuration注解表明这个类是一个配置类,该类应该包含在
Spring应用上下文中如何创建bean的细节。
步骤二声明简单的bean
要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建
所需类型的实例,然后给这个方法添加@Bean注解。
@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为
Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻
辑。
默认情况下,bean的ID与带有@Bean注解的方法名是一样的。
那么可以重命名该方法,也可以通过name属性指定一个不同的
名字:
所需类型的实例,然后给这个方法添加@Bean注解。
@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为
Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻
辑。
默认情况下,bean的ID与带有@Bean注解的方法名是一样的。
那么可以重命名该方法,也可以通过name属性指定一个不同的
名字:
步骤三借助Java Config实现注入
引用创建bean的方法
引用创建bean的方法
cdPlayer()方法像sgtPeppers()方法一样,同样使用了@Bean注解,这表
明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。所
创建的bean ID为cdPlayer,与方法的名字相同。
明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。所
创建的bean ID为cdPlayer,与方法的名字相同。
通过调用方法来引用bean的方式有点令人困惑。其实还有一
种理解起来更为简单的方式:
种理解起来更为简单的方式:
如果你想通过
Setter方法注入CompactDisc的话,那么代码看起来应该是这样的:
Setter方法注入CompactDisc的话,那么代码看起来应该是这样的:
隐式的bean发现机智和自动装配
组件扫描(component scanning):
Spring会自动发现应用上下文中
所创建的bean。
Spring会自动发现应用上下文中
所创建的bean。
@ComponentScan注解启用了组件扫描
创建XXconfig类并且使用注解@ComponentScan
和注解@Configration
创建XXconfig类并且使用注解@ComponentScan
和注解@Configration
如果不为@ComponentScan设置任何属性。
按照默认规则,它会以配置类所在的包作为基础包(base package)来
扫描组件
按照默认规则,它会以配置类所在的包作为基础包(base package)来
扫描组件
@ComponentScan的value属性中指明包的名称
在@ComponentScan的value属性中指明包的名称
@ComponentScan还提供了另外一
种方法,那就是将其指定为包中所包含的类或接口
种方法,那就是将其指定为包中所包含的类或接口
@Component注解。
这个简单的注解表明该
类会作为组件类,并告知Spring要为这个类创建bean。
有这个注解的类可以被Spring发现
这个简单的注解表明该
类会作为组件类,并告知Spring要为这个类创建bean。
有这个注解的类可以被Spring发现
为组件扫描的bean命名
如果想为这个bean设置不同的ID,你所要做的就是将期望的ID作为值传
递给@Component注解。@Component("car")
如果想为这个bean设置不同的ID,你所要做的就是将期望的ID作为值传
递给@Component注解。@Component("car")
还有另外一种为bean命名的方式,使用Java依赖注入规范
中所提供的@Named注解来为bean设置ID@Named("car")
中所提供的@Named注解来为bean设置ID@Named("car")
自动装配(autowiring):
Spring自动满足bean之间的依赖。
Spring自动满足bean之间的依赖。
@Autowired
@Autowired注解可以用在类的
任何方法上
包括构造器和属性的Setter方法
任何方法上
包括构造器和属性的Setter方法
假如有且只有一个bean匹配依赖需求的话,那么这
个bean将会被装配进来。
如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一
个异常。为了避免异常的出现,你可以将@Autowired的required属性设
置为false:
将required属性设置为false时,Spring会尝试执行自动装配,但是如果
没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。但
是,把required属性设置为false时,你需要谨慎对待。如果在你的代
码中没有进行null检查的话,这个处于未装配状态的属性有可能会出现
NullPointerException。
个bean将会被装配进来。
如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一
个异常。为了避免异常的出现,你可以将@Autowired的required属性设
置为false:
将required属性设置为false时,Spring会尝试执行自动装配,但是如果
没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。但
是,把required属性设置为false时,你需要谨慎对待。如果在你的代
码中没有进行null检查的话,这个处于未装配状态的属性有可能会出现
NullPointerException。
@Autowired是Spring特有的注解。如果你不愿意在代码中到处使用Spring
的特定注解来完成自动装配任务的话,那么你可以考虑将其替换
为@Inject:
@Inject注解来源于Java依赖注入规范,该规范同时还为我们定义了
@Named注解。在自动装配中,Spring同时支持@Inject和@Autowired。尽
管@Inject和@Autowired之间有着一些细微的差别,但是在大多数场景
下,它们都是可以互相替换的。
的特定注解来完成自动装配任务的话,那么你可以考虑将其替换
为@Inject:
@Inject注解来源于Java依赖注入规范,该规范同时还为我们定义了
@Named注解。在自动装配中,Spring同时支持@Inject和@Autowired。尽
管@Inject和@Autowired之间有着一些细微的差别,但是在大多数场景
下,它们都是可以互相替换的。
@Resource
@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按byName自动注入罢了。@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字。
二者具体区别:
1、@Autowired与@Resource都可以用来装配bean.都可以写在字段上,或写在setter方法上。
2、@Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false),如果我们想使用名称装配可以结合@Qualifier注解进行使用。
3、@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
1、@Autowired与@Resource都可以用来装配bean.都可以写在字段上,或写在setter方法上。
2、@Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false),如果我们想使用名称装配可以结合@Qualifier注解进行使用。
3、@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
推荐排名:自动化配置>java配置>XML配置
高级装配
Spring profile
(Spring4.0以前)
(Spring4.0以前)
profile bean 功能可以使同一个部署单元能够适应于所有的环境,没有必要进行重新构建
在配置类中使用@Profile注解指定某个bean属于哪一个profile。
在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。
当对应的profile没有激活时,带有@bean注解的方法会被忽略
在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。
当对应的profile没有激活时,带有@bean注解的方法会被忽略
从Spring3.2开始,可以在方法级别上使用@Profile注解,与@Bean注解一同使
用,能将这两个bean的声明放到同一个配置类
用,能将这两个bean的声明放到同一个配置类
激活profile:
步骤一:需要依赖两个独立的属
性:spring.profiles.active和spring.profiles.default
性: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
spring.profiles.active属性的话,那么它的值就会用来确定哪个
profile是激活的。但如果没有设置spring.profiles.active属性的话,
那Spring将会查找spring.profiles.default的值。如果
spring.profiles.active和spring.profiles.default均没有设置的话,
那就没有激活的profile,因此只会创建那些没有定义在profile中的
bean
当应用程序部署到QA、生产或其他环境之中时,负责部署的人根据情
况使用系统属性、环境变量或JNDI设置spring.profiles.active即可。
当设置spring.profiles.active以后,至于spring.profiles.default置
成什么值就已经无所谓了;系统会优先使用spring.profiles.active中
所设置的profile。
况使用系统属性、环境变量或JNDI设置spring.profiles.active即可。
当设置spring.profiles.active以后,至于spring.profiles.default置
成什么值就已经无所谓了;系统会优先使用spring.profiles.active中
所设置的profile。
你可能已经注意到了,在spring.profiles.active和
spring.profiles.default中,profile使用的都是复数形式。这意味着你
可以同时激活多个profile,这可以通过列出多个profile名称,并以逗号
分隔来实现。当然,同时启用dev和prod profile可能也没有太大的意
义,不过你可以同时设置多个彼此不相关的profile。
spring.profiles.default中,profile使用的都是复数形式。这意味着你
可以同时激活多个profile,这可以通过列出多个profile名称,并以逗号
分隔来实现。当然,同时启用dev和prod profile可能也没有太大的意
义,不过你可以同时设置多个彼此不相关的profile。
步骤二:怎么设置这两个属性
作为DispatcherServlet的初始化参数;
作为Web应用的上下文参数;
作为JNDI条目;
作为环境变量;
作为JVM的系统属性;
在集成测试类上,使用@ActiveProfiles注解设置。
作为Web应用的上下文参数;
作为JNDI条目;
作为环境变量;
作为JVM的系统属性;
在集成测试类上,使用@ActiveProfiles注解设置。
Spring提供了@ActiveProfiles注解,我们可以使用它来指定运行测试时
要激活哪个profile。在集成测试时,通常想要激活的是开发环境的
profile。例如,下面的测试类片段展现了使用@ActiveProfiles激活dev
profile:
要激活哪个profile。在集成测试时,通常想要激活的是开发环境的
profile。例如,下面的测试类片段展现了使用@ActiveProfiles激活dev
profile:
条件化的bean声明
(Springt 4开始)
(Springt 4开始)
使用@Conditional注解条件化地配置
设置给@Conditional的类可以是任意实现了Condition接口的类型。可以
看出来,这个接口实现起来很简单直接,只需提供matches()方法的实
现即可。如果matches()方法返回true,那么就会创建带有@Conditional
注解的bean。如果matches()方法返回false,将不会创建这些bean。
看出来,这个接口实现起来很简单直接,只需提供matches()方法的实
现即可。如果matches()方法返回true,那么就会创建带有@Conditional
注解的bean。如果matches()方法返回false,将不会创建这些bean。
举例:
在上面的程序清单中,matches()方法很简单但功能强大。它通过给定
的ConditionContext对象进而得到Environment对象,并使用这个对象检
查环境中是否存在名为magic的环境属性。在本例中,属性的值是什么
无所谓,只要属性存在即可满足要求。如果满足这个条件的
话,matches()方法就会返回true。所带来的结果就是条件能够得到满
足,所有@Conditional注解上引用MagicExistsCondition的bean都会被
创建。
话说回来,如果这个属性不存在的话,就无法满足条件,matches()方
法会返回false,这些bean都不会被创建。
的ConditionContext对象进而得到Environment对象,并使用这个对象检
查环境中是否存在名为magic的环境属性。在本例中,属性的值是什么
无所谓,只要属性存在即可满足要求。如果满足这个条件的
话,matches()方法就会返回true。所带来的结果就是条件能够得到满
足,所有@Conditional注解上引用MagicExistsCondition的bean都会被
创建。
话说回来,如果这个属性不存在的话,就无法满足条件,matches()方
法会返回false,这些bean都不会被创建。
ConditionContext
MagicExistsCondition中只是使用了ConditionContext得到的
Environment,但Condition实现的考量因素可能会比这更多。matches()
方法会得到ConditionContext和AnnotatedTypeMetadata对象用来做出决
策。ConditionContext是一个接口,大致如下所示:
Environment,但Condition实现的考量因素可能会比这更多。matches()
方法会得到ConditionContext和AnnotatedTypeMetadata对象用来做出决
策。ConditionContext是一个接口,大致如下所示:
借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
借助getBeanFactory()返回的ConfigurableListableBeanFactory检
查bean是否存在,甚至探查bean的属性;
借助getEnvironment()返回的Environment检查环境变量是否存在以
及它的值是什么;
读取并探查getResourceLoader()返回的ResourceLoader所加载的资
源;
借助getClassLoader()返回的ClassLoader加载并检查类是否存在。
借助getBeanFactory()返回的ConfigurableListableBeanFactory检
查bean是否存在,甚至探查bean的属性;
借助getEnvironment()返回的Environment检查环境变量是否存在以
及它的值是什么;
读取并探查getResourceLoader()返回的ResourceLoader所加载的资
源;
借助getClassLoader()返回的ClassLoader加载并检查类是否存在。
AnnotatedTypeMetadata
AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有
什么其他的注解。像ConditionContext一样,AnnotatedTypeMetadata也
是一个接口。它如下所示:
什么其他的注解。像ConditionContext一样,AnnotatedTypeMetadata也
是一个接口。它如下所示:
借助isAnnotated()方法,我们能够判断带有@Bean注解的方法是不是还
有其他特定的注解。借助其他的那些方法,我们能够检查@Bean注解的
方法上其他注解的属性。
有其他特定的注解。借助其他的那些方法,我们能够检查@Bean注解的
方法上其他注解的属性。
Spring4中@Profilede注解重构
其基于@Conditional和Condition实现
其基于@Conditional和Condition实现
@Profile注解如下所示:
ProfileCondition检查某个bean profile是否可用
我们可以看到,ProfileCondition通过AnnotatedTypeMetadata得到了用
于@Profile注解的所有属性。借助该信息,它会明确地检查value属
性,该属性包含了bean的profile名称。然后,它根据通过
ConditionContext得到的Environment来检查[借助acceptsProfiles()方
法]该profile是否处于激活状态。
我们可以看到,ProfileCondition通过AnnotatedTypeMetadata得到了用
于@Profile注解的所有属性。借助该信息,它会明确地检查value属
性,该属性包含了bean的profile名称。然后,它根据通过
ConditionContext得到的Environment来检查[借助acceptsProfiles()方
法]该profile是否处于激活状态。
自动装配与歧义性
当自动装配出现歧义,有多个bean符合条件时,
Spring将会抛出NoUniqueBeanDefinitionException
Spring将会抛出NoUniqueBeanDefinitionException
解决方式:
方式一:使用@Primary标识首选的bean
1.@Primary能够与@Component组合用在组件扫描的bean
上,在编写类时直接加上@Primary
上,在编写类时直接加上@Primary
2.通过Java配置显示声明
在XXConfig文件中,与@Bean注解一同使用
在XXConfig文件中,与@Bean注解一同使用
方式二:使用@Qualifier注解限定自动装配的ban
1.基于默认的bean ID作为限定符
为@Qualifier注解所设置的参数就是
想要注入的bean的ID()
可以与@Autowired和@Inject协同使用
为@Qualifier注解所设置的参数就是
想要注入的bean的ID()
可以与@Autowired和@Inject协同使用
2.创建自定义的限定符
1.@Qualifier与@Component组合使用,给类设置自己的限定符
2.当通过Java显示定义bean时,@Qualifier可以与@Bean注解一起使用
3.使用自定义的限定符注解:
创建一个注解,使用@Qualifier注解来标注。
这样我们就拥有了一个新的@Cold注解,它具有
@Qualifier注解的特性,它本身就成为了限定符注解
这样我们就拥有了一个新的@Cold注解,它具有
@Qualifier注解的特性,它本身就成为了限定符注解
Java不允许在同一个条目上重复出现相同类型的多个注解
但如果声明自定义限定符注解,我们同时可以使用多个限定符
但如果声明自定义限定符注解,我们同时可以使用多个限定符
与此同时,相对于使用原始的@Qualifier
并借助String类型来指定限定符,自定义的注解也更为类型安全。
并借助String类型来指定限定符,自定义的注解也更为类型安全。
bean的作用域
作用域类型
1.单例(Singleton):在整个应用中,只创建bean的一个实例。
2.原型(Prototype):每次注入或者通过Spring应用上下文获取的时
候,都会创建一个新的bean实例。
候,都会创建一个新的bean实例。
3.会话(Session):在Web应用中,为每个会话创建一个bean实例。
4.请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
改变作用域
单例Singleton->原型Prototype
1.使用@Scope注解,它可以与@Component
或@Bean一起使用。
这里,使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了
原型作用域。你当然也可以使用@Scope("prototype"),但是使
用SCOPE_PROTOTYPE常量更加安全并且不易出错。
或@Bean一起使用。
这里,使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了
原型作用域。你当然也可以使用@Scope("prototype"),但是使
用SCOPE_PROTOTYPE常量更加安全并且不易出错。
2.想在Java配置中将Notepad声明为原型bean,那么可以组合使
用@Scope和@Bean来指定所需的作用域
用@Scope和@Bean来指定所需的作用域
使用会话和请求作用域
指定会话作用域,我们可以使用@Scope注解
它的使用方式与指定原型作用域是相同
它的使用方式与指定原型作用域是相同
这里,我们将value设置成了WebApplicationContext中的SCOPE_SESSION
常量(它的值是session)。这会告诉Spring为Web应用中的每个会话创
建一个ShoppingCart。这会创建多个ShoppingCart bean的实例,但是对
于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean
实际上相当于单例的。
常量(它的值是session)。这会告诉Spring为Web应用中的每个会话创
建一个ShoppingCart。这会创建多个ShoppingCart bean的实例,但是对
于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean
实际上相当于单例的。
proxyMode属性
@Scope同时还有一个proxyMode属性,它被设置成了
ScopedProxyMode.INTERFACES。这表
明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。
@Scope同时还有一个proxyMode属性,它被设置成了
ScopedProxyMode.INTERFACES。这表
明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。
将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,
以此来表明要以生成目标类扩展的方式创建代理。
以此来表明要以生成目标类扩展的方式创建代理。
请求作用域的bean应该也以作用域代理的方式进行注入
注入外部的值
Spring表达式语言
(Spring Expression
Language,SpEL)
(Spring Expression
Language,SpEL)
SpEL拥有很多特性,包括:
1.使用bean的ID来引用bean;
2.调用方法和访问对象的属性;
3.对值进行算术、关系和逻辑运算;
4.正则表达式匹配;
5.集合操作。
1.使用bean的ID来引用bean;
2.调用方法和访问对象的属性;
3.对值进行算术、关系和逻辑运算;
4.正则表达式匹配;
5.集合操作。
SpEL基础样例
需要了解的第一件事情就是SpEL表达式要放到“#{ ... }”之中,这与属
性占位符有些类似,属性占位符需要放到“${ ... }”之中。
性占位符有些类似,属性占位符需要放到“${ ... }”之中。
它的最终结果是计算表达式的那一刻当前时间的毫秒数。T()表达式会
将java.lang.System视为Java中对应的类型,因此可以调用其static修
饰的currentTimeMillis()方法。
将java.lang.System视为Java中对应的类型,因此可以调用其static修
饰的currentTimeMillis()方法。
SpEL表达式也可以引用其他的bean或其他bean的属性。例如,如下的表
达式会计算得到ID为sgtPeppers的bean的artist属性:
达式会计算得到ID为sgtPeppers的bean的artist属性:
我们还可以通过systemProperties对象引用系统属性:
在bean装配时使用SpEL表达式
如果通过组件扫描创建bean的话,在注入属性和构造器参数时,我们可
以使用@Value注解,这与之前看到的属性占位符非常类似。不过,在这
里我们所使用的不是占位符表达式,而是SpEL表达式。例如,下面的
样例展现了BlankDisc,它会从系统属性中获取专辑名称和艺术家的名
字:
以使用@Value注解,这与之前看到的属性占位符非常类似。不过,在这
里我们所使用的不是占位符表达式,而是SpEL表达式。例如,下面的
样例展现了BlankDisc,它会从系统属性中获取专辑名称和艺术家的名
字:
SpEL所支持的基础表达式
表示字面值
SpEL表达式样例所表示的就是浮点值 #{3.14159}
数值还可以使用科学记数法的方式进行表示。如下面的表达式计算得到
的值就是98,700:#{9.87E4}
的值就是98,700:#{9.87E4}
SpEL表达式也可以用来计算String类型的字面值,如 #{'hello'}
最后,字面值true和false的计算结果就是它们对应的Boolean类型的
值。例如:#{'false'}
值。例如:#{'false'}
引用bean、属性和方法
通过ID引用其他的bean。例
如,你可以使用SpEL将一个bean装配到另外一个bean的属性中,此时要
使用bean ID作为SpEL表达式(在本例中,也就是sgtPeppers)
#{sgtPeppers}
如,你可以使用SpEL将一个bean装配到另外一个bean的属性中,此时要
使用bean ID作为SpEL表达式(在本例中,也就是sgtPeppers)
#{sgtPeppers}
现在,假设我们想在一个表达式中引用sgtPeppers的artist属性:
#{sgtPeppers.artist}
表达式主体的第一部分引用了一个ID为sgtPeppers的bean,分割符之后
是对artist属性的引用。
#{sgtPeppers.artist}
表达式主体的第一部分引用了一个ID为sgtPeppers的bean,分割符之后
是对artist属性的引用。
除了引用bean的属性,我们还可以调用bean上的方法。例如,假设有另
外一个bean,它的ID为artistSelector,我们可以在SpEL表达式中按照
如下的方式来调用bean的selectArtist()方法:
#{sgtPeppers.selectArtist()}
外一个bean,它的ID为artistSelector,我们可以在SpEL表达式中按照
如下的方式来调用bean的selectArtist()方法:
#{sgtPeppers.selectArtist()}
对于被调用方法的返回值来说,我们同样可以调用它的方法。例如,如
果selectArtist()方法返回的是一个String,那么可以调
用toUpperCase()将整个艺术家的名字改为大写字母形式:
#{sgtPeppers.selectArtist().tuUpperCase()}
果selectArtist()方法返回的是一个String,那么可以调
用toUpperCase()将整个艺术家的名字改为大写字母形式:
#{sgtPeppers.selectArtist().tuUpperCase()}
如果selectArtist()的返回值不是null的话,这没有什么问题。为了避
免出现NullPointerException,我们可以使用类型安全的运算符:
#{sgtPeppers.selectArtist()?.tuUpperCase()}
免出现NullPointerException,我们可以使用类型安全的运算符:
#{sgtPeppers.selectArtist()?.tuUpperCase()}
与之前只是使用点号(.)来访问toUpperCase()方法不同,现在我们使
用了“?.”运算符。这个运算符能够在访问它右边的内容之前,确保它所
对应的元素不是null。所以,如果selectArtist()的返回值是null的
话,那么SpEL将不会调用toUpperCase()方法。表达式的返回值会是
null。
用了“?.”运算符。这个运算符能够在访问它右边的内容之前,确保它所
对应的元素不是null。所以,如果selectArtist()的返回值是null的
话,那么SpEL将不会调用toUpperCase()方法。表达式的返回值会是
null。
在表达式中使用类型
如果要在SpEL中访问类作用域的方法和常量的话,要依赖T()这个关键
的运算符。例如,为了在SpEL中表达Java的Math类,需要按照如下的方
式使用T()运算符:T(java.lang.Math)
的运算符。例如,为了在SpEL中表达Java的Math类,需要按照如下的方
式使用T()运算符:T(java.lang.Math)
这里所示的T()运算符的结果会是一个Class对象,代表了
java.lang.Math。如果需要的话,我们甚至可以将其装配到一个Class类
型的bean属性中。但是T()运算符的真正价值在于它能够访问目标类型
的静态方法和常量。
java.lang.Math。如果需要的话,我们甚至可以将其装配到一个Class类
型的bean属性中。但是T()运算符的真正价值在于它能够访问目标类型
的静态方法和常量。
假如你需要将PI值装配到bean属性中。如下的SpEL就能完成该任
务:T(java.lang.Math).PI
务:T(java.lang.Math).PI
与之类似,我们可以调用T()运算符所得到类型的静态方法。我们已经
看到了通过T()调用System.currentTimeMillis()。如下的这个样例会计
算得到一个0到1之间的随机数:T(java.lang.Math).random()
看到了通过T()调用System.currentTimeMillis()。如下的这个样例会计
算得到一个0到1之间的随机数:T(java.lang.Math).random()
SpEL运算符
SpEL还提供了三元运算符(ternary),它与Java中的三元运算符非常类
似。例如,如下的表达式会判断如果scoreboard.score>1000的话,计算
结果为String类型的“Winner!”,否则的话,结果为Loser:
似。例如,如下的表达式会判断如果scoreboard.score>1000的话,计算
结果为String类型的“Winner!”,否则的话,结果为Loser:
三元运算符的一个常见场景就是检查null值,并用一个默认值来替代
null。例如,如下的表达式会判断disc.title的值是不是null,如果
是null的话,那么表达式的计算结果就会是“Rattle and Hum”:
null。例如,如下的表达式会判断disc.title的值是不是null,如果
是null的话,那么表达式的计算结果就会是“Rattle and Hum”:
计算正则表达式
当处理文本时,有时检查文本是否匹配某种模式是非常有用的。SpEL
通过matches运算符支持表达式中的模式匹配。matches运算符对String类
型的文本(作为左边参数)应用正则表达式(作为右边参
数)。matches的运算结果会返回一个Boolean类型的值:如果与正则表
达式相匹配,则返回true;否则返回false。
当处理文本时,有时检查文本是否匹配某种模式是非常有用的。SpEL
通过matches运算符支持表达式中的模式匹配。matches运算符对String类
型的文本(作为左边参数)应用正则表达式(作为右边参
数)。matches的运算结果会返回一个Boolean类型的值:如果与正则表
达式相匹配,则返回true;否则返回false。
为了进一步解释matches运算符,假设我们想判断一个字符串是否包含
有效的邮件地址。在这个场景下,我们可以使用matches运算符,如下所示
有效的邮件地址。在这个场景下,我们可以使用matches运算符,如下所示
计算集合
最简单的事情可能就是引用列表中的一个元素了
“[]”运算符用来从集合或数组中按照索引获取元素,实际上,它还可以
从String中获取一个字符。比如:
这个表达式引用了String中的第四个(基于零开始)字符,也就是“s”。
从String中获取一个字符。比如:
这个表达式引用了String中的第四个(基于零开始)字符,也就是“s”。
SpEL还提供了查询运算符(.?[]),它会用来对集合进行过滤,得到集
合的一个子集。作为阐述的样例,假设你希望得到jukebox中artist属性
为Aerosmith的所有歌曲。如下的表达式就使用查询运算符得到了
Aerosmith的所有歌曲:
合的一个子集。作为阐述的样例,假设你希望得到jukebox中artist属性
为Aerosmith的所有歌曲。如下的表达式就使用查询运算符得到了
Aerosmith的所有歌曲:
SpEL还提供了另外两个查询运算符:“.^[]”和“.$[]”,它们分别用来在
集合中查询第一个匹配项和最后一个匹配项。例如,考虑下面的表达
式,它会查找列表中第一个artist属性为Aerosmith的歌曲:
集合中查询第一个匹配项和最后一个匹配项。例如,考虑下面的表达
式,它会查找列表中第一个artist属性为Aerosmith的歌曲:
最后,SpEL还提供了投影运算符(.![]),它会从集合的每个成员中选
择特定的属性放到另外一个集合中。作为样例,假设我们不想要歌曲对
象的集合,而是所有歌曲名称的集合。如下的表达式会将title属性投
影到一个新的String类型的集合中:
择特定的属性放到另外一个集合中。作为样例,假设我们不想要歌曲对
象的集合,而是所有歌曲名称的集合。如下的表达式会将title属性投
影到一个新的String类型的集合中:
实际上,投影操作可以与其他任意的SpEL运算符一起使用。比如,我
们可以使用如下的表达式获得Aerosmith所有歌曲的名称列表:
们可以使用如下的表达式获得Aerosmith所有歌曲的名称列表:
使用@PropertySource注解和Environment
声明属性源并通过Spring的Environment来检索属性
在本例中,@PropertySource引用了类路径中一个名为app.properties的文
件。它大致会如下所示:
件。它大致会如下所示:
getProperty()方法有四个重载的变种形式
面向切面的Spring
面向切面编程的基本原理
什么是面向切面编程
如果要重用通用功能的话,最常见的面向对象技术是继承
(inheritance)或委托(delegation)。但是,如果在整个应用中都使用
相同的基类,继承往往会导致一个脆弱的对象体系;而使用委托可能需
要对委托对象进行复杂的调用。
切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清
晰简洁。在使用面向切面编程时,我们仍然在一个地方定义通用功能,
但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无
需修改受影响的类。横切关注点可以被模块化为特殊的类,这些类被称
为切面(aspect)。这样做有两个好处:首先,现在每个关注点都集中
于一个地方,而不是分散到多处代码中;其次,服务模块更简洁,因为
它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被
转移到切面中了。
(inheritance)或委托(delegation)。但是,如果在整个应用中都使用
相同的基类,继承往往会导致一个脆弱的对象体系;而使用委托可能需
要对委托对象进行复杂的调用。
切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清
晰简洁。在使用面向切面编程时,我们仍然在一个地方定义通用功能,
但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无
需修改受影响的类。横切关注点可以被模块化为特殊的类,这些类被称
为切面(aspect)。这样做有两个好处:首先,现在每个关注点都集中
于一个地方,而不是分散到多处代码中;其次,服务模块更简洁,因为
它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被
转移到切面中了。
定义AOP术语
通知(advice)
切面也有目标——它必须要完成的工作。在AOP术语中,切面
的工作被称为通知。
的工作被称为通知。
Spring切面可以应用5种类型的通知:
前置通知(Before):在目标方法被调用之前调用通知功能;
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
返回通知(After-returning):在目标方法成功执行之后调用通知;
异常通知(After-throwing):在目标方法抛出异常后调用通知;
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
前置通知(Before):在目标方法被调用之前调用通知功能;
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
返回通知(After-returning):在目标方法成功执行之后调用通知;
异常通知(After-throwing):在目标方法抛出异常后调用通知;
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
连接点(join point)
我们的应用可能也有数以千计的时机应用通知。这些时机被称为
连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可
以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利
用这些点插入到应用的正常流程之中,并添加新的行为。
连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可
以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利
用这些点插入到应用的正常流程之中,并添加新的行为。
切点(pointcut)
一个切面并不需要通知应用的所有连接点。切点有助于缩小切
面所通知的连接点的范围。
面所通知的连接点的范围。
如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何
处”。切点的定义会匹配通知所要织入的一个或多个连接点。我们通常
使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法
名称来指定这些切点。有些AOP框架允许我们创建动态的切点,可以根
据运行时的决策(比如方法的参数值)来决定是否应用通知。
处”。切点的定义会匹配通知所要织入的一个或多个连接点。我们通常
使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法
名称来指定这些切点。有些AOP框架允许我们创建动态的切点,可以根
据运行时的决策(比如方法的参数值)来决定是否应用通知。
切面(Aspect)
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——
它是什么,在何时和何处完成其功能。
它是什么,在何时和何处完成其功能。
引入(Introduction)
引入允许我们向现有的类添加新方法或属性。例如,我们可以创建一
个Auditable通知类,该类记录了对象最后一次修改时的状态。这很简
单,只需一个方法,setLastModified(Date),和一个实例变量来保存这
个状态。然后,这个新方法和实例变量就可以被引入到现有的类中,从
而可以在无需修改这些现有的类的情况下,让它们具有新的行为和状
态。
个Auditable通知类,该类记录了对象最后一次修改时的状态。这很简
单,只需一个方法,setLastModified(Date),和一个实例变量来保存这
个状态。然后,这个新方法和实例变量就可以被引入到现有的类中,从
而可以在无需修改这些现有的类的情况下,让它们具有新的行为和状
态。
织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定
的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以
进行织入:
的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以
进行织入:
编译期:切面在目标类编译时被织入。这种方式需要特殊的编译
器。AspectJ的织入编译器就是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊
的类加载器(ClassLoader),它可以在目标类被引入应用之前增
强该目标类的字节码。AspectJ 5的加载时织入(load-time
weaving,LTW)就支持以这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入
切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring
AOP就是以这种方式织入切面的。
器。AspectJ的织入编译器就是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊
的类加载器(ClassLoader),它可以在目标类被引入应用之前增
强该目标类的字节码。AspectJ 5的加载时织入(load-time
weaving,LTW)就支持以这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入
切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring
AOP就是以这种方式织入切面的。
Spring 对AOP的支持
基于代理的经典Spring AOP;
纯POJO切面;
@AspectJ注解驱动的切面;
注入式AspectJ切面(适用于Spring各版本)。
前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之
上,因此,Spring对AOP的支持局限于方法拦截
纯POJO切面;
@AspectJ注解驱动的切面;
注入式AspectJ切面(适用于Spring各版本)。
前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之
上,因此,Spring对AOP的支持局限于方法拦截
通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的
bean中。如图4.3所示,代理类封装了目标类,并拦截被通知方法的调
用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调
用目标bean方法之前,会执行切面逻辑。
bean中。如图4.3所示,代理类封装了目标类,并拦截被通知方法的调
用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调
用目标bean方法之前,会执行切面逻辑。
直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的
是ApplicationContext的话,在ApplicationContext从BeanFactory中加
载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时
才创建代理对象,所以我们不需要特殊的编译器来织入Spring AOP的切
面。
是ApplicationContext的话,在ApplicationContext从BeanFactory中加
载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时
才创建代理对象,所以我们不需要特殊的编译器来织入Spring AOP的切
面。
Spring只支持方法级别的连接点
通过切点来选择连接点
Spring借助AspectJ的切点表达式语言来定义Spring切面
arg() 限制连接点匹配参数为指定类型的执行方法
@args() 限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配是连接点的执行方法
this() 限制连接点匹配AOP代理的bean引用为指定类型的类
target 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)
@annotation 限定匹配带有指定注解的连接点
@args() 限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配是连接点的执行方法
this() 限制连接点匹配AOP代理的bean引用为指定类型的类
target 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)
@annotation 限定匹配带有指定注解的连接点
编写切点
1.先定义一个接口
Performance可以代表任何类型的现场表演,如舞台剧、电影或音乐
会。假设我们想编写Performance的perform()方法触发的通知。图4.4展
现了一个切点表达式,这个表达式能够设置当perform()方法执行时触发
通知的调用。
会。假设我们想编写Performance的perform()方法触发的通知。图4.4展
现了一个切点表达式,这个表达式能够设置当perform()方法执行时触发
通知的调用。
我们使用execution()指示器选择Performance的perform()方法。方法表
达式以“*”号开始,表明了我们不关心方法返回值的类型。然后,我们
指定了全限定类名和方法名。对于方法参数列表,我们使用两个点号
(..)表明切点要选择任意的perform()方法,无论该方法的入参是什么。
达式以“*”号开始,表明了我们不关心方法返回值的类型。然后,我们
指定了全限定类名和方法名。对于方法参数列表,我们使用两个点号
(..)表明切点要选择任意的perform()方法,无论该方法的入参是什么。
现在假设我们需要配置的切点仅匹配concert包。在此场景下,可以使
用within()指示器来限制匹配,如图4.5所示。
用within()指示器来限制匹配,如图4.5所示。
请注意我们使用了“&&”操作符把execution()和within()指示器连接在一
起形成与(and)关系(切点必须匹配所有的指示器)。类似地,我们
可以使用“||”操作符来标识或(or)关系,而使用“!”操作符来标识非
(not)操作。
因为“&”在XML中有特殊含义,所以在Spring的XML配置里面描述切点
时,我们可以使用and来代替“&&”。同样,or和not可以分别用来代
替“||”和“!”。
起形成与(and)关系(切点必须匹配所有的指示器)。类似地,我们
可以使用“||”操作符来标识或(or)关系,而使用“!”操作符来标识非
(not)操作。
因为“&”在XML中有特殊含义,所以在Spring的XML配置里面描述切点
时,我们可以使用and来代替“&&”。同样,or和not可以分别用来代
替“||”和“!”。
在切点中选择bean
在这里,我们希望在执行Performance的perform()方法时应用通知,但
限定bean的ID为woodstock。
限定bean的ID为woodstock。
在某些场景下,限定切点为指定的bean或许很有意义,但我们还可以使
用非操作为除了特定ID以外的其他bean应用通知:
用非操作为除了特定ID以外的其他bean应用通知:
使用注解创建切面
定义切面
Audience类使用@AspectJ注解进行了标注。该注解表明Audience不仅仅
是一个POJO,还是一个切面。Audience类中的方法都使用注解来定义
切面的具体行为。
是一个POJO,还是一个切面。Audience类中的方法都使用注解来定义
切面的具体行为。
代码
@After 通知方法会在目标方法返回或抛出异常后调用
@AfterReturning 通知方法会在目标方法返回后调用
@AfterThrowing 通知方法会在目标方法抛出异常后调用
@Around 通知方法会将目标方法封装起来
@Before 通知方法会在目标方法调用之前执行
@AfterReturning 通知方法会在目标方法返回后调用
@AfterThrowing 通知方法会在目标方法抛出异常后调用
@Around 通知方法会将目标方法封装起来
@Before 通知方法会在目标方法调用之前执行
通过@Pointcut注解声明频繁使用的切点表达式
在Audience中,performance()方法使用了@Pointcut注解。为@Pointcut
注解设置的值是一个切点表达式,就像之前在通知注解上所设置的那
样。通过在performance()方法上添加@Pointcut注解,我们实际上扩展
了切点表达式语言,这样就可以在任何的切点表达式中使
用performance()了,如果不这样做的话,你需要在这些地方使用那个
更长的切点表达式。我们现在把所有通知注解中的长表达式都替换成了
performance()。
performance()方法的实际内容并不重要,在这里它实际上应该是空
的。其实该方法本身只是一个标识,供@Pointcut注解依附。
注解设置的值是一个切点表达式,就像之前在通知注解上所设置的那
样。通过在performance()方法上添加@Pointcut注解,我们实际上扩展
了切点表达式语言,这样就可以在任何的切点表达式中使
用performance()了,如果不这样做的话,你需要在这些地方使用那个
更长的切点表达式。我们现在把所有通知注解中的长表达式都替换成了
performance()。
performance()方法的实际内容并不重要,在这里它实际上应该是空
的。其实该方法本身只是一个标识,供@Pointcut注解依附。
需要注意的是,除了注解和没有实际操作的performance()方
法,Audience类依然是一个POJO。我们能够像使用其他的Java类那样调
用它的方法,它的方法也能够独立地进行单元测试,这与其他的Java类
并没有什么区别。Audience只是一个Java类,只不过它通过注解表明会
作为切面使用而已。
像其他的Java类一样,它可以装配为Spring中的bean
如果你就此止步的话,Audience只会是Spring容器中的一个bean。即便
使用了AspectJ注解,但它并不会被视为切面,这些注解不会解析,也不
会创建将其转换为切面的代理
法,Audience类依然是一个POJO。我们能够像使用其他的Java类那样调
用它的方法,它的方法也能够独立地进行单元测试,这与其他的Java类
并没有什么区别。Audience只是一个Java类,只不过它通过注解表明会
作为切面使用而已。
像其他的Java类一样,它可以装配为Spring中的bean
如果你就此止步的话,Audience只会是Spring容器中的一个bean。即便
使用了AspectJ注解,但它并不会被视为切面,这些注解不会解析,也不
会创建将其转换为切面的代理
在JavaConfig中启用AspectJ注解的自动代理
如果你使用JavaConfig的话,可以在配置类的类级别上通过使
用EnableAspectJ-AutoProxy注解启用自动代理功能。程序清单4.3展现
了如何在JavaConfig中启用自动代理
用EnableAspectJ-AutoProxy注解启用自动代理功能。程序清单4.3展现
了如何在JavaConfig中启用自动代理
创建环绕通知
环绕通知是最为强大的通知类型。它能够让你所编写的逻辑将被通知的
目标方法完全包装起来。实际上就像在一个通知方法中同时编写前置通
知和后置通知。
目标方法完全包装起来。实际上就像在一个通知方法中同时编写前置通
知和后置通知。
子主题
在这里,@Around注解表明watchPerformance()方法会作
为performance()切点的环绕通知。在这个通知中,观众在演出之前会
将手机调至静音并就坐,演出结束后会鼓掌喝彩。像前面一样,如果演
出失败的话,观众会要求退款。
可以看到,这个通知所达到的效果与之前的前置通知和后置通知是一样
的。但是,现在它们位于同一个方法中,不像之前那样分散在四个不同
的通知方法里面。
为performance()切点的环绕通知。在这个通知中,观众在演出之前会
将手机调至静音并就坐,演出结束后会鼓掌喝彩。像前面一样,如果演
出失败的话,观众会要求退款。
可以看到,这个通知所达到的效果与之前的前置通知和后置通知是一样
的。但是,现在它们位于同一个方法中,不像之前那样分散在四个不同
的通知方法里面。
关于这个新的通知方法,你首先注意到的可能是它接受
ProceedingJoinPoint作为参数。这个对象是必须要有的,因为你要在通
知中通过它来调用被通知的方法。通知方法中可以做任何的事情,当要
将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint的
proceed()方法。
需要注意的是,别忘记调用proceed()方法。如果不调这个方法的话,
那么你的通知实际上会阻塞对被通知方法的调用。有可能这就是你想要
的效果,但更多的情况是你希望在某个点上执行被通知的方法。
有意思的是,你可以不调用proceed()方法,从而阻塞对被通知方法的
访问,与之类似,你也可以在通知中对它进行多次调用。要这样做的一
个场景就是实现重试逻辑,也就是在被通知方法失败后,进行重复尝
试。
ProceedingJoinPoint作为参数。这个对象是必须要有的,因为你要在通
知中通过它来调用被通知的方法。通知方法中可以做任何的事情,当要
将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint的
proceed()方法。
需要注意的是,别忘记调用proceed()方法。如果不调这个方法的话,
那么你的通知实际上会阻塞对被通知方法的调用。有可能这就是你想要
的效果,但更多的情况是你希望在某个点上执行被通知的方法。
有意思的是,你可以不调用proceed()方法,从而阻塞对被通知方法的
访问,与之类似,你也可以在通知中对它进行多次调用。要这样做的一
个场景就是实现重试逻辑,也就是在被通知方法失败后,进行重复尝
试。
处理通知中的参数
在图4.6中需要关注的是切点表达式中的args(trackNumber)限定符。它
表明传递给playTrack()方法的int类型参数也会传递到通知中去。参数
的名称trackNumber也与切点方法签名中的参数相匹配。
这个参数会传递到通知方法中,这个通知方法是通过@Before注解和命
名切点trackPlayed(trackNumber)定义的。切点定义中的参数与切点方
法中的参数名称是一样的,这样就完成了从命名切点到通知方法的参数
转移。
表明传递给playTrack()方法的int类型参数也会传递到通知中去。参数
的名称trackNumber也与切点方法签名中的参数相匹配。
这个参数会传递到通知方法中,这个通知方法是通过@Before注解和命
名切点trackPlayed(trackNumber)定义的。切点定义中的参数与切点方
法中的参数名称是一样的,这样就完成了从命名切点到通知方法的参数
转移。
通过注解引入新功能
(引入的AOP)
(引入的AOP)
可以看到,EncoreableIntroducer是一个切面。但是,它与我们之前所
创建的切面不同,它并没有提供前置、后置或环绕通知,而是通过
@DeclareParents注解,将Encoreable接口引入到Performance bean中。
创建的切面不同,它并没有提供前置、后置或环绕通知,而是通过
@DeclareParents注解,将Encoreable接口引入到Performance bean中。
@DeclareParents注解由三部分组成:
1.value属性指定了哪种类型的bean要引入该接口。在本例中,也就
是所有实现Performance的类型。(标记符后面的加号表示
是Performance的所有子类型,而不是Performance本身。)
2.defaultImpl属性指定了为引入功能提供实现的类。在这里,我们
指定的是DefaultEncoreable提供实现。
3.@DeclareParents注解所标注的静态属性指明了要引入了接口。在这
里,我们所引入的是Encoreable接口。
1.value属性指定了哪种类型的bean要引入该接口。在本例中,也就
是所有实现Performance的类型。(标记符后面的加号表示
是Performance的所有子类型,而不是Performance本身。)
2.defaultImpl属性指定了为引入功能提供实现的类。在这里,我们
指定的是DefaultEncoreable提供实现。
3.@DeclareParents注解所标注的静态属性指明了要引入了接口。在这
里,我们所引入的是Encoreable接口。
需要将EncoreableIntroducer声明为一个bean
在XML中声明切面
为AspectJ切面注入依赖
第三部分后端中的Spring
Nosql数据库_Redis
连接到Redis
Spring Data Redis为
四种Redis客户端实现提供了连接工厂
四种Redis客户端实现提供了连接工厂
JedisConnectionFactory
JredisConnectionFactory
LettuceConnectionFactory
SrpConnectionFactory
JredisConnectionFactory
LettuceConnectionFactory
SrpConnectionFactory
如何配置JedisConnectionFactory bean
通过默认构造器创建的连接工厂会向localhost上的6379端口创建连接,
并且没有密码。如果你的Redis服务器运行在其他的主机或端口上,在
创建连接工厂的时候,可以设置这些属性:
并且没有密码。如果你的Redis服务器运行在其他的主机或端口上,在
创建连接工厂的时候,可以设置这些属性:
所有的Redis连接工厂都具有setHostName()、setPort()和setPassword()
方法。这样,它们在配置方面实际上是相同的。
方法。这样,它们在配置方面实际上是相同的。
使用RedisTemplate
Spring Data Redis提供了两个模板
RedisTemplate
StringRedisTemplate
RedisTemplate
StringRedisTemplate
缓存数据
启用对缓存的支持
通过使用@EnableCaching启用注解驱动的缓存
工作方式:
创建一个切面(aspect)并触发Spring缓存注解的
切点(pointcut)。根据所使用的注解以及缓存的状态,这个切面会从
缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值。
它们不仅仅启用了注解驱动的缓存,还声明了一个缓存管理器(cache manager)的
bean。缓存管理器是Spring缓存抽象的核心,它能够与多个流行的缓存实现进行集成
创建一个切面(aspect)并触发Spring缓存注解的
切点(pointcut)。根据所使用的注解以及缓存的状态,这个切面会从
缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值。
它们不仅仅启用了注解驱动的缓存,还声明了一个缓存管理器(cache manager)的
bean。缓存管理器是Spring缓存抽象的核心,它能够与多个流行的缓存实现进行集成
配置缓存管理器:
SimpleCacheManager
NoOpCacheManager
ConcurrentMapCacheManager
CompositeCacheManager
EhCacheCacheManager
RedisCacheManager(来自于Spring Data Redis项目)
GemfireCacheManager(来自于Spring Data GemFire项目)
SimpleCacheManager
NoOpCacheManager
ConcurrentMapCacheManager
CompositeCacheManager
EhCacheCacheManager
RedisCacheManager(来自于Spring Data Redis项目)
GemfireCacheManager(来自于Spring Data GemFire项目)
可以看到,在为Spring的缓存抽象选择缓存管理器时,我们有很多
可选方案。具体选择哪一个要取决于想要使用的底层缓存供应商。每一
个方案都可以为应用提供不同风格的缓存,其中有一些会比其他的更加
适用于生产环境。尽管所做出的选择会影响到数据如何缓存,但是
Spring声明缓存的方式上并没有什么差别
可选方案。具体选择哪一个要取决于想要使用的底层缓存供应商。每一
个方案都可以为应用提供不同风格的缓存,其中有一些会比其他的更加
适用于生产环境。尽管所做出的选择会影响到数据如何缓存,但是
Spring声明缓存的方式上并没有什么差别
以Java配置的方式设置EhCacheCacheManager
第四部分Spring集成
0 条评论
下一页