Spring

[TOC]

Spring模块

untitledq.png
  1. Core Container: 核心容器 spring-context:包含DI, IOC, springEl表达式
  2. Test: 加载Spring 上下文的Spring测试
  3. 数据访问与集成
  4. web 与远程调用
  5. AOP 提供对AOP的支持

核心技术

DI 依赖注入 : IOC容器(控制反转),利用依赖关系注入的方式,实现对象之间的解耦。

AOP 面向切面编程:把应用各处可重用的功能分离出来形成重用组件。

在运行时,动态地将代码切入到类的指定方法、指定位置上

bean容器

  • bean 工厂

  • 应用上下文(基于bean工厂,提供应用框架级别的应用)

    ApplicationContext 是BeanFactory 子类,加强了很多功能。如果使用简单的BeanFactory,大量的功能将失效,比如:transactions 和AOP . 避免直接用BeanFactory

untitled.png

应用上下文

  • AnnotationConfigApplicationContext

  • AnnotationConfigWebApplicationContext

  • ClassPathXmlApplicationContext

  • FileSystemXmlApplicatonContext

  • XmlWebApplicationContext

    应用上下文通过getBean()获取bean

bean生命周期

untitled.jpg

回调机制

三种方式实现:

  • 实现InitializingBean 和实现DisposableBean 回调接口
  • 自定义init()和destroy()方法 并XML 面配置 Init-method=”” ; Destroy-method=””
  • @PostConstruct and @PreDestroy 注解。

容器扩展点

实现BeanPostProcessor接口

在Spring容器完成实例化、配置、初始化bean之前之后执行自定义逻辑。
可以配置多个BeanPostProcessor实例,可以设置BeanPostProcessors的order属性来控制其执行次序。
points:

  1. 接口中的两个方法都要将传入的bean返回,而不能返回null
  2. 不要将BeanPostProcessor标记为延迟初始化,因为如果这样做,Spring容器将不会注册它们.
  3. 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定义
  1. @Profile可以用于元数据注解,注解方法(@Bean 注解的方法),注解类(@Configuration。 i.g. @Profile("production")
  2. 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

数据库的隔离级别

数据库事务隔离级别:一个事务对数据库的修改与并行的另一个事务的隔离程度。

并发访问数据库存在的问题

  1. 脏读:事务T1更新了一行记录,还未提交所做的修改,这个T2读取了更新后的数据,然后T1执行回滚操作,取消刚才的修改,所以T2所读取的行就无效,也就是脏数据。
  2. 不可重复读取:事务T1读取一行记录,紧接着事务T2修改了T1刚刚读取的记录,然后T1再次查询,发现与第一次读取的记录不同,这称为不可重复读。
  3. 幻读:事务T1读取一条指定where条件的语句,返回结果集。此时事务T2插入一行新记录,恰好满足T1的where条件。然后T1使用相同的条件再次查询,结果集中可以看到T2插入的记录,这条新纪录就是幻想。

不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读和幻读是读取了前一事务提交的数据。

不可重复读的重点是修改,幻读的重点在于新增或者删除

四种隔离级别

  1. Read uncommitted (读未提交):最低级别,任何情况都无法保证。
  2. Read committed (读已提交):可避免脏读的发生。
  3. Repeatable read (可重复读):可避免脏读、不可重复读的发生。
  4. 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)。

  1. 小于2.5 的Servlet web容器: RequestContextFilter
  2. Servlet 2.5的web容器则需要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener。
  3. Servlet 3.0+,这些设置可以通过编程式方式使用WebApplicationInitializer接口完成。

DispatcherServlet,RequestContextListener,RequestContextFilter都是做相同的事,也就是绑定HTTP request对象到服务的Thread线程中,并开启接下来 用到的session-scoped功能。

SSH 需要上面的spring + spring-web +struts2配置。如果是用spring-mvc 只需spring + spring-mvc 自己的servlet (DispatcherServlet)配置就可以了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,778评论 6 342
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 什么是Spring Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Jav...
    jemmm阅读 16,445评论 1 133
  • 1. 前几天和朋友出去逛,看到很精致的冰箱贴,吸引了她,就快要入手。 想了想,上班独居的生活,既没有人要提醒她什么...
    凉风有信YY阅读 146评论 0 0
  • 01 有人曾这样说过,小学是一个班的小学,初中是一群人的初中,高中是几个人的高中,而大学是一个人的大学。年少的我对...
    马铃薯煮土豆阅读 369评论 20 9