基于Spring Framework 版本:5.0.2.RELEASE


IoC

概念:传统Java开发中,程序通过new主动创建对象实例,而Spring有专门的IoC容器来创建对象,具体来说就是在Spring容器中注册过的类,其创建、销毁等过程交由Spring来统一负责管理,所以这一过程也叫依赖注入(DI)。

Spring的基础IoC容器包是org.springframework.beans和org.springframework.context。

核心接口

BeanFactory接口作为容器的根接口,提供Bean的一些基础定义和方法;

ApplicationContext是BeanFactory的子接口,为应用程序提供了更丰富的功能。

BeanFactory、ApplicationContext之间的继承关系如下图

Spring IoC和AOP的介绍-LMLPHP

注入方式

Spring的主要有两种注入方式:构造器注入、Setter方法注入。如何选择呢?

  • Spring推荐使用构造器注入,因为构造器注入时,component组件可以当作不可变对象,并且能确保其不为空,而且构造器注入的component返回时是完全初始化的状态。
  • 当一个类中需要注入太多的参数时,可能这个类负责了太多的功能,可以考虑适当的重构。
  • Setter方法注入可以作为备选方案,但是如果没有默认值时最好进行非空检查。
  • Setter注入的一个好处是类的对象能够在后来重新配置或重新注入。
  • 如果使用的第三方源码不提供Setter方法时,就只能选择构造器注入的方式了。
  • 构造器注入可能引入循环依赖问题。比如:A类构造器注入B,B类构造器注入A,此时Spring容器会抛出异常BeanCurrentlyInCreationException。此时需要考虑使用Setter方法注入的方式了。

配置方法

Spring常用XML和注解的方式来配置类,这里推荐使用注解来配置。需要注意的是注解是在XML之前执行注入的,因此后者的配置将覆盖注解的配置。

Bean的作用域和生命周期

Bean的作用域,其中request、Session、application、websocket仅在ApplicationContext上下文才有效。

singleton

默认值,单例,整个IoC容器只有一个实例对象。

prototype

原型,每次调用都会实例化一个地响。

request

作用于HTTP请求的生命周期,每个HTTP请求都有一个自己的实例。仅在ApplicationContext上下文有效。

session

作用于HTTP Session的生命周期,仅在ApplicationContext上下文有效。

application

作用于ServletContext的生命周期,仅在ApplicationContext上下文有效。

websocket

作用于WebSocket的生命周期,仅在ApplicationContext上下文有效。

Bean的生命周期

Spring IoC和AOP的介绍-LMLPHP

过程描述

1.Spring对bean进行实例化;

2.Spring将值和bean的引用注入到bean对应的属性中;

3.如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;

4.如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;

5.如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;

6.如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;

7.如果bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()方法。类似地,如果bean使用init-method声明了初始化方法,该方法也会被调用;

8.如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;

9.此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;

10.如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。

常用注解说明

@Bean, @Configuration表示基于Java配置的类

@Bean除了配置在@Configuration,也可以在@Component定义,此时没有特殊意义,只是普通的工厂方法。

@Import 导入依赖的类,进行优先注入

@ImportResource 导入依赖的Spring配置

@Value("${jdbc.url}") 可以使用${}动态获取配置参数

@PropertySource 可导入properties配置文件

@Qualifier 指定属性名称

@Autowired和 @Resource

@Autowired通过类型选择Bean,@Resource通过名称选择Bean。

@Resource 是JSR-250注解

@Primary 注入的优先级

@PostConstruct 在初始化时执行的方法

@PreDestroy 在销毁时执行的方法

@Bean(initMethod = "init") @Bean(destroyMethod = "cleanup") 调用初始化和销毁的方法

@Component, @Repository, @Service, @Controller,都是component,所以都可以被scan,一般分别用于标注po、dao、service和controller

Spring MVC 提供@RestController ,它是@Controller 和 @ResponseBody的组合形式。

@ComponentScan (basePackages = "") 扫描component

扫描过滤器使用:@ComponentScan(basePackages = "org.example",  includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),  excludeFilters = @Filter(Repository.class))

  基于XML的配置可以用ClassPathXmlApplicationContext来获取Bean,基于Java的配置可以使用AnnotationConfigApplicationContext,它提供register()注册配置类。

Spring的国际化

当加载一个ApplicationContext时,它会自动搜索上下文中定义的MessageSource bean。bean必须有名称messageSource。如果找到了这样的bean,那么所有对方法的调用都将被委托给消息源。如果没有找到消息源,ApplicationContext将尝试寻找包含同名bean的父类。

Spring提供了一些国际化的实现类,如:ResourceBundleMessageSource

Spring事件

Spring的事件处理是通过ApplicationEvent类和ApplicationListener接口实现的。如果一个实现ApplicationListener接口的bean被部署到上下文中,那么每次应用程序事件被发布到ApplicationContext时,就会通知bean。实际上这就是观察者模式

可以在容器中配置监听器以启动容器

 <context-param>
<param-name>contextConfigLocation</param-name>
<param-value> /WEB-INF/applicationContext.xml</param-value>
</context-param> <listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Resource

Spring提供了更加高级的资源文件处理方法,具体使用可查看API文档

校验、数据绑定、类型转换

Spring实现Validator接口可以非侵入式地校验!另外可以使用这两个类MessageCodesResolver,DefaultMessageCodesResolver来获取国际化错误信息。

Spring提供DataBinder类实现数据绑定的功能。DataBinder和Validator组合,实现Spring的校验包。

Bean包装器BeanWrapper,用来包装Bean,给Bean设置属性。

   BeanWrapper company = new BeanWrapperImpl(new Company());

Spring实现Converter接口、ConverterFactory接口来实现类型转换。

常用的转换器:

通用转换器GenericConverter;

条件转换器ConditionalGenericConverter

门面模式转换器ConversionService

Spring实现Formatter接口、AnnotationFormatterFactory注解格式化接口,实现参数的格式化

举例如下:

@NumberFormat(style=Style.CURRENCY)

@DateTimeFormat(iso=ISO.DATE)

Spring提供注解格式化规则注册类:FormatterRegistry ,FormatterRegistrar

Spring MVC中的格式化类是FormattingConversionServiceFactoryBean

自定义校验注解实现ConstraintValidator


AOP

Aspect-Oriented Programming (AOP) ,OOP的关键模块单元是class,而AOP的关键模块单元是切面。AOP主要可以应用在事务,日志,安全等方面。

 一些概念

  • 切面Aspect:由切面和切点来定义出一个切面;
  • 连接点Join point:执行程序时的一个点,例如执行方法、处理异常、修改字段,这些点可以用来插入切面代码。在Spring AOP中,连接点就是执行方法。
  • 通知Advice:在特定连接点上采取的行动。
  • 切点Pointcut:表达式匹配通知所要织入的一个或多个连接点,是AOP的核心,Spring在默认情况下使用AspectJ切点表达式语言。
  • 引入Introduction:引入允许我们向现有的类添加新方法或属性。Spring AOP允许向任何被通知的对象引入新的接口(以及相应的实现)。(在AspectJ社区中,介绍称为跨类型声明。)
  • 目标对象Target object:被通知的对象,由于Spring AOP是动态代理实现的,所以这个对象将永远是一个代理对象。
  • AOP代理AOP proxy:一个由AOP框架创建的对象,以实现切面约定(通知方法执行等等)。在Spring框架中,AOP代理将是一个JDK动态代理或CGLIB代理。
  • 织入Weaving:把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:编译期(AspectJ)、类加载期、运行期(Spring AOP)。

通知类型

  • 前置通知Before advice: 在目标方法被调用之前调用通知功能;
  • 返回通知After returning advice: 在目标方法成功执行之后调用通知;
  • 异常通知After throwing advice: 在目标方法抛出异常后调用通知;
  • 后置通知After (finally) advice: 在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
  • 环绕通知Around advice: 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

AOP代理,默认使用标准的JDK动态代理,如果业务对象没有实现接口,则默认使用CGLIB

对于JDK代理,只有在代理上调用的公共接口方法才能被拦截。使用CGLIB,在代理的public和protected方法调用将被拦截,甚至在必要时也可以使用包可见default的方法。

简单使用说明

1、声明切面@Aspect

首先要启用@AspectJ注释

@Configuration

@EnableAspectJAutoProxy

public class AppConfig{ }

2、声明切点@Pointcut

例如:@Pointcut("execution(* transfer(..))")匹配任何名为'transfer'的方法的执行

Spring的PCD支持

Spring不支持AspectJ中的call, get, set, preinitialization, staticinitialization, initialization, handler, adviceexecution, withincode, cflow, cflowbelow, if, @this, @withincode,如果使用了会抛出IllegalArgumentException

Spring AOP支持的PCD(切点指示符):

execution - 匹配执行方法的连接点,Spring是最主要的PCD;

within - 用类型匹配连接点;

this - 指定的Bean引用类型;

target - 指定的对象实例;

args - 指定的类型参数;

@target - 执行对象的类有指定类型的注释;

@args - 实际参数的运行时类型有指定类型的注释;

@within - 在具有指定注释的类型中限制匹配的连接点;

@annotation - 限制的连接点有指定的注释;

Spring AOP还支持bean(idOrNameOfBean)指定Bean的id或名字。

PCD组合使用

切入点表达式可以通过'&&'''和'!'来组合。也可以通过名称引用切入点表达式。举例如下:

@Pointcut("execution(public * *(..))")

private void anyPublicOperation() {}

@Pointcut("within(com.xyz.someapp.trading..*)")

private void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()")

private void tradingOperation() {}

3、声明通知

@Before

@AfterReturning

@AfterThrowing

@After //After (finally) advice

@Around

Spring代理机制简单说明

    • final方法不能通知;
    • Spring 3.2以后不需要再引用CGLIB包;
    • Spring 4.0以后代理对象的构造器不再被调用两次,因为CGLIB代理实例将通过Objenesis来创建,只有在JVM不允许构造器绕过的情况下才能看到两次调用

强制使用CGLIB方法:@EnableAspectJAutoProxy(proxyTargetClass = true)

 

参考资料

Spring Framework官方文档:https://docs.spring.io/spring/docs/5.0.2.RELEASE/spring-framework-reference/

《Spring In Action》

05-25 21:54