spring-core

bean: 应用里被Spring IoC容器管理的对象叫做bean.Spring IoC容器负责bean的初始化,组装和管理。bean以及它们之间的依赖关系反映在容器使用的配置元数据中。

BeanFactory: 提供了配置框架和基本功能。BeanFactory提供了一套高级的配置机制可以管理所有类型的对象。

ApplicationContext: BeanFactory的子类。用来表示IoC容器并负责bean的初始化,配置,组装。容器通过读取配置元数据获取有关要实例化,配置和组装的对象的说明.

循环依赖问题

循环依赖:类A在构造方法里依赖类B,类B的构造方法里需要类A。此时就会导致循环依赖问题,Spring IoC容器在运行时检测到后,会抛出BeanCurrentlyInCreationException
一种解决办法就是使用setter注入而不是构造注入,尽管推荐构造注入,但是setter注入可以解决循环依赖。

bean引入不同生命周期bean的解决方法

问题,当单例bean A需要非单例的bean B时,容器只会创建一次bean A,也就是只有一次机会设置属性。容器不能在每次bean A都需要bean B的时候实例化bean B。

一种解决方法就是不用IoC,通过实现ApplicationContextAware接口让bean A知道容器,然后加入一个getBean("B")的方法来在bean A需要B的时候实例化。如下:

package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class BManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("B", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

不过这样不太好,毕竟业务代码和Spring框架耦合了。更高级的方式就是使用方法注入,从这可以了解更多this blog entry.

查找方法注入

查找方法注入是容器重载容器管理的bean上方法的能力,以返回容器中另一个命名bean的查找结果。Spring框架通过使用cglib库中的字节码生成来实现此方法注入,以动态生成覆盖该方法的子类。

  • 为了spring动态创建子类能正常工作,Spring容器将要继承的类和要被重载的方法不能是final
  • 另一个关键的限制是查找方法不能用于工厂方法,特别是不能在配置类中使用@bean方法,因为在这种情况下容器不负责创建实例,因此不能创建运行时生成的子类在飞行中。
    新的代码:
package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    @Lookup("myCommand")
    protected abstract Command createCommand();
}
管理bean生命周期的行为

通过实现Spring的InitializingBeanDisposableBean接口对bean的生命周期做出不同的反应。不过更好的是使用@PostConstruct@PreDestroy注解管理初始化和销毁。不用和Spring的接口产生耦合。还有xml的init-methoddestroy-method都可以解耦。

使用BeanPostProcessor自定义bean

The BeanPostProcessor接口定了回调方法让你可以实现你自己或重载容器默认的实例化逻辑,依赖解析逻辑等等。如果需要再Spring的容器完成实例化、配置化和初始化一个bean后加上自己的逻辑,可以实现一个或者多个BeanPostProcessor做到。如果配置了多个BeanPostProcessor,想要设置顺序的话,通过implement Ordered接口提供的Ordered属性来设置执行顺序

通过索引提高启动速度

虽然类路劲扫描很快,但依然可以在编译期间创建一组静态候选者提高大型应用的启动速度,原理是当ApplicationContext检测到索引后,会使用它而不是再扫描。
生成索引的方式是,在每个包含组件(component)的模块下添加以下依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.0.5.BUILD-SNAPSHOT</version>
        <optional>true</optional>
    </dependency>
</dependencies>

gradle添加的方式:

dependencies {
    compileOnly("org.springframework:spring-context-indexer:5.0.5.BUILD-SNAPSHOT")
}

这个过程会创建一个META-INF/spring.component的文件,会被包含在jar里。

限定符的方式注入对象
  • @Primary注解标识为优先注入的对象
  • @Qualifier 注解的方式指定要注入的
  • 使用泛型的方式
@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}
@Autowired
private Store<String> s1; // <String> 泛型限定符, 注入stringStore()

@Autowired
private Store<Integer> s2; // <Integer>泛型限定符, 注入integerStore()

// 注入所有的Store,只要他们的泛型是<Integer>
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;

ApplicationContext事件处理和自定义事件

ApplicationContext事件处理是通过ApplicationEventApplicationListener接口实现的,当实现了ApplicationListener接口的bean部署到context里,每当ApplicationEvent获取到了发布给ApplicationContext的事件,相关的bean就会被通知。从本质上来讲,这是标准的观察者模式。
Spring提供了以下标准的event,ContextRefreshedEventContextStartedEvent,ContextStoppedEvent,ContextStoppedEvent,RequestHandledEvent

自定义事件

首先定义一个event, 需要继承Spring的ApplicationEvent

public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String test;

    public BlackListEvent(Object source, String address, String test) {
        super(source);
        this.address = address;
        this.test = test;
    }

    // accessor and other methods...
}

要发布event的话,调用ApplicationEventPublisherpublishEvent()方法。然后相关的bean需要实现ApplicationEventPublisher。代码如下:

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String text) {
        if (blackList.contains(address)) {
            BlackListEvent event = new BlackListEvent(this, address, text);
            publisher.publishEvent(event);
            return;
        }
        // send email...
    }
}

Spring容器会在配置期间检测到实现了ApplicationEventPublisherAwareEmailService类,然后自动调用setApplicationEventPublisher()。实际上,传入的是Spring容器自己。

想要接收到发送的event,创建一个实现了ApplicationListener接口的类并注册为Spring bean。代码如下:

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

注意ApplicationListener被参数化为自定义的event,BlackListEvent。这意味着onApplicationEvent()方法可以保持类型安全,避免不必要的向下转换。你可以注册多个event listener,但是默认的event listener接收events是同步的。这就会导致publishEvent()被阻塞,直到所有的listeners处理完event。单线程和同步的一个好处是可以共享事务。如果需要另个一event发布策略,可以参考Spring的ApplicationEventMulticaster接口。

注解的方式创建listeners
public class BlackListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlackListEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

使用@EventListener后,不需要实现指定的接口,方法名也可以自定义。

如果想监听多个event,并且不想定义任何参数,可以在注解上指定event类型,如下:

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    ...
}

如果想要把发布的event的结果处理另一个event,只需要将方法返回类型改为想要被发布的event,当然不支持异步,如下:

@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

上面的代码就会处理完BlackListEvent后发布一个新的ListUpdateEvent。如果需要发布多个event,只需要返回events的Collection

异步监听器

如果想要特定的listeners异步处理,只需要简单的加上一个@Async注解,如下:

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

使用异步event注意以下几点:

  • 如果event listener抛出了异常,不会通知给调用者,check AsyncUncaughtExceptionHandler了解更多。
  • 这样的event listener无法发送返回。如果需要,自己注入ApplicationEventPublisher手动发送。

如果有多个Listener,可以使用@Order注解指定顺序,如下:

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}

如果想用泛型定义event的结构。可以使用EntityCreatedEvent<T><T>是创建的实体类型。You can create the following listener definition to only receive EntityCreatedEvent for a Person:

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    ...
}

可能会遇到类型擦除的问题,具体可以参考使用ResolvableTypeProvider.

Spring AOP

让我们从中心AOP和术语开始。这些术语不是Spring专属的,而AOP术语也不是很直观。如果Spring使用它的术语可能更加令人疑惑。

  • Aspect: 切面,关注跨越多个类的模块。事务管理就是一个好例子。在Spring AOP里,aspects是通过被@Aspect注解的类实现,或者基于schema实现的。

  • Joint Point:一个程序执行期间的点。比如方法处理或者异常处理。在Spring AOP里,一个Joint Point总是代表一个方法的执行。

  • Advice:切面里特定的Joint Point采取的行动。不同类型的advice包含beforearound, after等。许多AOP框架里,包含Spring,将advice作为拦截器的模型。围绕着Joint Point维护一系列的拦截器。

  • Pointcuts: 切入点。匹配到joint point的。Advice是与切入点表达式相关联,并且运行任何匹配到joint point的切入点(比如,指定名字的方法的执行)。与切入点表达式匹配的连接点的概念是aop的核心,Spring默认使用AspectJ表达式语言。

  • Introduction: 为类型定义其他的方法或者字段。Spring AOP允许你为任意advised对象引入新的接口(包括相应的实现)。比如,你可以使用一个introduction让bean实现IsModified接口。

  • Target Object: 被一个或者多个切面advised的对象。也被称为advised对象。由于spring aop是使用运行时代理实现的,因此该对象将始终是被代理对象。

  • AOP proxy: 有AOP框架创建的对象,用于实现aspect contract。在Spring框架里,AOP proxy是JDK动态代理或者CGLIB代理。

  • Weaving: 将aspect于其他应用的类型或者对象连接起来,来创建一个advised对象。这个可能在编译期(比如AspectJ编译器)、加载期或者运行期完成。Spring AOP, 想起他纯java AOP框架一样,在运行期执行weaving。

advice的类型
  • Before advice: 在joint point前执行的advice
  • After return advice: 在joint point执行完成并返回后执行advice。比如,方法返回了并且没有抛异常
  • After throwing advice: 在方法抛出异常后执行。
  • After (finally) advice: 不过joint point正常返回或异常都会执行。
  • Around advice: 方法调用时所有的情况都执行。around advice很全面。around advice可以在方法调用前和后执行自定义行为。也会在正常返回和抛出异常时执行。
    虽然around advice包含了所有的joint point, 但Spring建议使用指定类型的advice。而不是直接使用around advice.
    从Spring 2.0开始,所有的advice参数都可以是合适的类型,而不是一个Objects数组。

Spring AOP的能力和目标

Spring AOP使用纯Java实现的。所以不需要特别的复杂的处理。Spring AOP不需要控制类加载器层,因此适用于Servelt容器或者应用服务器。

Spring AOP当前仅支持方法执行joint point(Spring beans上的advising的方法执行)。字段拦截没有被实现,如果想拦截字段,可以考虑使用AspectJ。

Spring AOP的目标和其他AOP框架不同,Spring AOP的目标不是提供一个完整的AOP实现。而是想要提供一个整合AOP和Spring IoC的方式来解决企业通过用的问题。
所以Spring AOP的功能是和Spring IoC容器一般是一起使用的。Aspect用正常的bean定义语法配置:这是与其他AOP框架最关键的区别。所以用Spring AOP,不能简单或者高效的处理细粒度的类:AspectJ在这种情况下很合适。Spring AOP永远不会与AspectJ竞争来提供一个全面的AOP解决方案。我们相信基于代理的框架,比如Spring AOP和成熟的AspectJ框架都是有价值的,两者相互补充,而不是竞争。Spring的Spring AOP和Spring IoC无缝整合AspectJ,以便照顾到所有基于Spring应用结构的AOP使用。

AOP 代理

Spring AOP默认为AOP代理使用Java动态代理。这让所有的接口都可以被代理。

Spring AOP也可以使用CGLIB代理。当需要代理类而不是接口的时候。如果一个对象类没有实现一个接口,Spring AOP会使用CGLIB。但更好的是基于接口编程而不是类。业务类通常会实现一个或者多个业务接口。

Spring组件扫面可能扫不到@Aspect注解,建议可以再加上个@Component注解

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,649评论 18 139
  • 什么是Spring Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Jav...
    jemmm阅读 16,458评论 1 133
  • 感恩日记第十五天:1.感恩垚瑶妈妈中午炒的芹菜,腌制的小咸菜,味道美美哒,我又享受了一顿美味的午餐!谢谢!谢谢!谢...
    丰盛富足阅读 111评论 0 0
  • 我现在的问题不是学习不好的困惑,而是如何让大学生活更有意义的困惑。摆脱掉原来好好学习的想法,其实学习并不能带给我...
    最爱曹格格阅读 135评论 0 0
  • 关于幼儿班的印象,一直很模糊,我记得就上了半年,就因为我天资聪颖破格升到了一年级,没办法,无敌就是这么寂寞。长大后...
    会飞的丶鱼阅读 126评论 0 2