Spring的两个核心特性,也就是依赖注入(dependency injection,DI)和面向切面编程 (aspect-oriented programming,AOP)。
Spring自动配置、基于Java的配置以及XML配置
条件化装配、处理自动装配时的歧义性、作用域以及Spring表达式语言
使用Spring的AOP特性把系统级的服务(例如安全和审计)从它们所服务的对象中解耦出来
将Spring AOP用于声明式安全以及缓存。
Spring提供了更加轻量级和简单的编程模型。它增强了简单老式Java对象 (Plain Old Java object,POJO)的功能,使其具备了之前只有EJB和其他企业级Java规范才具有的功能。
Spring是为了解决企业级应用开发的复杂性而创建的,使用Spring可以让简单的JavaBean实现之前只有EJB才能完成的事情。但Spring不仅仅局限于服务器端开发,任何Java应用都能在简单性、可测试性和松 耦合等方面从Spring中获益。
为了降低Java开发的复杂性,Spring采取了以下4种关键策略:
1,基于POJO的轻量级和最小侵入性编程;
2,通过依赖注入和面向接口实现松耦合;
3,基于切面和惯例进行声明式编程;
4,通过切面和模板减少样板式代码。
通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。对象无需自行创建或管理它们的依赖关系,如图1.1所示,依赖关系将被自动注入到需要它们的对象当中去。
构造器注入
面向切面编程(aspect-oriented programming,AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。
<aop:aspect>
<aop:before> 前置通知(before advice)
<aop:after>)后置通知(after advice)
pointcut-ref
<pointcut>
Spring容器(container)
容器是Spring框架的核心。Spring容器使用DI管理构成应用的组件,它会创建相互协作的组 件之间的关联。
Spring自带了多个容器实现,可以归为两种不同的类型。bean工 厂(由org.springframework. beans. factory.BeanFactory接口定义)是最简单 的容器,提供基本的DI支持。应用上下文 (由org.springframework.context.ApplicationContext接口定义)基于 BeanFactory构建,并提供应用框架级别的服务,例如从属性文件解析文本信息以及发布应用 事件给感兴趣的事件监听者。
AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加 载Spring应用上下文。
AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中 加载Spring Web应用上下文。
ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件 中加载上下文定义。
XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上 下文定义。
Bean的生命周期
实例化--》填充属性--》调用BeanNameAware的setBeanName方法--》调用BeanFactoryAware的setBeanFactory方法--》调用ApplicationContextAware的setApplicationContext方法--》调用BeanPostProcessor的预初始化方法--》调用InitializingBean的afterPropertiesSet方法--》调用自定义的初始化方法--》调用BeanostProcessor的初始化后方法--》bean可以使用了。
容器关闭--》调用DisposableBean的destroy方法--》调用自定义的销毁方法
行详细描 述:
1.Spring对bean进行实例化;
2.Spring将值和bean的引用注入到bean对应的属性中;
3.如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方 法;
4.如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将 BeanFactory容器实例传入;
5.如果bean实现了ApplicationContextAware接口,Spring将调 用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
6.如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization()方法;
7.如果bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()方法。类似地,如果bean使用init-method声明了初始化方法,该方 法也会被调用;
8.如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessAfterInitialization()方法;
9.此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中, 直到该应用上下文被销毁;
10.如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同 样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。
第二章 装配Bean
创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本 质。
提供了三种主要的装配机制:
在XML中进行显式配置。
在Java中进行显式配置。
隐式的bean发现机制和自动装配。
自动化装配
Spring从两个角度来实现自动化装配:
组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
自动装配(autowiring):Spring自动满足bean之间的依赖。
@Component注解。这个注解表明该类会作为组件类,并告知Spring要为这个类创建bean。
@ComponentScan注解,这个注解能够在Spring中启用组件扫描。 @ComponentScan默认会扫描与配置类相同的包。Spring将会扫描这个包以及这个包下 的所有子包,查找带有@Component注解的类。并且 会在Spring中自动为其创建一个bean。 (使用XML来启用组件扫描的话,那么可以使用Spring context命名空间 的<context:component-scan>元素)。,如果想扫描不同的包或者多个基础包,需要在@ComponentScan的value属性中指明包的名称(@ComponentScan(“packageName”)),可以通过basePackages属性进行配置(@ComponentScan(baePackages=“packageName”)或者@ComponentScan(baePackages=(“packageName1”,“packageName2”)))。@ComponentScan还提供了另外一种方法,那就是 将其指定为包中所包含的类或接口@ComponentScan(baePackagesClasses=(CLass1.class,CLass1.class))。
@ContextConfiguration会告诉它需要 在CDPlayerConfig中加载配置。
Spring应用上下文中所有的bean都会给定一个ID。我们没有明确地设置ID,但Spring会根据类名为其指定一个ID。具体来讲,这个bean所给定的ID为也就是将类名的第一个字母变为小写。 如果想为这个bean设置不同的ID,你所要做的就是将期望的ID作为值传递给@Component注 解.
还有另外一种为bean命名的方式,这种方式不使用@Component注解,而是使用Java依赖注 入规范(Java Dependency Injection)中所提供的@Named注解来为bean设置ID:
自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会 在Spring应用上下文中寻找匹配某个bean需求的其他bean。
@Autowired注解不仅能够用在构造器上,还能用在属性的Setter方法上。甚至可以用在类的任何方法上。
如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异 常的出现,你可以将@Autowired的required属性设置为false
@Autowired(required=false)
将required属性设置为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean 的话,Spring将会让这个bean处于未装配的状态。
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选 择哪个bean进行自动装配。
@Inject注解来源于Java依赖注入规范,该规范同时还为我们定义了@Named注解。在自动 装配中,Spring同时支持@Inject和@Autowired。尽管@Inject和@Autowired之间有着 一些细微的差别,但是在大多数场景下,它们都是可以互相替换的。
通过Java代码装配bean
创建配置类
创建JavaConfig类的关键在于为其添加@Configuration注解
@Configuration注解表 明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。
声明简单的bean
@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中 的bean。方法体中包含了最终产生bean实例的逻辑。
默认情况下,bean的ID与带有@Bean注解的方法名是一样的。如果你想为其设置成一个不同的名字的话,那么可以重命名该方法,也可 以通过name属性指定一个不同的名字:
@Bean(name=“beanName”)
借助JavaConfig实现注入
通过XML装配bean
用来装配bean的最基本的XML元素包含在spring-beans模式之中
<beans>是该模式中的一个元素
<bean>元素类似于JavaConfig中的@Bean注解
高级配置
环境与profile
使用@Profile注解指定某个bean属于哪一个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注解设置。
条件化bean
@Conditional注解,它可以用到带有@Bean注解的方法上。如果给定的条件计算结果 为true,就会创建这个bean
@Conditional(ConditionalClass.class):设置给@Conditional的类可以是任意实现了Condition接口的类型。只需提供matches()方法的实现即可。如果matches()方法返 回true,那么就会创建带有@Conditional注解的bean。如果matches()方法返回 false,将不会创建这些bean。
通过ConditionContext,可以做到如下几点:
借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是 否存在,甚至探查bean的属性;
借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是 什么;
读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
借助getClassLoader()返回的ClassLoader加载并检查类是否存在。
处理自动装配的歧义性
如果不止有一个bean能够匹 配结果的话,这种歧义性会阻碍Spring自动装配属性、构造器参数或方法参数。
1,标示首选
在声明bean的时候,通过将其中一个可选的bean设置为首选(primary)bean能够避免自动装 配时的歧义性。
通过@Primary。@Primary能够与@Component组合用在组件扫描的bean上,也可以与@Bean组合用在 Java配置的bean声明中。
如果你使用XML配置bean的话,同样可以实现这样的功能。<bean>元素有一个primary属 性用来指定首选的bean
<bean id="iceCream" class="com.desserteater.Icecream" primary="true" />
2,限定自动装配的bean
Spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到只 有一个bean满足所规定的限制条件。如果将所有的限定符都用上后依然存在歧义性,那么你 可以继续使用更多的限定符来缩小选择范围。
@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使 用,在注入的时候指定想要注入进去的是哪个bean
@Qualifier注解所设置的参数就是想要注入的bean的 ID。所有使用@Component注解声明的类都会创建为bean,并且bean的ID为首字母变为小写 的类名。因此,@Qualifier("iceCream")指向的是组件扫描时所创建的bean,并且这个 bean是IceCream类的实例。
创建自定义的限定符
可以为bean设置自己的限定符,而不是依赖于将bean ID作为限定符。在这里所需要做的 就是在bean声明上添加@Qualifier注解。例如,它可以与@Component组合使用
bean的作用域
在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。也 就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。
Spring定义了多种作用域,可以基于这些作用域创建bean,包括:
单例(Singleton):在整个应用中,只创建bean的一个实例。
原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新 的bean实例。
会话(Session):在Web应用中,为每个会话创建一个bean实例。
请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
如果选择其他的 作用域,要使用@Scope注解,它可以与@Component或@Bean一起使用。
就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。要指定会 话作用域,我们可以使用@Scope注解,它的使用方式与指定原型作用域是相同的
@Component
@Scope(
value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES
)
public ShoppingCart cart(){}
将value设置成了WebApplicationContext中的SCOPE_SESSION常量(它 的值是session)。这会告诉Spring为Web应用中的每个会话创建一个ShoppingCart。这会创建多个ShoppingCart bean的实例,但是对于给定的会话只会创建一个实例,在当前 会话相关的操作中,这个bean实际上相当于单例的。
@Scope同时还有一个proxyMode属性,它被设置成了 ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单 例bean中所遇到的问题。
假设我们要将ShoppingCart bean注入到单例StoreService bean的Setter方法中,如下所 示:
@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的代理,当StoreService调 用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的 ShoppingCart bean。
proxyMode属性被设置成了ScopedProxyMode.INTERFACES,这表明这个代理要实 现ShoppingCart接口,并将调用委托给实现bean。
运行时值注入
Spring提供了两种在运行时求值的方式:
属性占位符(Property placeholder)。
Spring表达式语言(SpEL)。
1,注入外部的值
在Spring中,处理外部值的最简单方式就是声明属性源并通过Spring的Environment来检索 属性。
2,使用Spring表达式语言进行装配
SpEL拥有很多特性,包括:
使用bean的ID来引用bean;
调用方法和访问对象的属性;
对值进行算术、关系和逻辑运算;
正则表达式匹配;
集合操作。
1,测试,修改scomponentscan注解,看看有什么影响
2,搜索ComponentScan