spring-core

作者: 竹天亮 | 来源:发表于2018-04-02 17:30 被阅读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里。

    限定符的方式注入对象
    • @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注解

    相关文章

      网友评论

        本文标题:spring-core

        本文链接:https://www.haomeiwen.com/subject/cycofftx.html