我爱编程

spring-core

2018-04-02  本文已影响61人  竹天亮

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里。

限定符的方式注入对象
@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注意以下几点:

如果有多个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使用它的术语可能更加令人疑惑。

advice的类型

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注解

上一篇 下一篇

猜你喜欢

热点阅读