一、Spring实战-第一部分.Spring核心知识
- 第1章将会概要地介绍Spring,包括DI和AOP的一些基本样例。同时,读者还会了解到更大的Spring生态系统的整体情况。
- 第2章更为详细地介绍DI,展现应用程序中的各个组件(bean)如何装配在一起。这包括基于XML装配、基于Java装配以及自动装配。
- 在掌握了基本的bean装配后,第3章会介绍几种高级装配技术,读者可能并不会经常用到这些技术,但是如果用到的话,本章的内容将会 告诉读者如何发挥Spring容器最强大的威力。
- 第4章介绍如何使用Spring的AOP来为对象解耦那些对其提供服务的横切性关注点。这一章也为后面各章提供基础,在后面读者将会使用 AOP来提供声明式服务,如事务、安全和缓存。
(一)装配Bean
1.组件扫描和自动装配
- @Component :value @Named
- @Autowired,@Inject
- @ComponentScan: basePackages,basePackageClasses
2.装配三方库组件--通过Java代码装配Bean
尽管在很多场景下通过组件扫描和自动装配实现Spring的自动化配置是更为推荐的方式,但有时候自动化配置的方案行不通,因此需要明确配 置Spring。比如说,你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添 加@Component和@Autowired注解的,因此就不能使用自动化装配的方案了。
- @Configuration
- @Bean
3.通过通XML装配装bean
-
声明bean
<bean></bean> -
注入bean
<constructor-arg></constructor-arg>-
ref属性、value属性
-
<null/>;<list></list>、<set></set>;<value></value>、<ref></ref>;
-
`<property></property>`
name属性
`<null/>` ;`<list></list>`、`<set></set>`;`<value></value>`、`<ref></ref>`;
4.混合配置
-
JavaConfig+JavaConfig
用@Import注解,将CDPlayerConfig.java、CDConfig.java配置类与SoundSystemConfig.java配置组合在一起。 CDPlayerConfig.java、CDConfig.java也是被@Configuration注解的配置类。
-
JavaConfig中引入xml配置
使用@ImportResource
-
xml配置引入JavaConfig
使用Bean标签引入JavaConfig
-
xml配置引入xml配置
使用<import/>标签
(二)、高级装配
1、条件化的bean
实现Condition的matches方法:
public class MagicBeanCondition implements Condition{
/**
*
* @param conditionContext 可以获得Environment实例
* @param annotatedTypeMetadata
* @return
*/
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
return environment.getProperty("magic",Boolean.class);
}
}
使用@Conditional注解
@Component
@Conditional(MagicBeanCondition.class)
public class MagicBean{
...
}
如果是显式地定义bean,则可以这样使用:
@Bean
@Conditional(MagicBeanCondition.class)
public MagicBean magicBean(){
return new MagicBean();
}
这样当MagicBeanCondition.matches方法返回true时,就可以创建magicBean了,反之就不会创建实例。
2、条件化的bean的特例-profile
profile是一种特殊的条件化bean。解决了跨环境部署时,中间件配置或其他配置存在环境差异的问题。最典型的例子就是数据库——各个环境都会配置属于自己的数据库。
(1) 使用@Profile
使用@Profile注解指定某个bean属于哪一个profile,@Profile注解应用在了类级别上。它会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建。如果dev profile没有激活的话,那么带有@Bean注解的方法都会被忽略掉。
@Configuration
@Profile("dev") // dev profile被激活时才会创建实例
public class DbConfig{
@Bean
public DataSource db(){
return new ...;
}
}
(2) 激活profile
-
作为DispatcherServlet的初始化参数;
-
作为Web应用的上下文参数;
-
作为JNDI条目;
-
作为环境变量;
-
作为JVM的系统属性;
-
在集成测试类上,使用@ActiveProfiles注解设置。
最佳实践
spring.profiles.active和spring.profiles.default组合使用:
a)spring.profiles.default=dev
DispatcherServlet、Web应用的上下文参数配置spring.profiles.default=dev因此,所有的开发人员都能从版本控制软件中获得应用程序源码,并使用开发环境的设置(如数据库)运行代码,而不需要任何额外的配置。
b)spring.profiles.active按需配置
使用使用系统属性、环境变量或JNDI设置配置spring.profiles.active,又可实现不同环境的按需配置。
3、自动装配的歧义性
(1)什么是自动装配的歧义性
Spring自动装配时,当注入的类型是一个接口,并且程序中存在该接口的多个实现,这多个实现都生成了实例,自动装配就会出现歧义:两个实现我选择哪个呢?
举个例子更具体地说明这个问题:
- Dessert是一个接口,有若干实现,很显然@Component标注后,Spring会为他们创建实例,它们都是Dessert类型。
@Component
public class IceCream implements DessertDessert
类型{...}
@Component
public class Cake implements Dessert{...}
- 现在有个实例依赖Dessert:
@Autowired
public void setDessert(Dessert dessert){
this.dessert=dessert;
}
这个时候Spring自动装配的时候,就懵逼了:该选哪一个呢?
(2)怎么解决歧义
(a)指定首选项
方式1,@Component+@Primary
@Component
@Primary
public class IceCream implements Dessert{
...
}
如果是显式地声明bean的话,也可以@Bean+@Primary
@Bean
@Primary
public Dessert dessert(){
return new IceCream();
}
(b)限定自动装配的bean
step1. @Qualifier限定bean
@Component
@Qualifier("cold")
public class IceCream implements Dessert{
...
}
如果是显式地声明bean的话,也可以@Bean+@Qualifier
@Bean
@Qualifier("cold")
public Dessert dessert(){
return new IceCream();
}
step2. 赖注入时使用@Qualifier指定限定的bean
@Autowired
public void setDessert(Dessert dessert){
this.dessert=dessert;
}
4、bean的作用域
(1)四种作用域
- 单例:默认作用域。
- 原型:每次注入或者通过Spring上下文获取的时候,都会创建一个新的实例。
- 会话:一个会话对应一个实例。
- 请求:一个request对应一个实例。
如何修改默认作用域?
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public NotePad notepad {
return new NotePad();
}
(2)会话和请求作用域装配后如何工作?
问题思考:
ShoppingCart作用域=会话,Store作用域=单例,其中Store依赖于ShoppingCart,Spring创建Store单例并进行依赖注入时,没有ShoppingCart可用,因为还没有会话生成。这个时候怎么实现注入呢?
Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理。
这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。见下图。
图1 作用域代理能够延迟注入请求和会话作用域的bean
5、运行时值注入
- 占位符: ${变量名}
- SpEL表达式: #{...}
(三)Spring AOP
1、定义切点
定义一个方法,咱们准备从这个方法的执行去做切点
@Component
public class Performance {
public void perform(){
...
}
}
表2 Spring借助借 AspectJ的切点表达式语言来定义 的 Spring切面
2、定义切面
@Aspect
public class PerformanceAspect {
@Before("execution(* Performance.perform())") // 查看1中【表2 Spring借助借 AspectJ的切点表达式语言来定义 的 Spring切面】
public void before(){
...
}
}
3、创建通知
(1)通知类型
@Aspect
public class PerformanceAspect {
@Before("execution(* Performance.perform())")
public void before(){
...
}
@After("execution(* Performance.perform())")
public void after(){
...
}
@Around("execution(* Performance.perform())")
public void around(ProceedingJoinPoint joinPoint){
...
joinPoint.proceed();// 继续执行被拦截方法
...
}
@AfterThrowing("execution(* Performance.perform())")
public void afterThrowing(){
...
}
@AfterReturning("execution(* Performance.perform())")
public Object afterReturning(){
...
}
}