基于SpringFramework 5.3.9 的core部分,一些常用方法、类、注解说明
1 IOC容器
1.1 Bean
1.1.1 实例化
- 构造器实例化
- 静态工厂实例化
- 实例工厂实例化
1.1.2 依赖
- 构造函数注入
- setter方法注入
1.1.3 作用域
作用域 | 描述 |
---|---|
singleton | (默认) 每一Spring IOC容器都拥有唯一的实例对象。 |
prototype | 一个Bean定义可以创建任意多个实例对象. |
request | 将单个bean定义范围限定为单个HTTP请求的生命周期。 也就是说,每个HTTP请求都有自己的bean实例,它是在单个bean定义的后面创建的。 只有基于Web的Spring ApplicationContext 的才可用。 |
session | 将单个bean定义范围限定为HTTP Session 的生命周期。 只有基于Web的Spring ApplicationContext 的才可用。 |
application | 将单个bean定义范围限定为ServletContext 的生命周期。 只有基于Web的Spring ApplicationContext 的才可用。 |
websocket | 将单个bean定义范围限定为 WebSocket 的生命周期。 只有基于Web的Spring ApplicationContext 的才可用。 |
自定义作用域需实现org.springframework.beans.factory.config.Scope
,然后注册到ConfigurableBeanFactory.registerScope(..)
容器中
1.1.4 自定义Bean特性
Bean生命周期
1.1.4.1初始化回调
org.springframework.beans.factory.InitializingBean
所有属性设置后进行实例化,实现**afterPropertiesSet()**方法
@PostConstruct
@Bean
指定**initMethod()**方法
销毁回调
org.springframework.beans.factory.DisposableBean
实现destroy()方法
@PreDestroy
@Bean
指定**destroyMethod()**方法
Lifecycle
org.springframework.context.Lifecycle
Lifecycle
接口的start
和stop
方法需要显示的调用,若想在容器容器时调用需要实现SmartLifecycle
spring启动刷新容器时调用refreshContext 方法,在AbstractApplicationContext
类的refresh()
方法的finishRefresh()
方法中
java
protected void finishRefresh() {
// Clear context-level resource caches (such as ASM metadata from scanning).
clearResourceCaches();
// 初始化容器的生命周期处理接口,默认为DefaultLifecycleProcessor实现
initLifecycleProcessor();
// DefaultLifecycleProcessor刷新并找到容器中实现了Lifecycle接口的Bean并调用start()方法
getLifecycleProcessor().onRefresh();
// Publish the final event.
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
if (!NativeDetector.inNativeImage()) {
LiveBeansView.registerApplicationContext(this);
}
}
DefaultLifecycleProcessor
先找到实现了SmartLifecycle接口并且isAutoStartup()方法为true的Bean,Phased
接口可实现执行顺序,值越小,越先执行
java
private void startBeans(boolean autoStartupOnly) {
Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
Map<Integer, LifecycleGroup> phases = new TreeMap<>();
lifecycleBeans.forEach((beanName, bean) -> {
if (!autoStartupOnly || (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())) {
int phase = getPhase(bean);
phases.computeIfAbsent(
phase,
p -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly)
).add(beanName, bean);
}
});
if (!phases.isEmpty()) {
phases.values().forEach(LifecycleGroup::start);
}
}
ApplicationContextAware
org.springframework.context.ApplicationContextAware
可获取ApplicationContext
,以获取容器的Bean等,方法setApplicationContext(ApplicationContext applicationContext)
由ApplicationContextAwareProcessor
实现
BeanNameAware
org.springframework.beans.factory.BeanNameAware
获取Bean的名字,方法setBeanName(String name)
在设置属性之后InitializingBean.afterPropertiesSet()
和初始化方法之前
1.1.5 容器扩展
BeanPostProcessor
org.springframework.beans.factory.config.BeanPostProcessor
用于容器完成实例化,配置和初始化bean之后可实现实例化逻辑,依赖关系解析逻辑等。在配置类上使用 @Bean
工厂方法声明BeanPostProcessor
时,工厂方法返回的类型应该是实现类自身或至少也是org.springframework.beans.factory.config.BeanPostProcessor
接口, 要清楚地表明这个bean是后置处理器。否则,在它完全创建之前,ApplicationContext
将不能通过类型自动探测它
可实现org.springframework.core.Ordered
排序,容器中的每个Bean都会执行实现的后置处理器,以addBeanPostProcessor
方式注册的处理器的Ordered无效,AbstractAutoProxyCreator
实现了创建AOP自动代理,也实现了BeanPostProcessor,@Autowired
注解由AutowiredAnnotationBeanPostProcessor
实现。
自定义BeanPostProcessor
实现毁掉接口或注解是容器扩展的一种常用手段
java
public interface BeanPostProcessor {
/**
* 在InitializingBean's afterPropertiesSet和初始化方法之前
*/
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* 在InitializingBean's afterPropertiesSet和初始化方法之后
*/
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
BeanFactoryPostProcessor
org.springframework.beans.factory.config.BeanFactoryPostProcessor
,Bean实例化前,可修改BeanDefinition
,占位符解析设置属性值等
与BeanPostProcessor
类似,可以获取Bean的元数据并操作,若通过BeanFactory.getBean()
获取Bean实例,会提前实例化Bean,不推荐
java
@FunctionalInterface
public interface BeanFactoryPostProcessor {
/**
* 已加载所有的Bean定义,未实例化Bean
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
PropertySourcesPlaceholderConfigurer
用于替换占位符
FactoryBean
org.springframework.beans.factory.FactoryBean
继承该接口,注册为Bean,通过id获取FactoryBean实例时,需在前面加上**&**,否则获取的是getObject
中的实例
java
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
1.1.6 基于注解的容器配置
@Autowired
用于set方法、构造函数、属性
按照类型注入,required = false
依赖为非必需,未找到不会报错。由BeanPostProcessor
处理
在构造器上
只有一个构造器时可不使用;参数为数组、集合、map(键为Bean名称)时至少有一个在容器中。注入的Bean可实现org.springframework.core.Ordered
或使用@Order
注解指定注入顺序(默认为容器中注册顺序);多个构造函数使用注解,只能有一个required=true
,若都为false,则尝试使用依赖最多的构造函数
@Primary
当按照类型注入有多个Bean时使用
@Qualifier
结合@Autowired注入指定名称的Bean
@Resource
属性、set方法
按照名称注入,默认为属性名或方法对应的Bean名。先按照名称,再按照类型注入
@Value
注入外部属性(@Value("${catalog.name:defaultCatalog}")
,可设置默认值
可自定义
PropertySourcesPlaceholderConfigurer
的Bean(@Bean方法需用static修饰),可设置占位符前缀后缀等
BeanPostProcessor
使用ConversionService
将string转为目标类型
#{}
获取Bean属性
@Component、@Service、@Repository、@Controller
注册Bean,后三个视为和@Component
一样
@ComponetScan
自动扫描包下的Bean并注册,在@Configuration
类上添加该注解,容器会扫描basePackages
属性的包路径。includeFilters
或excludeFilters
属性可自定义包括或排除的行为,每个filter元素都需要包含type
和expression
属性
过滤类型 | 表达式例子 | 描述 |
---|---|---|
annotation (default) | org.example.SomeAnnotation |
要在目标组件中的类级别出现的注解。 |
assignable | org.example.SomeClass |
目标组件可分配给(继承或实现)的类(或接口)。 |
aspectj | org.example..*Service+ |
要由目标组件匹配的AspectJ类型表达式。 |
regex | org\.example\.Default.* |
要由目标组件类名匹配的正则表达式。 |
custom | org.example.MyTypeFilter |
org.springframework.core.type .TypeFilter 接口的自定义实现。 |
@Configuration、@Bean
可以在@Configuration
类或@Component
类中使用@Bean
,bean的类型是方法的返回值类型。默认情况下, bean名称将与方法名称相同,@Bean方法中的参数会自动注入。@Configuration
类允许通过调用同一个类中的其他@Bean
方法来定义bean间依赖关系,多次调用同一个@Bean方法返回的是同一个被CGLB代理的对象
@Scope
指定Bean范围
@Import
允许在一个配置类中显式引入其他类(如 @Configuration
类、@Component
类、ImportSelector
或 ImportBeanDefinitionRegistrar
的实现类)并注入为容器的Bean
@Profiles
当组件满足环境条件时,可以注册该组件,类或方法注解
java
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
@PropertySource
用于增加PropertySource
到Spring的 Environment
中,可使用占位符${}
Spring的StandardEnvironment
配置有两个PropertySource
对象 ,一个表示JVM系统属性(System.getProperties()
),一个表示系统环境变量(System.getenv()
)。系统属性优先于环境变量,属性值为合并。
@EnableLoadTimeWeaving
LoadTimeWeaver
被Spring用来在将类加载到Java虚拟机(JVM)中时动态地转换类
1.1.7 扩展
1.1.7.1 国际化
ApplicationContext
接口扩展了一个名为MessageSource
的接口,提供了国际化("i18n")功能,ApplicationContext
被加载时,它会自动搜索在上下文中定义的一个MessageSource
,bean名称必须为messageSource
1.1.7.2 事件
内置事件
事件 | 说明 |
---|---|
ContextRefreshedEvent | 初始化或刷新ApplicationContext 时发布(例如,通过使用ConfigurableApplicationContext 接口上的refresh() 方法)。 这里,"initialized"意味着加载所有bean,检测并激活bean的后置处理器,预先实例化单例,并且可以使用ApplicationContext 对象。 只要上下文尚未关闭,只要所选的ApplicationContext 实际支持这种"热"刷新,就可以多次触发刷新。 例如,XmlWebApplicationContext 支持热刷新,但GenericApplicationContext 不支持。 |
ContextStartedEvent | 启动 ApplicationContext 时发布。通过使用ConfigurableApplicationContext 接口上的start() 方法启动ApplicationContext 时发布。 通常,此信号用于在显式停止后重新启动Bean,但它也可用于启动尚未为自动启动配置的组件(例如,在初始化时尚未启动的组件)。 |
ContextStoppedEvent | 当 ApplicationContext 停止时发布。通过使用ConfigurableApplicationContext 接口上的close() 方法停止ApplicationContext 时发布。 这里,"已停止"表示所有生命周期bean都会收到明确的停止信号。 可以通过start() 调用重新启动已停止的上下文。 |
ContextClosedEvent | 当 ApplicationContext 关闭时发布。通过使用ConfigurableApplicationContext 接口上的close() 方法关闭ApplicationContext 时发布。 这里, "关闭" 意味着所有单例bean都被销毁。 封闭的环境达到其寿命终结。 它无法刷新或重新启动。 |
RequestHandledEvent | 一个特定于Web的事件,告诉所有bean已经为HTTP请求提供服务。 请求完成后发布此事件。 此事件仅适用于使用Spring的DispatcherServlet的Web应用程序。 |
ServletRequestHandledEvent | RequestHandledEvent的一个子类,添加了Servlet的特定上下文信息 |
自定义事件
继承ApplicationEvent
以自定义事件,通过调用ApplicationEventPublisher
的publishEvent()
方法发布事件,通常,通过创建一个实现 ApplicationEventPublisherAware
接口的类并将其注册为 bean 来完成的
java
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlackListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
java
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 content) {
if (blackList.contains(address)) {
publisher.publishEvent(new BlackListEvent(this, address, content));
return;
}
// send email...
}
}
接收自定义事件,事件侦听器会同步接收事件。publishEvent()`方法将阻塞,直到所有侦听器都已完成对事件的处理
java
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...
}
}
基于注解的事件监听@EventListener
,若返回一个事件则相当于发布了一个事件(异步监听器@Async
不支持),可通过@Order
指定监听顺序
java
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...
}
}
2 资源
Resource
资源抽象接口封装资源的操作,ApplicationContext
的部分子类构造函数上接收一个参数用于创建Resource,如ClassPathXmlApplicationContext
2.1 内置实现
2.1.1 UrlResource
UrlResource
包装了 java.net.URL,访问任何通常可以通过 URL 访问的对象,如:
- file:用于访问文件系统路径
- https:用于通过 HTTPS 协议访问资源
- ftp:用于通过 FTP 访问资源
- 以及其他类型
java
public class UrlResourceTest {
public static void main(String[] args) throws Exception {
UrlResource urlResource = new UrlResource("file:D:\\学习\\study-code\\spring\\Spring-5.3.9\\src\\main\\resources\\微信图片_20240902182123.jpg");
System.out.printf("description: %s \n", urlResource.getDescription());
System.out.printf("filename: %s \n", urlResource.getFilename());
System.out.println("available:" + urlResource.getInputStream().available());
}
cmd
description: URL [file:D:/学习/study-code/spring/Spring-5.3.9/src/main/resources/微信图片_20240902182123.jpg]
filename: 微信图片_20240902182123.jpg
available:55754
2.1.2 ClassPathResource
ClassPathResource
从类路径(classpath)中获取资源,它使用线程上下文类加载器、指定的类加载器或指定的类来加载资源,打包后资源位于JAR内,只能通过流读取,不可用 File
操作
java
public class ClassPathResourceTest {
public static void main(String[] args) throws Exception {
//加载类路径下的资源
ClassPathResource classPathResource1 = new ClassPathResource("微信图片_20240902182123.jpg");
System.out.println("available:" + classPathResource1.getInputStream().available());
}
}
cmd
available:55754
2.1.3 FileSystemResource
封装对java.io.File
的处理
java
public class FileSystemResourceTest {
public static void main(String[] args) throws Exception {
//以/开头表示绝对路径,否则表示相对路径
FileSystemResource resource = new FileSystemResource("D:\\学习\\study-code\\spring\\Spring-5.3.9\\src\\main\\resources\\微信图片_20240902182123.jpg");
System.out.printf("description: %s \n", resource.getDescription());
System.out.printf("filename: %s \n", resource.getFilename());
System.out.println("available:" + resource.getInputStream().available());
}
}
cmd
description: file [D:\学习\study-code\spring\Spring-5.3.9\src\main\resources\微信图片_20240902182123.jpg]
filename: 微信图片_20240902182123.jpg
available:55754
2.1.4 PathResource
封装对java.nio.file.Path
的处理
java
public class PathResourceTest {
public static void main(String[] args) throws Exception {
PathResource resource = new PathResource("D:\\学习\\study-code\\spring\\Spring-5.3.9\\src\\main\\resources\\微信图片_20240902182123.jpg");
System.out.printf("description: %s \n", resource.getDescription());
System.out.printf("filename: %s \n", resource.getFilename());
System.out.println("available:" + resource.getInputStream().available());
}
}
cmd
description: path [D:\学习\study-code\spring\Spring-5.3.9\src\main\resources\微信图片_20240902182123.jpg]
filename: 微信图片_20240902182123.jpg
available:55754
2.1.5 ServletContextResource
2.1.6 InputStreamResource
封装一打开的InputStream
,只能读取一次
java
public InputStream getInputStream() throws IOException, IllegalStateException {
if (this.read) {
throw new IllegalStateException("InputStream has already been read - " +
"do not use InputStreamResource if a stream needs to be read multiple times");
}
this.read = true;
return this.inputStream;
}
2.1.7 ByteArrayResource
封装字节数组,每次读取时创建InputStream
2.2 ResourceLoader
加载Resource
的策略接口,ApplicationContext
接口扩展了此接口的功能
java
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
ClassPathXmlApplicationContext
返回ClassPathResource
FileSystemXmlApplicationContext
返回FileSystemResource
WebApplicationContext
返回ServletContextResource
可使用前缀如:classpath:
、https:
、file:
等强制使用对应Resource
ResourceLoader
标准实现类为DefaultResourceLoader
可在ApplicationContext
之外使用
java
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContex
java
public class ResourceLoaderTest {
public static void main(String[] args) throws Exception {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource classPathResource = resourceLoader.getResource("classpath:微信图片_20240902182123.jpg");
System.out.println("classpath-available:" + classPathResource.getInputStream().available());
System.out.println("classPathResource的类名:" + classPathResource.getClass().getName());
Resource fileResource = resourceLoader.getResource("file:D:\\学习\\study-code\\spring\\Spring-5.3.9\\src\\main\\resources\\微信图片_20240902182123.jpg");
System.out.println("file-available:" + fileResource.getInputStream().available());
System.out.println("fileResource的类型:" + fileResource.getClass().getName());
}
}
cmd
classpath-available:55754
classPathResource的类名:org.springframework.core.io.ClassPathResource
file-available:55754
fileResource的类型:org.springframework.core.io.FileUrlResource
2.3 ResourcePatternResolver
ResourceLoader
的扩展接口,解析Resource
位置模式的策略接口(如ant路径模式),可解析类路径下的多个资源(classpath*:)
任何标准 ApplicationContext
中的默认 ResourceLoader
实际上都是实现 ResourcePatternResolver
的 PathMatchingResourcePatternResolver
的实例接口
java
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,MessageSource, ApplicationEventPublisher, ResourcePatternResolver
java
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
PathMatchingResourcePatternResolver
是一个独立的实现,可在 ApplicationContext
外部使用
2.4 Resource作为依赖
spring会自动将字符串转为对应的Resource
3 验证、数据绑定和类型转换
3.1 Spring中的Validator
ValidationUtils
工具类可用于校验,错误信息传入Error
中
java
public static class Person {
private String name;
private int age;
//...getter setter
}
public static class PersonValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Person.class.equals(clazz);
}
@Override
public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
3.2 MessageCodesResolver
解析Error
中的错误消息,默认实现为DefaultMessageCodesResolver
3.3 BeanWrapper
封装对Bean的操作,不直接使用,由DataBinder
(在BeanPropertyBindingResult
中,Errors
和BingingResult
的默认实现)和BeanFactory
使用,提供了设置和获取属性值(单独或批量)、获取属性描述符以及查询属性是否可读或可写的功能。支持属性的无限嵌套
java
public interface BeanWrapper extends ConfigurablePropertyAccessor
java
public interface ConfigurablePropertyAccessor extends PropertyAccessor, PropertyEditorRegistry, TypeConverter
PropertyEditorRegistry
用于注册PropertyEditor
3.3.1 设置和获取基本及嵌套属性
name
:与getName()
或isName()
和setName(..)
方法对应的属性name
account.name
:getAccount().setName()
或getAccount().getName()
方法对应的属性account
中的嵌套属性name
accont[2]
:索引属性account
的第三个元素。索引属性可以是array
类型、list
类型,或其他自然排序的集合account[COMPANYNAME]
:account
Map
属性中的COMPANYNAME
关键索引的映射条目的值
java
public class BeanWrapperTest {
public static void main(String[] args) {
BeanWrapper company = new BeanWrapperImpl(new Company());
//设置company name
company.setPropertyValue("name", "Some Company Inc.");
//等同于
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);
//创建managingDirector并绑定到company
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());
//设置managingDirector的salary属性
company.setPropertyValue("managingDirector.salary", 100000f);
//通过company获取managingDirector的salary属性
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
}
private static class Company {
private String name;
private Employee managingDirector;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Employee getManagingDirector() {
return this.managingDirector;
}
public void setManagingDirector(Employee managingDirector) {
this.managingDirector = managingDirector;
}
}
private static class Employee {
private String name;
private float salary;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
}
}
3.3.2 内置PropertyEditor实现
Spring 使用 PropertyEditor
的概念来实现 Object
和 String
之间的转换,可以自定义 java.beans.PropertyEditor
的编辑器,在 BeanWrapper
上注册自定义编辑器,或者在特定的 IoC 容器中注册。
在XML的属性值或MVC框架解析HTTP请求的参数是由PropertyEditor
完成的
PropertyEditorRegistrySupport
内置了默认的PropertyEditor
注册自定义PropertyEditor
到ApplicationContext
- 使用
ConfigurableBeanFactory
接口的registerCustomEditor()
方法 - 使用
CustomEditorConfigurer
- 使用
PropertyEditorRegistrar
,将PropertyEditor
注册到PropertyEditorRegistry
java
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
//可以注册多个PropertyEditor...
}
}
3.4 类型转换
通用类型转换系统,作为 PropertyEditor
实现的替代方案,Spring容器用来绑定Bean属性值,SpEL和DataBinder使用
3.4.1 Converter
java
@FunctionalInterface
public interface Converter<S, T> {
T convert(S source);
}
s为源类型,T为目标类型
3.4.2 ConverterFactory
java
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
Converter
的工厂类,T为R的子类,根据传入的targetType获取对应的Converter
3.4.3 GenericConverter
java
package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
更复杂更灵活的转换接口,可以转换多个源、目标类型,源、目标类型成对在getConvertibleTypes()
方法中(源、目标类型封装为ConvertiblePair
)
ConditionalGenericConverter
在满足条件时运行
java
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
3.4.4 ConversionService
封装类型转换逻辑统一的API,大多数 ConversionService
实现也实现了 ConverterRegistry
,它提供了用于注册转换器,应用程序启动时实例化,可以注入到Bean中使用
GenericConversionService
是一个通用实现,适用于大多数环境,内部有两个适配器类ConverterAdapter
、ConverterFactoryAdapter
,用于将Converter
、ConverterFactory
转为GenericConverter
spring中的默认ConversionService
,实例化类型为GenericConversionService
,可以调用setConverters()
方法补充自定义转换器
java
public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean
3.5 spring字段格式化
3.5.1 Formatter
格式化日期和数字
java
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
3.5.2 注解形式的格式化
实现AnnotationFormatterFactory
接口,将注解与Formatter
绑定
java
public interface AnnotationFormatterFactory<A extends Annotation> {
//返回注解可使用的字段类型
Set<Class<?>> getFieldTypes();
Printer<?> getPrinter(A annotation, Class<?> fieldType);
Parser<?> getParser(A annotation, Class<?> fieldType);
}
例:@NumberFormat
格式化Number
字段,如Double
和 Long
,@DateTimeFormat
格式化 java.util.Date
、 java.util.Calendar
、 Long
(用于毫秒时间戳)以及 JSR-310 java.time
3.5.3 FormatterRegistry
注册格式化和转换器
3.5.4 FormatterRegistrar
向FormatterRegistry
注册Formatter
3.6 java的Bean的校验
更多信息见Bean Validation Hibernate Validator
LocalValidatorFactoryBean
同时实现 javax.validation.ValidatorFactory
和 Validator
以及 Spring 的 org.springframework.validation.Validator
,Spring在LocalValidatorFactoryBean
中配置了默认的SpringConstraintValidatorFactory
(ConstraintValidatorFactory
的子类)创建ConstraintValidator
实例
可注入javax.validation.Validator
进行手动校验
自定义
- 注解加上
@Constraint
- 实现接口
ConstraintValidator
4 SpEL
4.1 评估
常规使用是提供一个表达式,再将该表达式根据特定的根对象进行评估
基础使用,getValue()指定了转换类型时,原理是通过Converter接口转换
java
public class SpElTest {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
//表达式为字符串字面量
Expression expression = parser.parseExpression("'Hello World'");
String msg = (String) expression.getValue();
System.out.println("打印字符:" + msg);
//调用字符串字面量的方法
Expression upperCasexpression = parser.parseExpression("'Hello World'.toUpperCase()");
String upperCaseMsg = (String) upperCasexpression.getValue();
System.out.println("转为大写字符:" + upperCaseMsg);
//调用字符串字面量的属性并获取长度
Expression lenExpression = parser.parseExpression("'Hello World'.bytes.length");
Integer len = (Integer) lenExpression.getValue();
System.out.println("获取字符长度:" + len);
//通过String的构造函数创建字符串并调用转大写方法,getValue()指定了转换类型,原理是通过Converter接口转换
String constructMsg = parser
.parseExpression("new String('Hello World').toUpperCase()")
.getValue(String.class);
System.out.println("String构造函数的创建的字符串并转为大写:" + constructMsg);
}
}
cmd
打印字符:Hello World
转为大写字符:HELLO WORLD
获取字符长度:11
String构造函数的创建的字符串并转为大写:HELLO WORLD
4.1.1 EvaluationContext
评估表达式时的上下文,Spring提供的实现:
SimpleEvaluationContext
仅支持 SpEL 语言语法的子集。不包括 Java 类型引用、构造函数和 bean 引用StandardEvaluationContext
4.1.2 SpelParserConfiguration
配置ExpressionParser
表达式解析器,SpelCompilerMode
指定编译器模式,评估过程中编译器生成一个java类,模式如下:
OFF
(默认):编译器关闭IMMEDIATE
:通常在第一次评估滞后MIXED
:随着时间推移在解释和编译模式之间切换
4.2 Bean定义中的表达式
#{}
设置Bean的属性,可以通过Bean的名称获取其属性
4.3 参考
更多信息参考docs.spring.io/spring-fram...
数组和列表可通过下标获取,map根据键名获取
java
// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
context, tesla, String.class);
// evaluates to "Idvor"
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
societyContext, String.class);
可以使用特殊的 T
操作符来指定 java.lang.Class
(类型)的实例。静态方法也可以通过使用此操作符来调用, java.lang
包内对类型的 T()
引用不需要完全限定
在EvaluationContext
通过setVariable
方法设置变量,在表达式中通过#{variableName}
引用
5 Spring的面向切面编程
由于 Spring 的 AOP 框架基于代理,目标对象内的调用不会被拦截。对于 JDK 代理,只有代理上的公共接口方法调用可以被拦截。使用 CGLIB 时,代理上的公共和受保护方法调用会被拦截,通过 this
调用时,而 this
指向的是目标对象本身,而非代理对象
5.1 概念
Spring AOP仅支持在方法或类上执行连接点,更多的拦截功能使用AspectJ
切面
跨越多个类的关注点的模块化,例如实现切面的类
连接点
程序执行过程中的一个点,例如方法执行或异常处理
切点
匹配连接点的断言,用于确定哪些连接点会被织入通知
通知
切面在连接点采取的操作,类似拦截器
- 前置通知(Before):连接点之前运行
- 后置通知(After):连接点之后运行
- 返回通知(After Returning):连接点正常完成之后运行
- 异常通知(After Throwing):连接点抛出异常时运行
- 环绕通知(Around):连接点之前之后都会运行,可以控制连接点是否执行
引入
允许在不修改目标类代码的情况下,为现有类添加新的方法或字段
目标对象
被一个或多个切面通知的对象,该对象为代理对象
AOP代理
JDK动态代理或CGLB代理的对象
织入
将切面和目标对象连接起来,创建代理对象的过程
5.2 @Aspect
该注解在aspectjweaver
包中
xml
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
或使用
xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.9</version>
</dependency>
启用注解需要添加@EnableAspectJAutoProxy
,proxyTargetClass
属性表示使用CGLB代理,exposeProxy
为true时,将代理对象暴露到AOP 上下文,可在目标对象内通过AopContext.currentProxy()
获取代理对象
java
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
或使用XML的元素
xml
<aop:aspectj-autoproxy/>
自定义一个切面,添加注解@Aspect
,并将该Bean注入到Spring容器中
切点,在返回类型为void
的方法上使用@Pointcut
注解,定义的切点方法应为空方法
java
@Pointcut("execution(* transfer(..))") //切点表达式
private void anyOldTransfer() {} //切点的签名方法
常用切点指示符
execution
匹配方法执行的连接点within
匹配指定类的内部方法this
匹配实现了指定接口的代理对象target
匹配实现了指定接口的目标对象args
匹配参数实现了指定类的方法@target
匹配有指定注解的类@args
匹配有指定注解的方法@within
匹配带有指定注解的类的方法@annotation
匹配带有指定注解的方法
基本语法,支持正则,其中?
表示可选
plaintext
execution(修饰符? 返回类型 类限定名.方法名(参数) 异常?)
- 修饰符 :可选,如
public
、protect
。 - 返回类型 :必填,如
void
、String
。 - 参数 :使用
..
表示任意个参数,*
表示单个任意参数。
可以使用 &&,
||
和 !
组合切入点表达式
java
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")//通过方法名称引用
private void tradingOperation() {}
-
@Before
-
@AfterReturning
returning属性名称必须与方法中的参数名对应 -
@AfterThrowing
throwing属性名称必须与方法中的参数名对应 -
@After
-
@Around
方法第一个参数必须是ProceedingJoinPoint
,返回的值为为方法的返回值,若为void
则对应null
上述注解中的
argNames
若编译时带有 -parameters 参数,则编译时参数名会保存在字节码中,argNames可以省略,JoinPoint
内置方法如下 -
getArgs()
: 返回方法参数 -
getThis()
: 返回代理对象 -
getTarget()
: 返回目标对象 -
getSignature()
: 返回正在被通知的方法的描述 -
toString()
: 打印正在被建议的方法的有用描述。
第一个参数可以声明 org.aspectj.lang.JoinPoint
类型,如下的示例中的args
,绑定值按照方法顺序绑定,也可替换为@annotation
获取注解(也可换为this
、target
、@within
、@target
、@args
)
java
public class AOPTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.pht.aop");
TestBean bean = context.getBean(TestBean.class);
bean.testMethod("test 参数");
bean.testMethod("参数1", "参数2");
context.close();
}
}
@EnableAspectJAutoProxy
@Component
@Aspect
class AOPAspect {
// 定义一个切点,匹配TestBean类中返回值为任意类型、参数为任意数量的testMethod方法
@Pointcut("execution(* com.pht.aop.TestBean.testMethod(..))")
public void pointcut() {
}
// 前置通知,匹配pointcut定义的切点
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
System.out.println("前置通知,方法:" + joinPoint.getSignature().getName());
}
//返回任意类型,参数类型为String的testMethod方法
@Before("execution(* com.pht.aop.TestBean.testMethod(String))")
public void before2(JoinPoint joinPoint) {
System.out.println("前置通知2,方法:" + joinPoint.getSignature().getName());
}
/*
*返回任意类型,参数类型为任意数量的testMethod方法,
*如果使用参数名代替args中参数类型,则会传递值到通知方法中的参数中,
*匹配参数类型与方法中参数类型一致的方法
*/
@Before("execution(* com.pht.aop.TestBean.testMethod(..)) && args(name)")
public void before3(JoinPoint joinPoint, String name) {
System.out.println("前置通知3,参数:" + name);
}
/*
*如果使用参数名代替args中参数类型,则会传递值到通知方法中的参数中,
*args必须指定且与方法中的参数一致
*匹配参数类型与方法中参数类型一致的方法
* 若编译时带有 -parameters 参数,则编译时参数名会保存在字节码中,argNames可以省略
*/
@Pointcut(value = "execution(* com.pht.aop.TestBean.testMethod(..))&&args(name,value)", argNames = "name,value")
public void pointcut1(String name, String value) {
}
/*
*如果使用参数名代替args中参数类型,则会传递值到通知方法中的参数中,
*argNames必须指定且与方法中的参数一致,JoinPoint可省略
* 若编译时带有 -parameters 参数,则编译时参数名会保存在字节码中,argNames可以省略
*/
@Before(value = "pointcut1(name,value)", argNames = "joinPoint,name,value")
public void before4(JoinPoint joinPoint, String name, String value) {
System.out.println("前置通知4,参数:" + name);
System.out.println("前置通知4,参数2:" + value);
}
@AfterReturning(pointcut = "pointcut()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
System.out.println("返回通知,方法:" + joinPoint.getSignature().getName());
System.out.println("返回通知,结果:" + result);
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知开始,方法:" + joinPoint.getSignature().getName());
Object result = joinPoint.proceed();
System.out.println("环绕通知结束,方法:" + joinPoint.getSignature().getName());
return result;
}
}
@Component
class TestBean {
public String testMethod(String name) {
System.out.println("原方法被调用");
return "test result";
}
public String testMethod(String name, String value) {
System.out.println("两个参数的原方法被调用");
return "two parameters method";
}
cmd
环绕通知开始,方法:testMethod
前置通知,方法:testMethod
前置通知2,方法:testMethod
前置通知3,参数:test 参数
原方法被调用
返回通知,方法:testMethod
返回通知,结果:test result
环绕通知结束,方法:testMethod
环绕通知开始,方法:testMethod
前置通知,方法:testMethod
前置通知4,参数:参数1
前置通知4,参数2:参数2
两个参数的原方法被调用
返回通知,方法:testMethod
返回通知,结果:two parameters method
环绕通知结束,方法:testMethod
对同一个连接点,两个切面的通知顺序可由org.springframework.core.Ordered
或@Ordered
指定,值越低,优先级越高
接口ParameterNameDiscoverer
用于解析方法参数
5.3 引入
允许切面为被通知的对象实现指定接口,并为这些对象提供具体的实现
@DeclareParents
注解
value
类型匹配模式defaultImpl
指定默认的实现
java
public class DeclareParentsTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.pht.aop");
UserService userService = context.getBean(UserService.class);
userService.createUser("张三");
if (userService instanceof Loggable) {
Loggable loggable = (Loggable) userService;
loggable.log("UserService 实现了 Loggable.");
} else {
System.out.println("UserService 未实现 Loggable.");
}
}
}
interface Loggable {
void log(String message);
}
class LoggableImpl implements Loggable {
@Override
public void log(String message) {
System.out.println("[LOG] " + message);
}
}
//被通知的类
@Service
class UserService {
public void createUser(String username) {
System.out.println("创建用户: " + username);
}
}
@EnableAspectJAutoProxy
@Aspect
@Component
class LoggableIntroductionAspect {
// @DeclareParents 注解用于引入接口
// value:指定目标类的匹配模式(UserService及其子类)
// defaultImpl:指定接口的默认实现类
@DeclareParents(value = "com.pht.aop.UserService+", defaultImpl = LoggableImpl.class)
public Loggable loggable; //引入的接口实例
}
也可以使用 org.springframework.aop.aspectj.annotation.AspectJProxyFactory
类为目标对象创建代理
5.4 基于XML的AOP
见官网
5.5 AOP的APIS
5.5.1 Pointcut
org.springframework.aop.Pointcut
,子类AspectJExpressionPointcut
用于解析Aspecth切点表达式
java
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
ClassFilter
用于判断类是否匹配,MethodMatcher
用于判断方法是否匹配,MethodMatcher
的两个参数的方法matches()
在创建AOP代理时执行,若返回true
且isRuntime()
也为true
,则方法在每次调用时都会执行三个参数的matches()
方法动态判断。
在AbstractAutoProxyCreator
类中的方法postProcessBeforeInstantiation()
中,调用的getAdvicesAndAdvisorsForBean()方法
会调用Pointcut
的getClassFilter()
方法和getMethodMatcher()
方法判断切点是否匹配类上的方法
java
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
...
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
//判断切点是否匹配类上的方法
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
//创建代理对象,以及需要通知的方法
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return null;
}
具体的使用在AopUtils.canApply()
方法中
java
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
return true;
}
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
Set<Class<?>> classes = new LinkedHashSet<>();
if (!Proxy.isProxyClass(targetClass)) {
classes.add(ClassUtils.getUserClass(targetClass));
}
classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
for (Class<?> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if (introductionAwareMethodMatcher != null ?
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
5.5.2 通知
环绕通知 org.aopalliance.intercept.MethodInterceptor
前置通知 BeforeAdvice
异常通知 ThrowsAdvice
返回通知 AfterReturningAdvice
通知的具体调用都是通过实现MethodInterceptor
调用对应Advice
ThrowsAdvice
必须实现public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
方法,该方法通过反射调用,前三个参数是可选的,可以为一个或四个参数,具体调用由ThrowsAdviceInterceptor
实现
5.5.3 Advisor
将切点表达式与通知关联,以实现对要拦截的方法进行通知
java
public class MethodInterceptorTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
TargetClass target = context.getBean(TargetClass.class);
target.targetMethod();
context.close();
}
}
class CustomMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("前置拦截: " + invocation.getMethod().getName());
Object result = invocation.proceed();
System.out.println("后置拦截: " + invocation.getMethod().getName());
return result;
}
}
class TargetClass {
public void targetMethod() {
System.out.println("执行目标方法");
}
}
@EnableAspectJAutoProxy
@Configuration
class TestConfiguration {
@Bean
public CustomMethodInterceptor customMethodInterceptor() {
return new CustomMethodInterceptor();
}
@Bean
public TargetClass targetClass() {
return new TargetClass();
}
@Bean
public AspectJExpressionPointcutAdvisor aspectJExpressionPointcutAdvisor() {
AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
advisor.setExpression("execution(* com.pht.aop.TargetClass.targetMethod(..))");
advisor.setAdvice(customMethodInterceptor());
return advisor;
}
}
cmd
前置拦截: targetMethod
执行目标方法
后置拦截: targetMethod
5.5.4 ProxyFactoryBean创建代理对象
目标类若实现了接口,则创建JDK代理,否则创建CGLB代理
org.springframework.aop.framework.ProxyFactoryBean
为目标bean手动设置代理对象
属性名 | 作用 |
---|---|
target |
指定被代理的目标对象。 |
proxyInterfaces |
指定代理需要实现的接口(仅 JDK 代理需要)。 |
interceptorNames |
指定拦截器 Bean 的名称(如org.aopalliance.intercept.MethodInterceptor , Advisor , Advice , SingletonTargetSource )。若为* 则匹配所有拦截器,或使用global* 匹配前缀相同的拦截器 |
proxyTargetClass |
是否使用 CGLIB 代理目标类(true 时),默认为 false (JDK 代理)。 |
optimize |
是否优化代理,启用 CGLIB 代理的优化策略。 |
exposeProxy |
是否将代理暴露到 ThreadLocal 中,允许通过 AopContext.currentProxy() 访问。 |
java
public class ProxyFactoryBeanTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProxyFactoryBeanConfig.class);
TargetProxyClass target = context.getBean(TargetProxyClass.class);
target.myMethod();
context.close();
}
}
@Configuration
class ProxyFactoryBeanConfig {
@Bean
public ProxyFactoryBean targetProxyClass() {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
//设置拦截对象
proxyFactoryBean.setTarget(new TargetProxyClass());
//设置拦截器,拦截器为容器的Bean的id
proxyFactoryBean.setInterceptorNames("myMethodInterceptor", "beforeAdvice");
return proxyFactoryBean;
}
@Bean
public MethodInterceptor myMethodInterceptor() {
return invocation -> {
System.out.println("前置拦截: " + invocation.getMethod().getName());
Object result = invocation.proceed();
System.out.println("后置拦截: " + invocation.getMethod().getName());
return result;
};
}
@Bean
public MethodBeforeAdvice beforeAdvice() {
return (method, args, target) -> {
System.out.println("前置拦截1: " + method.getName());
};
}
}
class TargetProxyClass {
public void myMethod() {
System.out.println("执行目标方法");
}
}
cmd
前置拦截: myMethod
前置拦截1: myMethod
执行目标方法
后置拦截: myMethod
5.5.5 ProxyFactory创建代理对象
org.springframework.aop.framework.ProxyFactory
java
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
5.5.6 自动创建代理对象
BeanNameAutoProxyCreator
代理匹配属性beanNames
的Bean,通过指定interceptorNames
属性设置拦截器或通知,
DefaultAdvisorAutoProxyCreator