[TOC]
Spring模块
- Core Container: 核心容器 spring-context:包含DI, IOC, springEl表达式
- Test: 加载Spring 上下文的Spring测试
- 数据访问与集成
- web 与远程调用
- AOP 提供对AOP的支持
核心技术
DI 依赖注入 : IOC容器(控制反转),利用依赖关系注入的方式,实现对象之间的解耦。
AOP 面向切面编程:把应用各处可重用的功能分离出来形成重用组件。
在运行时,动态地将代码切入到类的指定方法、指定位置上
bean容器
bean 工厂
-
应用上下文(基于bean工厂,提供应用框架级别的应用)
ApplicationContext 是BeanFactory 子类,加强了很多功能。如果使用简单的BeanFactory,大量的功能将失效,比如:transactions 和AOP . 避免直接用BeanFactory
应用上下文
AnnotationConfigApplicationContext
AnnotationConfigWebApplicationContext
ClassPathXmlApplicationContext
FileSystemXmlApplicatonContext
-
XmlWebApplicationContext
应用上下文通过getBean()获取bean
bean生命周期
回调机制
三种方式实现:
- 实现InitializingBean 和实现DisposableBean 回调接口
- 自定义init()和destroy()方法 并XML 面配置 Init-method=”” ; Destroy-method=””
- @PostConstruct and @PreDestroy 注解。
容器扩展点
实现BeanPostProcessor接口
在Spring容器完成实例化、配置、初始化bean之前之后执行自定义逻辑。
可以配置多个BeanPostProcessor实例,可以设置BeanPostProcessors的order属性来控制其执行次序。
points:
- 接口中的两个方法都要将传入的bean返回,而不能返回null
- 不要将BeanPostProcessor标记为延迟初始化,因为如果这样做,Spring容器将不会注册它们.
- BeanPostProcessors容器级别的。当使用容器继承时BeanPostProcessors不会继承。
注册方式:
- xml:
<bean class="com.spring.containerExtension.MyBeanPostProcessor" />
- 注解:@Component 修饰自定义的BeanPostProcessor类。并加到context里面。(声明时或者@ComponentScan)
使用注解的方式因为@Configuration类本身也会注册为一个bean定义,还有一些系统的bean 所以不好控制有效范围, 通过xml 方式容易控制。
e.g. @Required 就是用RequiredAnnotationBeanPostProcessor 实现的。
BeanFactoryPostProcessor
此接口的语法和BeanPostProcessor类似,用于读取配置元数据并且在容器实例化bean之前修改bean配置。这个也是在自己容器内有效,也不会继承。
延迟初始化
ApplicationContext的各种实现默认的初始化处理过程,都是尽早的创建、配置所有的单例bean。lazy-initialized bean告诉Ioc容器,只有在第一次请求的时候采取初始化,而不是在启动容器时初始化。
通过设置<beans/>元素的default-lazy-init属性,可以设置容器级别的延迟加载。
普通bean 需要的时候初始化。
装配bean
定义bean
xml 显式定义
-
注解显式定义
@Configuration @Bean @Component @Repository @Service @Controller +组件扫描ComponentScan
@Configuration
@ComponentScan("com.spring.baseIOC")
public class ConfigWithAnnotationTest {
@Bean //修饰方法
@Qualifier("bean1") //如果全局只有一个String 需要被注入,那这里可以省略Qualifier
String method(@Qualifier("bean2") String a){
return new String(a);
}
@Scope("prototype")
@Bean(name = "bean2")
@Qualifier("bean2_qual")
String method2(){
return new String("cvszz22");
}
@Autowired //声明这个field 会被自动注入
@Qualifier("SomeBeanAnnotation")
private SomeBeanAnnotation someBean;
}
注入bean
-
xml 显式配置
构造器注入 属性注入
-
注解显式配置
@Autowired
隐式的bean发现机制和自动装配
注解注入在XML注入之前执行,因此同时使用这两种方式注入时,XML配置会覆盖注解配置。
xml自动装配
autowire="byName"
- no 默认非自动装配
- byName
- byType
- constructor
设置<bean/>元素的autowire-candidate属性为false;可以排除他们自动注入到其他bean,但是它本身是可以被自动注入的。
通过设置<bean/>袁术的primary属性为true来指定单个bean定义作为主候选bean。
常用注解
@Autowired注解可以应用在setter, 构造函数上或者属性上,默认required 属性是 true 的。
@Required注解应用于bean的setter方法, 声明在配置时必须赋值
@Qualifier注解。可以将qualifier关联指定参数.
public void prepare(@Qualifier("main")MovieCatalog movieCatalog){};
qualifier值,只是为了缩小类型匹配的范围;他们并不能作为引用的bean的唯一标示符。
@PostConstruct @PreDestroy bean的回调
@Repository, @Service, @Controller 各层代码注解,分别注解dao , service, layers表现层. 他们默认id是类名首字母小写
@Repository能自动转换异常。 @Component是他们的元注解。
@Scope("prototype") 指定bean作用域scope
@Import注解也允许从其他配置类(@Configuration)中加载@Bean定义。 @Import(ConfigA.class) 这样相当与使用AnnotationConfigApplicationContext 或者@ConponentScan @ImportResource("classpath:/com/acme/properties-config.xml") 配置类(@Configuration)中引用XML配置。
@Configuration注解的类表明该类的主要目的是作为bean定义的源。 @Configuration类本身也会注册为一个bean定义,通过CGLIB创建一个子类。类内所有的@Bean注解的方法也会注册为bean定义。类不能是final.
@Bean注解用于表明一个方法将会实例化、配置、初始化一个新对象,该对象由Spring IoC容器管理。
@Primary该bean首选
@ComponentScan 自动探测类和自动注册bean定义
注解方式:在@Configuration注解的类上增加@ComponentScan,其basePackages属性就是上面两个类的所在的父级包路径。
xml: <context:component-scan base-package="org.example"/> 使用<context:component-scan>将会隐式的启用<context:annotation-config>
@Configuration 只是声明该类为bean源,还需要@ConponentScan 去加载到context 里面。或者在声明AnnotationConfigApplicationContext 的时候都加到里面。
bean 作用域
singleton | 默认的。一个bean定义,在一个IoC容器内只会产生一个对象。 |
prototype原型 | 每次都创建一个新的实例 |
request | 每个HTTP请求都产生新的实例。Spring web上下文环境中有效。 |
session | 生命周期在HTTP 会话期间。Spring web上下文环境中才有效。 |
一般来说 ,原型bean用于有状态bean,单例bean用于无状态bean。一个数据访问对象(DAO)通常不会配置成原型作用域,因为通常DAO不会持有任何会话状态。
如果想将HTTP request作用域(request,session,globalSession)bean注入给其他singleton bean,就得给作用域bean(request或者session)注入一个AOP代理用来替换作用域bean。容器会创建一个代理对象,该对象拥有和原对象完全相同的public方法。
因为容器初始化的时候singleton 需要加载,但此时session bean还未存在
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/> //代理只会拦截public方法调用。非public方法不会“呼叫转移”给实际的作用域bean。
</bean>
若是在Java代码中配置bean,使用@Scope注解并配置其proxyMode属性.默认配置是没有代理ScopedProxyMode.NO, 但是你可以设置ScopedProxyMode.TARGET_CLASS或者ScopedProxyMode.INTERFACES。 @Scope(value ="session", proxyMode =ScopedProxyMode.TARGET_CLASS)
@Scope(value = WebApplicationContext.SCOPE_SESSION,proxyMode = ScopedProxyMode.TARGET_CLASS)
高级装配
Environment环境
Environment环境在容器中是一个抽象的集合,是指应用环境的2个方面: profiles 和 properties.
profile
bean定义profile是核心容器内的一种机制,该机制能在不同环境中注册不同的bean。
比如:
- 开发期,QA或者产品上使用来自JNDI的不同数据源
- 开发期使用监控组件,当部署以后则关闭监控组件
Profile定义
- @Profile可以用于元数据注解,注解方法(@Bean 注解的方法),注解类(@Configuration。 i.g. @Profile("production")
- XML中的beans 也有profile 属性。 profile="dev"
profile定义的bean都将被忽略,除非该profile被激活
如果给定的profile,使用了NOT操作(!)前缀,若当前profile未被激活则注解元素将会注册。对于@Profile({"p1", "!p2"}),在profile 'p1'被激活或者'p2'未激活时,发生注册。
激活profile
1.编程式开启方式
AnnotationConfigApplicationContext ctx =new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
2.使用spring.profiles.active属性激活配置 。
spring.profiles.active="profile1,profile2"
3.servlet初始化参数,web上下文参数,环境变量等等
PropertySource
Spring的环境抽象可以检索一系列的property sources属性配置文件。
Spring的StandardEnvironment配置了2个PropertySource对象-其一是JVM系统properties(System.getProperties()),另一个是一组系统环境变量(System.getenv())。
@Component
@PropertySource(value ="classpath:com/spring/Property/quartz.properties")
public class MyProperty{
//为了支持spring el 表达式
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfig() {
return new PropertySourcesPlaceholderConfigurer();
}
}
@ContextConfiguration(classes = {MyProperty.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class PropertyTest {
@Autowired
private Environment env;
@Test
public void test(){
System.out.print(env);
}
}
条件化的bean
使用@Conditional可以用到带有@Bean 注解的方法上,指向一个实现了Condition接口的类,根据matches方法返回的boolean值确定是否注册这个bean
@Conditional(MyConditional.class)
@Bean(name = "b2")
public ClientServiceImpl getService() {
return ...;
}
方法替换
设置replaced-method元素,就可用其他实现来替换已经部署的bean中存在的方法实现。
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<replaced-method name="computeValue" replacer="replacementComputeValue">
<!--替换computerValue方法-->
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
<!--ReplacementComputeValue 需要实现org.springframework.beans.factory.support.MethodReplacer 接口-->
方法注入
在容器范围内查找一个bean作为方法返回结果
<bean id="A" class="com.spring.lookup_method.UserManager" >
<lookup-method name="getUser" bean="B" />
</bean>
<bean id="B" class="com.spring.lookup_method.User"/>
public abstract class UserManager {
protected abstract User getUser();
public void run(){
User user = getUser();
System.out.println(user);
}
}
class User{
String name;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
事务
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.SERIALIZABLE)
事务传播行为
PROPAGATION_SUPPORTS | 有事务就用,没有就不用 |
PROPAGATION_NOT_SUPPORTED | 不用事务,如果当前有就挂起事务 |
PROPAGATION_MANDATORY | 有就用,没有就抛异常 |
PROPAGATION_NEVER | 有事务就跑出异常 |
PROPAGATION_REQUIRED | 有就用,没有就新建 |
PROPAGATION_REQUIRES_NEW | 新建一个,如果当前有就挂起当前事务 |
Propagation.NESTED | 没有就新建,有就当前事务中再嵌套事务运行 — 嵌套事务中可以定义储存点,因此可以独立于外部的Transaction而进行rollback |
数据库的隔离级别
数据库事务隔离级别:一个事务对数据库的修改与并行的另一个事务的隔离程度。
并发访问数据库存在的问题
- 脏读:事务T1更新了一行记录,还未提交所做的修改,这个T2读取了更新后的数据,然后T1执行回滚操作,取消刚才的修改,所以T2所读取的行就无效,也就是脏数据。
- 不可重复读取:事务T1读取一行记录,紧接着事务T2修改了T1刚刚读取的记录,然后T1再次查询,发现与第一次读取的记录不同,这称为不可重复读。
- 幻读:事务T1读取一条指定where条件的语句,返回结果集。此时事务T2插入一行新记录,恰好满足T1的where条件。然后T1使用相同的条件再次查询,结果集中可以看到T2插入的记录,这条新纪录就是幻想。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读和幻读是读取了前一事务提交的数据。
不可重复读的重点是修改,幻读的重点在于新增或者删除
四种隔离级别
- Read uncommitted (读未提交):最低级别,任何情况都无法保证。
- Read committed (读已提交):可避免脏读的发生。
- Repeatable read (可重复读):可避免脏读、不可重复读的发生。
- Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Read committed (读已提交)和Serializable (串行化)级别这两种级别,其中默认的为Read committed级别。
设置数据库的隔离级别一定要是在开启事务之前,隔离级别的设置只对当前链接有效。
例子:
左面是事务T1,右面是事务T2,因为T2级别为SERIALIZABLE,所以即使事务T1在提交了数据之后,事务T2还是看不到T1提交的数据,幻想读和不可重复读都不允许了。
那如何能查看到T1新增的记录呢? 上面T1和T2是并发执行,在T1执行insert的时候如果事务T2已经开始了,因为T2级别是SERIALIZABLE,所以T2所查询的数据集是T2事务开始前数据库的数据。即事务T1在事务T2开始之后的insert和update操作的影响都不会影响事务T2。现在重新开启一个事务T3 就可以看到T1新增的记录了。
AOP
五种类型的通知
- @Before
- @After
- @AfterReturning 成功执行后
- @AfterThrowing 抛异常后
- @Around
Spring 的AspectJ自动代理只是使用AspectJ作为创建切面的指导,切面仍然是基于代理的。本质上,它仍然是Spring 基于代理的切面。如果要使用全部AspectJ功能需要不依赖Spring 来创建切面并且在运行时使用AspectJ
@Aspect
@Component
public class MyInterceptor implements Ordered {
//第一个* 代表任意返回值, .. 代表任意参数
//符合条件的才被切入
@Pointcut("execution(* com.spring.aop.MyService.*(..))")
private void anyMethod(){}//定义一个切入点
@Before("anyMethod() && args(name,param2)")
public void doAccessCheck(String name,int param2){
System.out.println(name);
System.out.println(param2);
System.out.println("前置通知");
}
@AfterReturning("anyMethod()")
public void doAfter(){
System.out.println("后置通知");
}
}
<context:component-scan base-package="com.spring.aop" />
<!--打开aop -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="serviceBean" class="com.spring.aop.MyService">
</bean>
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/aop.xml");
MyService bean = (MyService) ctx.getBean("serviceBean");
bean.update("param1",2);
}
切面的织入顺序
order 的值越小,说明越先被执行
@Component
@Aspect
public class MyAspectJ implements Ordered{
...
@Override
public int getOrder() {
return 1;
}
}
<!-- 注解方式配置事物 -->
<tx:annotation-driven transaction-manager="transactionManager" order="2"/>
这样就实现了我们自己写的aop在事务介入之前就执行了!
JSR-330
Spring3.0开始,Spring提供了对JSR-330标准注解(依赖注入)的支持。这些注解以Spring注解相同的方式被扫描。
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
Spring | javax.inject.* | |
---|---|---|
@Autowired | @Inject | @Inject 没有required属性 |
@Component | @Named | |
@Scope("singleton") | @Singleton | JSR-330默认的作用域类似于Spring的prototype原型作用域。为了保持Spring的一致性,在Spring容器中的JSR-330的bean声明,默认是singleton单例。除了singleton,若要设置作用域,得使用Spring的@Scope注解。javax.inject也提供了一个@Scope注解。然而,这个注解仅仅是为了让你创建自定义注解用的译注,也就是元注解的源码注解。 |
@Qualifier | @Named | |
@Value | 无等价注解 | |
@Required | 无等价注解 | |
@Lazy | 无等价注解 |
others
spring-web 初始化配置 (spring-mvc 不需要这个)
为了支持request,sesssion,global session这种级别bean的作用域(web作用域bean),在定义bean之前需要一些初始化的配置。
若使用 Spring Web MVC访问这些作用域bean,实际上是使用Srping DispatcherServlet类或者DispatcherPortlet类处理request,则无需特别配置:DispatcherServlet 和 DispatcherPortlet已经暴露了所有的相关状态。
非Spring的DispacherServlet处理请求(比如,JSF或者Struts2)。
- 小于2.5 的Servlet web容器: RequestContextFilter
- Servlet 2.5的web容器则需要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener。
- Servlet 3.0+,这些设置可以通过编程式方式使用WebApplicationInitializer接口完成。
DispatcherServlet,RequestContextListener,RequestContextFilter都是做相同的事,也就是绑定HTTP request对象到服务的Thread线程中,并开启接下来 用到的session-scoped功能。
SSH 需要上面的spring + spring-web +struts2配置。如果是用spring-mvc 只需spring + spring-mvc 自己的servlet (DispatcherServlet)配置就可以了。