使用工厂中方法代替new形式创建对象的一种设计模式
一种思想,用于消减代码间的耦合。
实现思想:利用工厂设计模式,把创建对象代码从具体类中剥离出来,交由工厂完成,从而降低代码间依赖关系。
耦合分类:
1 内容耦合(最高程度耦合)
当一个模块直接修改 或 操作另一个模块数据时,或一个模块不通过正常入口而转入另一个模块
2 公共耦合
两个 或 两个以上模块共同引用一个全局数据项
3 外部耦合
一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息7
4 控制耦合
一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作
5 标记耦合
若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间存在一个标记耦合。
6 数据耦合(最低程度耦合)
模块之间通过参数来传递数据
7 非直接耦合
两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
避免内容耦合、少用控制耦合、限制公共耦合的范围(解耦部分)
耦合分类代码举例
spring框架IOC具体实现。
框架把持久层对象传入业务层
spring3.0后引入,表示当前类是spring的一个配置类,作用代替spring的application.xml。其本质为@Component
属性: value:用于存入spring的ioc容器中Bean的id
在构造ioc容器使用传入字节码的构造函数,则此注解可省略
//传入被注解的类的字节码方式,@Configuration注解可省略
AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//获取对象
SpringConfiguration springConfiguration = config.getBean(SpringConfiguration.class);
//执行
System.out.println(springConfiguration);
若使用传入包的构造函数,注解不可省略
//传入扫描包方式
//创建容器
AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext("config");
//获取对象
SpringConfiguration springConfiguration = config.getBean(SpringConfiguration.class);
//执行
System.out.println(springConfiguration);
属性包括有:
value、basePackages : 表示扫描service包
basePackageClasses:表示扫描UserService类所在包及其子包
如果只有@ComponentScan注解,不加属性,则表示扫描 该注解被修饰的类所在包及其子包
自定义beanName生成器
public class CustomBeanNameGenerator implements BeanNameGenerator {
private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
//定义BeanName
String beanName = null;
//判断当前bean的定义信息是否是注解形式
if (definition instanceof AnnotatedBeanDefinition){
//definition转为注解的bean定义信息
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition)definition;
//获取注解Bean定义的元信息
AnnotationMetadata metadata = annotatedBeanDefinition.getMetadata();
//获取定义信息中的所有注解
Set<String> types = metadata.getAnnotationTypes();
for (String type : types) {
//获取注解属性
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(type, false));
//判断attributes是否为null,且必须是@Component及其衍生注解
if (attributes != null && isStereotypeWithNameValue(type, metadata.getMetaAnnotationTypes(type), attributes)) {
//获取value属性的值
Object value = attributes.get("value");
//判断value是否为String类型
if (value instanceof String) {
String strVal = (String) value;
//判断value是否有值
if (StringUtils.hasLength(strVal)) {
if (beanName != null && !strVal.equals(beanName)) {
throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
"component names: '" + beanName + "' versus '" + strVal + "'");
}
beanName = strVal;
}
}
}
}
}
//如果beanName为空,则创建默认BeanName
return beanName !=null? "selfDIY_"+beanName :"selfDIY_"+buildDefaultBeanName(definition);
}
private boolean isStereotypeWithNameValue(String annotationType,
Set<String> metaAnnotationTypes, @Nullable Map<String, Object> attributes) {
boolean isStereotype = annotationType.equals(COMPONENT_ANNOTATION_CLASSNAME) ||
metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) ||
annotationType.equals("javax.annotation.ManagedBean") ||
annotationType.equals("javax.inject.Named");
return (isStereotype && attributes != null && attributes.containsKey("value"));
}
private String buildDefaultBeanName(BeanDefinition definition) {
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
String shortClassName = ClassUtils.getShortName(beanClassName);
return Introspector.decapitalize(shortClassName);
}
}
scopeResolver:用于处理并转换检测到的Bean作用范围
scopeProxy:用于指定bean生成时的代理方式(默认Default不使用代理)
用于指定符合组件检测条件的类文件,默认:**/*.class
(表示当前包及其子包下的任意类名字节码)
@Component(includeFilters/excludeFilters = @Component.Filter(type = FilterType.ANNOTATION,classes = xxx.class))
FilterType
过滤类型:
ANNOTATION:注解类型过滤规则
ASSIGNABLE_TYPE:特定类型过滤规则
ASPECTJ:aspectj表达式指定过滤规则
REGEX:正则表达式过滤规则
CUSTOM:自定义类型过滤规则
includeFilters/excludeFilters
包含哪些注解/排除哪些注解
自定义类型过滤规则
在内置注解value值相同冲突的情况下,自定义注解来进行过滤区分
Spring配置:
类型为自定义类型过滤规则,对应自定义过滤器类
自定义注解:
//用于定义区域的注解
@Retention(RetentionPolicy.RUNTIME)//描述其声明周期
@Target(ElementType.TYPE)//用于描述类、接口(含注解类型)、enum声明
public @interface District {
//用于指定区域名称
String value();
}
自定义扫描过滤器类:
//自定义扫描规则过滤器
public class DistrictTypeFilter extends AbstractTypeHierarchyTraversingFilter {
//定义路径校验对象
private PathMatcher pathMatcher;
//定义区域名称
//此处不能使用@Value读取properties配置文件内容,
//因负责填充属性值的InstantiationAwareBeanPostProcessor与TypeFilter实例创建 不一回事
private String districtName;
//重写默认构造函数
public DistrictTypeFilter(){
//调用父类构造函数,
//第一个参数表 是否考虑基类信息
//第二个参数表,是否考虑接口上的信息
super(false,false);
//借助Spring的默认Resource通配符路径方式
pathMatcher = new AntPathMatcher();
//读取配置文件(硬编码)
try{
Properties properties = PropertiesLoaderUtils.loadAllProperties("district.properties");
//给districtName赋值
districtName = properties.getProperty("district.name");
} catch (IOException e) {
e.printStackTrace();
}
}
//本类将注册为Exclude,返回true表拒绝.
@Override
protected boolean matchClassName(String className) {
//判断是否在指定包下的类(只处理和区域相关的业务类)
if(!isPotentialPackageClass(className)){
return false;//不符合路径规则
}
try {
//判断当前区域 与 配置区域是否一致,不一致则不能注册到Spring的IOC容器中
Class<?> c = ClassUtils.forName(className,DistrictTypeFilter.class.getClassLoader());
//获取District注解
District district = c.getAnnotation(District.class);
//判断是否有此注解
if (district == null) return false;
//取出注解的属性
String districtValue = district.value();
//校验,如果取出Value属性值和配置文件中提供值一致,则注册到ioc容器,返回true
return (!districtName.equalsIgnoreCase(districtValue));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
//定义可处理类的类名,指定package下的
private static final String PATTERN_STANDARD = ClassUtils.convertClassNameToResourcePath("wsw.service.*.*");
//本类逻辑中可处理的类
private boolean isPotentialPackageClass(String className){
//将类名转换成为资源路径,以匹配是否符合扫描条件
String path = ClassUtils.convertClassNameToResourcePath(className);
//校验
return pathMatcher.match(PATTERN_STANDARD,path);
}
}
district.properties配置文件:
#自定义注解中的value值
district.name= north
支持写在方法、注解上
是否支持按类型注入(5.1版本后autowire属性过时),即配置完后,是否可以让别人在引用时使用Autowired注解
@Resource(name ="dataSource")
private DataSource dataSource;
//创建数据源对象
//autowireCandidate属性默认为true,即别人引用时自动使用 自动类型注入.为false时通过外部定义变量
@Bean(value = "dataSource",autowireCandidate = false)
public DataSource createDataSource(){
return new DriverManagerDataSource();
}
//创建JDBC模板对象
@Bean("jdbcTemplate")
public JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(dataSource);
}
当属性设置为false时,不可通过@Autowired进行自动类型匹配注入
需要定义外部变量来传入
初始化方法属性:一般不使用,可通过编程方式在创建bean对象时,实例化对象,然后写初始化方法,然后返回实例化对象,如下:
销毁方法,必须不带参数,可能会抛出异常
若在创建bean的时候,未定义,则以方法名作为bean的id存入ioc容器中。如果存在重载方法(有参),则默认执行有参数的重载方法。
无参方法中的dataSource 通过外部定义的变量传入
被修饰的自定义注解 也会被解析 注入容器中,若未定义name、value属性值,则默认使用@Bean的方法,将被修饰的方法名作为注入容器的唯一id标识。
该注解写在类上,通常和注解驱动的配置类一起使用,作用:引入其他配置类.
从配置只需关心类内部的代码逻辑实现即可
通过@import导入 从属配置类,对应容器中的名称 默认为其类字节码的全限定类名!!!
在SpringBoot中,@EnableXXX注解,绝大多数借助ImportSelector或者ImportBeanDefinitionRegistrar。
在Spring中,@EnableTransactionManagement借助ImportSelector,
@EnableAspectJAutoporxy 借助ImportBeanDefinitionRegistrar
共同点:
用于动态注册bean对象到容器中,支持大批量bean导入
区别:
ImportSelector ,手动编写实现类,返回要注册的bean的全限定类名数组(String[ ] ),执行ConfigurationClassParser类中的processImports方法注册bean
ImportBeanDefinitionRegistrar,也需手动编写实现类,在实现类中手动注册bean至容器中
默认通过@ComponentScan来指定所在包及子包下所有组件的扫描,
通过自定义ImportSelector,可不用@ComponentScan,通过配置文件来指定要扫描的包下的组件及Config包下。
也可通过配置文件与@ComponentScan,分别指定不同的包及其子包来扫描其中组件(如果配置文件、@Component都没有,则只会扫描@Import所在包)
进一步,不需要写@Component、@Service等这类注解,全部交由自定义导入器来进行导入注册至ioc容器
CustomeImportSelector类:
public class CustomeImportSelector implements ImportSelector {
//AspectJ表达式
private String expression;
//使用者指定的包名
private String packageName;
//通过默认构造函数,
//读取配置文件,给expression赋值
public CustomeImportSelector(){
try {
//获取properties对象
Properties properties = PropertiesLoaderUtils.loadAllProperties("customerImport.properties");
//给expression赋值
expression = properties.getProperty("importselector.expression");
packageName = properties.getProperty("importselector.package");
}catch (Exception e){
e.printStackTrace();
}
}
/*
* 实现获取要导入类的字节码
* 需求: 导入的过滤规则TypeFilteryong用AspectJ表达的方式
* */
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//1 定义扫描包名称
List<String> basePackages = null;
//2 判断有@Import注解的类上是否存在@ComponentScan注解
if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())){
//若存在
//3 取出@ComponentScan注解属性(basePackages/value)
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
//4 取出basePackages 或 value 属性值
if (attributes.get("basePackages") != null) {
basePackages = new ArrayList<>(Arrays.asList((String[]) attributes.get("basePackages")));
}else if (attributes.get("value") != null){
basePackages = new ArrayList<>(Arrays.asList((String[]) attributes.get("value")));
}
}
//5 判断是否有@Component,是否指定了包扫描信息
if (basePackages == null || basePackages.size() == 0){
String basePackage = null;//用于记录@Import注解出现类所在的包
//6 取出@Import注释的类所在的包
try {
basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//7 把包名填充至basePackages中
basePackages = new ArrayList<>();
basePackages.add(basePackage);
}
//判断用户是否通过配置文件 自定义扫描的包名
if (!StringUtils.isEmpty(packageName)) basePackages.add(packageName);
//8 创建类路径扫描器, 参数表示不使用默认过滤规则
ClassPathScanningCandidateComponentProvider classPathScanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);
//9 创建类型过滤器(此处使用AspectJ表达式类型过滤器)
TypeFilter typeFilter = new AspectJTypeFilter(expression, CustomeImportSelector.class.getClassLoader());
//10 把类型过滤器添加到扫描器中
classPathScanningCandidateComponentProvider.addIncludeFilter(typeFilter);
//11 定义要扫描类的全限定类名集合
Set<String> classes = new HashSet<>();
//12 填充集合内容
for (String basePackage : basePackages) {
classPathScanningCandidateComponentProvider.findCandidateComponents(basePackage).forEach(
beanDefinition -> classes.add(beanDefinition.getBeanClassName())
);
}
//按照返回值要求,返回全限定类名数组
return classes.toArray(new String[classes.size()]);
}
}
配置文件:
#ASPECTJ规则,自定义的package必须符合该规则,且不能包含自定义ImportSelector类所在路径,会出现递归扫描导致栈溢出
importselector.expression = wsw..*
importselector.package = wsw
测试类:
bean的获取通过全限定类名
AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext("Config");
UserService userService = config.getBean("wsw.service.impl.UserServiceImpl", UserServiceImpl.class);
userService.save();
LogUtil logUtil = config.getBean("wsw.util.LogUtil", LogUtil.class);
logUtil.logPrint();
同自定义导入器类似
配置文件一样
CustomeImportBeanDefinitionRegistrar类:
public class CustomeImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
//定义表达式
private String expression;
//使用者自定义包
private String customPackage;
//默认构造器,用于给表达式赋值
public CustomeImportBeanDefinitionRegistrar() {
try {
//读取properties文件,创建properties对象
Properties properties = PropertiesLoaderUtils.loadAllProperties("customerImport.properties");
//给变量赋值
expression = properties.getProperty("importselector.expression");
customPackage = properties.getProperty("importselector.package");
} catch (IOException e) {
e.printStackTrace();
}
}
//实现注册bean的功能(通过扫描指定包实现)
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//1 定义扫描包名称
List<String> basePackages = null;
//2 判断有@Import注解的类上是否存在@ComponentScan注解
if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())){
//若存在
//3 取出@ComponentScan注解属性(basePackages/value)
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
//4 取出basePackages 或 value 属性值
if (attributes.get("basePackages") != null) {
basePackages = new ArrayList<>(Arrays.asList((String[]) attributes.get("basePackages")));
}else if (attributes.get("value") != null){
basePackages = new ArrayList<>(Arrays.asList((String[]) attributes.get("value")));
}
}
//5 判断是否有@Component,是否指定了包扫描信息
if (basePackages == null || basePackages.size() == 0){
String basePackage = null;
//6 取出@Import注释的类所在的包
try {
basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//7 把包名填充至basePackages中
basePackages = new ArrayList<>();
basePackages.add(basePackage);
}
//判断用户是否配置了扫描的包
if (!StringUtils.isEmpty(customPackage)) basePackages.add(customPackage);
//8 创建类路径扫描器, 参数表示不使用默认过滤规则
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry,false);
//9 创建类型过滤器(此处使用AspectJ表达式类型过滤器)
TypeFilter typeFilter = new AspectJTypeFilter(expression, CustomeImportBeanDefinitionRegistrar.class.getClassLoader());
//10 把类型过滤器添加到扫描器中
scanner.addIncludeFilter(typeFilter);
//11 扫描指定的包
scanner.scan(basePackages.toArray(new String[basePackages.size()]));
}
}
测试类:
bean注册默认将类名首字母改为小写(与自定义导入器有区分)
public class TEST {
public static void main(String[] args) {
AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext("Config");
UserService userService = config.getBean("userServiceImpl", UserServiceImpl.class);
userService.save();
LogUtil logUtil = config.getBean("logUtil", LogUtil.class);
logUtil.logPrint();
}
}
用于指定读取资源文件的位置,不仅支持properties,也支持xml文件,并且通过yaml解析器,配合自定义PropertySourceFactory实现解析yml配置文件
属性:
name:资源名称。(不给定的情况下,有默认生成规则)
value:资源路径. 支持类路径写法、也支持文件协议写法
factory:Spring4.3版本后,资源文件解析器
Spring在4.3版本以前,没有PropertySourceFactory,无法实现自动解析。需在配置类注册资源文件解析器的bean到ioc容器中
对于4.3版本以后,使用PropertySourceFactory接口的唯一实现类:DefaultPropertySourceFactory
支持xml文件,支持层级关系的描述,但xml文件存在暗区(图中黑色方框部分),存储会占用内存,传输会额外消耗流量
自定义解析规则,引入第三方yaml文件解析器
导入yaml解析工厂坐标:
<dependency>
<groupId>org.yamlgroupId>
<artifactId>snakeyamlartifactId>
<version>1.23version>
dependency>
YamlPropertySourceFactory.java:
public class YamlPropertySourceFactory implements PropertySourceFactory {
//自定义解析规则,引入第三方yaml文件解析器
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
//创建yaml文件解析工厂
YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
//设置要解析的资源内容
factoryBean.setResources(resource.getResource());
//把资源解析成properties文件
Properties properties = factoryBean.getObject();
//返回PropertySource对象
return (name != null ? new PropertiesPropertySource(name, properties)
: new PropertiesPropertySource(resource.getResource().getFilename(),properties));
}
}
随后就可以解析yml配置文件
注入时机、设定注入条件
用于控制对象创建的时机、顺序,如图,EventSource比EventListener更晚创建
用于指定单例bean对象创建时机。不使用此注解,单例bean的生命周期与容器相同,但使用次注解后,单例对象的创建时机变为第一次使用时创建。
只适用于单例对象,属性value默认为true
自定义条件类WindowsCondition.java:
public class WindowsCondition implements Condition {
//是否注册到ioc容器中的核心方法
@Override//context:ioc上下文对象
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取环境信息(为了取出当前操作系统是windows还是linux)
Environment environment = context.getEnvironment();
//获取当前系统名称
String os = environment.getProperty("OS");
//os = "Linux" ;//测试用
//判断是否包含windows规则
if (os.contains("Windows")) return true;
return false;
/*//获取ioc使用的BeanFactory对象
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//获取类加载器
ClassLoader classLoader = context.getClassLoader();*/
//输出所有系统环境信息
/*StandardEnvironment standardEnvironment = (StandardEnvironment) environment;
Map map = standardEnvironment.getSystemEnvironment();
for (Map.Entry m : map.entrySet()){
System.out.println(m.getKey()+"=>"+m.getValue());
}*/
//获取bean定义信息的注册器
//BeanDefinitionRegistry registry = context.getRegistry();
}
}
用于标明当前运行环境的注解 ,用在配置类上
@ActiveProfiles可以指定当前是什么环境,可用在测试类
来源JSR-250规范
通常用到的属性:
name:按名称匹配
type:按类型匹配
默认按类型匹配,类型和名称都匹配时,必须都符合才可匹配
来源:JSR-330规范(Jcp给出的官方标准反向依赖注入规范)
需先导入对应坐标:
<dependency>
<groupId>javax.injectgroupId>
<artifactId>javax.injectartifactId>
<version>1version>
dependency>
需要配合@Named 规定匹配的名称一起使用
可用在类上、方法上。表示优先注入
@PostConstruct :在对象创建时,需要初始化。则在初始化方法上添加
@PreDestroy:写在对象销毁方法上
多例对象,Spring不知道其生命周期,不知何时用完,所以在创建后不归Spring管理,回收销毁只能交由垃圾回收器
BeanFactory中定义的将近一半是获取bean对象的各种方法,另外就是对bean属性的获取与判定。该接口仅是定义IOC容器的最基本形式,具体实现均交由子类实现
BeanFactory子类之一,相对于BeanFactory增加对父(BeanFactory)的获取,子容器可通过接口方法访问父容器,使容器具备层次性。
该层次性增强了容器的扩展性和灵活性。层次容器特点:只有子容器可感知到父容器,反之不行。
例如SpringMVC,控制层的bean位于子容器中,将业务层、持久层的bean所在容器设置为父容器。
BeanFactory子类之一,该类引入了获取容器中bean的配置信息的若干方法(获取容器中bean的个数,获取容器中所有bean的名称列表,按照目标类型获取bean名称,检查容器中是否包含指定名称的bean等等 )
提供创建bean、自动注入、初始化及应用bean的后置处理器等功能。
Spring提供四种自动注入类型:
byName:根据名字自动装配
byType:根据类型自动匹配
constructor:仅对构造方法注入,类似与byType。如果bean A有一个构造方法,包含B类型的入参。若容器中有B类型的bean,则将其作为入参,如果找不到则抛出异常
autodetect: 根据bean的自省机制决定采用byType还是constructor进行自动装配。如果bean提供默认的构造函数,则采用byType,否则采用constructor
xml配置方式
提供配置Factory的各种方法,增强容器可定制性,定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法。
包含Ioc容器具备的重要功能,是容器完整功能的一个基本实现。
XmlBeanFactory由该类派生出的Factory,只增加了加载XML配置资源的逻辑,容器相关的特性全部由DefaultListableBeanFactory实现
例如:XmlBeanFactory实现,通过将resource资源对象(资源文件路径)传入loadBeanDefinition方法
通过io流将resource对象里的资源拿出,构造w3c提供的inputSource流封装对象。通过真正的执行方法dooadBeanDefinitions解析(Spring框架中真正执行的方法通常以do开头)。
通过doLoadDocument拿到org.w3c.dom对象,解析xml,然后通过registerBeanDefinitions方法里BeanDefinitionDocumentReader的registerBeanDefinitions方法内的doRegisterBeanDefinitions进行注册
除了简单容器所具备的功能外,ApplicationContext还提供许多额外功能,包括:
1 国际化支持:实现了MessageSource接口,提高国际化消息访问功能,支持具备多语言版本需求的应用开发,并提供多种实现简化国际化资源文件的装载与获取
2 发布应用上下文事件:实现ApplicationEventPublisher接口,该接口使容器包括容器启动、关闭事件等功能。如果一个bean需要接收容器事件,只需实现ApplicationListener接口即可。Spring会自动扫描对应监听器配置
3 丰富的资源获取方式:实现ResourcePatternResolver接口,其实现类可采用Ant风格的资源路径去加载配置文件
主要增加refresh、close两个方法,为应用上下文提供启动、刷新、关闭的能力。
refresh是高级容器的核心方法,方法中概括了高级容器初始化的主要流程
需要先导入坐标:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.1.6.RELEASEversion>
dependency>
为WEB应用定制的上下文,可基于WEB容器实现配置文件加载及初始化工作。对于非WEB应用而言,bean只有singleton和prototype两种作用域,而在WebApplicationContext中新增request、session、globalSession及application四种作用域
具体实现类:
AnnotationConfigApplicationContext是基于注解驱动开发的高级容器类,提供AnnotatedBeanDefinitionReader用于读取注解创建Bean的定义信息,ClassPathBeanDefinitionScanner负责扫描指定包获取Bean的定义信息。
ClasspathXmlApplicationContext用于加载类路径下配置文件,FileSystemXmlApplicationContext用于加载文件系统中配置文件(磁盘的绝对路径)。共用一套机制,
Spring源码中体现的是,将bean对象的描述存入到ioc容器中,即BeanDefinition。Spring将各bean的BeanDefinition实例注册记录在BeanDefinitionRegistry中,其实现类SimpleBeanDefinitionRegistry主要以Map作为存储标 。除了包含bean对象,还包含bean对象的一些说明(如是否单例,是否延迟加载,作用范围等)
AbstractBeanDefinition实现类部分继承图:
AnnotationConfigApplicationContext实现类,不仅具备注解类的注册,还具备了beanDefinition的注册.
两个方式,上面的构造函数对应的字节码类型,下面的构造函数是扫描的包
其中,this()方法初始化了一个容器,调用
GenericApplicationContext类(里面提供的一个DefaultListableBeanFactory容器)。
随后,在执行自身的无参默认构造韩式时,创建了一个reader和一个scanner。如果传的是类字节码形式,
则使用register方法把配置类注册到IOC容器中(用到reader);如果传的是包,则通过scan方法进行(用到scanner)。
接着,refresh()方法执行其13个方法。
最后通过AbstractBeanFactory的doGetBean方法实例化获取Bean对象.
给某一对象提供一个代理对象。并由代理对象控制对源对象的引用。
特点:1 间接特点,减少耦合;2 增强特点
面向切面编程,通过预编译方式 和 运行期动态代理实现程序功能的统一维护.(动态代理分别为基于接口、基于子类 )
术语:
开启Spring注解 aop配置的支持
属性:
proxyTargetClass:默认false,使用jdk官方 基于接口创建动态代理对象。为True时,使用子类作为动态代理(基于目标类)
exposeProxy:是否暴露代理对象
用于·配置切面
属性:
value:默认切面类为单例,当切面类为多例时,指定预处理的切入点表达式 .
用法是perthis(切入点表达式).或用@Pointcut修饰的方法名称(全限定名)
两个不同类型的切面中,如果通知类型相同,以类的首字母决定执行顺序
通过@Order,设置value的值来控制执行顺序(bean对象创建时机顺序)
用于指定切入点表达式。此注解就是代替xml中的< aop:pointcut>标签,实现切入点表达式的通用化
属性:
value:指定切入点表达式
argNames:指定切入点表达式的参数。参数可是execution中的,也可是args中的
参数会先被传入到前置通知,再传入切入点,所以两者是同一个地址
参数名称主要看args( )这部分的,argsNames可省略
切入表达式的解析,不是在创建解析bean对象时所做,而是在invokeBeanFactoryPostProcess(执行bean工厂后处理)时所做
@Before、@AfterReturning、@AfterThrowing、@After分别指定增强方法何时执行
@Before 前置通知可获取切入点方法的参数,用于记录日志
@After 最终通知,在切入点方法执行之后 执行(无论方法是否存在异常)
@AfterReturning后置通知 切入点方法正常执行并返回前 执行
@AfterThrowing异常通知 切入点执行产生异常后 执行
同一个切面中,相同类型的通知执行顺序不按照声明的顺序来,是以方法名中每个字母的ASCII码来决定的(越小越优先)
@Around 环绕通知
手动控制增强方法的执行时机,可应用于记录日志
!!! 注意,如果环绕通知没有返回值,但切入点方法有返回值,则切入点方法返回值拿不到
可实现引介的作用,对目标类中引入新的方法
@Component
@Aspect //当前类是一个切面类
public class LogUtil {
//让目标类具备当前声明接口中的方法,采用动态代理
@DeclareParents(value = "wsw.service.UserService+",defaultImpl = ValidateExtensionServiceImpl.class)
private ValidateExtensionService validateExtensionService;
@Pointcut(value = "execution(* wsw.service.impl.*.*(..))")
public void Pointcut(){}
@Before(value = "Pointcut()&&this(validateExtensionService)&&args(user)") //用于配置当前方法为前置通知
public void LogPrint(ValidateExtensionService validateExtensionService, User user){
//将验证加入到前置通知中
boolean isTrue = validateExtensionService.checkUser(user);
if (isTrue){
System.out.println("打印日志功能");
}else throw new IllegalArgumentException("用户昵称包含非法字符");
}
}
@DeclareParents包含两个属性:
value:目标类的全路径
defaultimpl:扩展方法接口的实现类
对UserService及其实现类进行扩展,扩展的方法在ValidateExtensionServiceImpl类内
this(当前增强方法的参数),args(切入点方法的参数)
特定情况下,选择性织入(编译期或类加载期)
属性:
aspectjWeaving:是否开启LTW的支持
ENABLED 开启LTW
DISABLED 不开启LTW
AUTODETECT 如果还类路径下能读取到META-INF/aop.xml文件,则开启LTW,否则关闭
1 首先进行创建cacheKey
2 判断是否已经处理过了
3 判断是否不需要增强
4 判断是否是基础设施类 或 是否是需要跳过的bean
5 获取增强器
6 根据增强器创建代理对象
概念: 遵循特定的语法用于捕获每一个种类的可使用连接点的语法
作用:用于对符合语法格式的连接点进行增强(运行期的增强)
主要种类:
方法执行:execution(MethodSignature)
方法调用:call(MethodSignature)
构造器执行:execution(ConstructorSignature)
构造器调用:call(ConstructorSignature)
类初始化:staticinitializaztion(TypeSignature)
属性读操作:get(FieldSignature)
属性写操作:set(FieldSignature)
例外处理执行:handler(TypeSignature)
对象初始化:initialization(ConstructorSignature)
对象预初始化:preinitialization(ConstructorSignature)
JdbcTemplate主要提供以下五类方法:
1 execute方法,可执行任何Sql语句
2 update、batchUpdate方法,update用于执行新增、修改、删除语句;batchUpdate用于执行批处理相关语句
3 query、queryForXXX方法,用于执行查询相关语句
4 call方法,用于执行存储过程、函数相关语句
queryForList可以封装一个列的值到集合中 或 封装所有名称与值的对应关系到集合中
queryForMap查询记录只有一条 将名称与值对应关系封装其中
queryForRowSet可获取某列的值,遍历输出
操作数据库中大对象类型(二进制大对象、文本大对象)
需要创建个LobHandler对象存入到容器中
使用lobHandler与lobCreator来存储:
public class LobTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private LobHandler lobHandler;
@Test
public void testWrite() throws IOException {
Resource resource = new FileSystemResource("C:\\Users\\Administrator\\Desktop\\1.jpg");
byte[] images = FileCopyUtils.copyToByteArray(resource.getFile());
String des = "````````````````````````````````````````````````";
UserInfo userInfo = new UserInfo();
userInfo.setImages(images);
userInfo.setDes(des);
jdbcTemplate.execute("insert into userinfo(images,des)values(?,?)", new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
@Override
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException, DataAccessException {
lobCreator.setBlobAsBytes(ps,1,userInfo.getImages());
lobCreator.setClobAsString(ps,2,userInfo.getDes());
}
});
}
@Test
public void testRead(){
UserInfo userInfo = jdbcTemplate.queryForObject("select * from userinfo where id = ?", new BeanPropertyRowMapper<UserInfo>(UserInfo.class), 1);
System.out.println(userInfo);
}
}
在sql语句中可以指定参数名称
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class NamedParameterJdbcTempalteTest {
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
@Test
public void testFind(){
Map<String,Object> map = new HashMap<>();
map.put("id",1);
Account account = jdbcTemplate.queryForObject("select * from account where id = :id", map, new BeanPropertyRowMapper<Account>(Account.class));
System.out.println(account);
}
@Test
public void testSave(){
Account account = new Account();
account.setName("new");
account.setMoney(666d);
BeanMap beanMap = BeanMap.create(account);
jdbcTemplate.update("insert into account(name,money) values(:name,:money)",beanMap);
}
}
queryForObject、update语句里参数可指定名称
也叫 政策模式,指对象具备某个行为,但在不同场景中,该行为有不同实现。策略模式面向接口编程思想的具体体现。
此接口是Spring的事务管理器核心接口。
Spring本身不支持事务实现,只负责提供标准,应用底层支持什么样的事务,需提供具体实现类。例如:DataSourceTransactionManager、HibernateTransactionManager等
Spring只是封装,真正实现事务控制离不开connection的三个方法:setAutoCommit 设置事务手动提交、commit、rollback
此接口是Spring中事务可控属性的顶层接口,里面定义了事务的一些属性及获取属性的方法。例如:事务的传播行为、事务的隔离级别、事务的只读、事务的超时等。
此接口是事务运行状态表示的顶层接口,里面定义着获取事务运行状态的一些方法。
配置事务控制配置类
使用@EnableTransactionManagement开启注解事务控制
使用@Transactional修饰要进行事务控制的类或方法
注解驱动开发事务配置的必备注解
属性:
proxyTargetClass:代理方式(CGLIB or jdk官方的proxy)默认为false,使用官方。true时表示实现目标类代理
mode:采用什么样的代理时期(运行期代理 or 类加载期代理)
order:通知优先级
该注解会添加AutoProxyRegistrar、ProxyTransactionManagementConfiguration两个bean对象,从而实现对切入点方法增强,同时创建事务的通知、事务的拦截器transactionInterceptor,由事务拦截器里的invoke方法触发事务的提交与回滚
事务控制必备,可出现在接口上、类上、方法上
接口上:当前接口的所有实现类中重写接口的方法有事务支持
类上:当前类中所有方法有事务支持
方法上:当前方法有事务的支持
优先级:方法上>类上>接口上
TransactionEventListener注解
首先需要事务管理器配置类:
public class TransactionManagerConfig {
@Bean
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
然后自定义一个事件
public class MyApplicationEvent extends ApplicationEvent {
private Object source;
public MyApplicationEvent(Object source) {
super(source);
this.source = source;
}
public Object getSource(){
return source;
}
}
自定义事件监听器,事件对象采用Map类型:
/**
* 事件监听器
*/
@Component
public class MyTransactionEventListener {
/**
* 当事务提交之后执行
* @param event
*/
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void doSomething(MyApplicationEvent event){
//从事件对象中获取事件源
Map map = (Map) event.getSource();
System.out.println("事务提交了,"+map.get("sourceName")+"给"+map.get("targetName")+"转了"+String.valueOf(map.get("money"))+"钱,转账成功");
}
/**
* 当事务回滚之后执行
* @param event
*/
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void otherSomething(MyApplicationEvent event){
//从事件对象中获取事件源
Map map = (Map) event.getSource();
System.out.println("事务回滚了,"+map.get("sourceName")+"给"+map.get("targetName")+"转了"+String.valueOf(map.get("money"))+"钱,转账失败");
}
}
在方法中添加事件:
@Override
@Transactional(propagation = Propagation.REQUIRED/*传播行为*/,
readOnly = false,timeout = -1
)
public void transfer(String sourceName, String targetName, Double money) {
try{
//根据名称查询转出账户
Account source = accountDao.findByName(sourceName);
//根据名称查询转入账户
Account target = accountDao.findByName(targetName);
//转出账户减钱
source.setMoney(source.getMoney() - money);
//转入账户加钱
target.setMoney(target.getMoney() + money);
//更新转出账户
accountDao.update(source);
//模拟异常
int i =1/0;
//更新转入账户
accountDao.update(target);
}finally {
Map map = new HashMap<>();
map.put("sourceName",sourceName);
map.put("targetName",targetName);
map.put("money",money);
applicationEventPublisher.publishEvent(new MyApplicationEvent(map));
}
}
1 tomcat加载自定义的config类
/**
* 初始化spring springmvc ioc容器的配置类 基于servlet3.0规范
*/
public class Config extends AbstractDispatcherServletInitializer {
/**
* 注册字符集过滤器
* @param servletContext
* @throws ServletException
*/
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//触发父类的onstart方法
super.onStartup(servletContext);
// 1 创建字符集过滤器对象
CharacterEncodingFilter filter = new CharacterEncodingFilter();
// 2 设置使用的字符集
filter.setEncoding("UTF-8");
// 3 添加到容器(添加到ServletContainer)
servletContext.addFilter("characterEncodingFilter",filter);
}
/**
* 用于创建springmvc的ioc容器
* @return
*/
@Override
protected WebApplicationContext createServletApplicationContext() { //表现层容器
AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
webApplicationContext.register(SpringMvcConfiguration.class);
return webApplicationContext;
}
/**
* 用于指定DispatcherServlet的请求映射
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 用于创建spring的ioc容器
* @return
*/
@Override
protected WebApplicationContext createRootApplicationContext() { //根容器
AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
webApplicationContext.register(SpringConfiguration.class);
return webApplicationContext;
}
}
config类需要继承AbstractDispatcherServletInitializer,重写方法。
2 由config父类 及其父类的父类进行实例化并初始化Servlet,即DispatcherServlet
3 DispatcherServlet 加载SpringMvcConfiguration配置类创建springmvc的ioc容器,根据配置初始化容器中的对象
由@Component衍生出的注解,spirng针对于表现层 专用的注解
建立请求URL与处理方法之间的对应关系
常用属性:
value、path(4.2版本后):与请求建立对应关系
name:给请求起名字
method:明确方法支持 何种方式请求
其他属性:
params:验证请求URL里是否有请求参数
headers:验证请求中是否包含指定的消息头
consumes:方法能够处理的请求体包含的MM类型
produces:指定最终产生的MM类型
RequestMapping注解衍生注解
GetMapping
PostMapping
PutMapping
DeleteMapping
PatchMapping
@RequestMapping使用注意细节
在不使用servlet原生获取项目名称的代码时,链接前不用带 /
(尤其是tomcat在部署了项目名称后)
表现层,RequestMapping value值带不带 / 没有区别,都可以
从请求征文中获取请求参数,给控制器方法 形参赋值
只能出现在方法参数上!!!!
表单能提供的数据类型只有三种:String类型,String数组,文件
属性:
value:对应请求中的参数名
defaultValue:设置默认值
required:参数是否必须存在
数据绑定器
属性:
value:给哪些参数做绑定
写在哪个表现层,就只对该表现层的方法起作用
属性:
value、basePackages:用于指定对哪些包下的controller进行增强
默认对所有controller进行增强
basePackageClasses:通过指定字节码,获取字节码所在的包,对其下的类增强
assignableTypes:对某个类或某些类进行特定地增强
annotations:对某些注解修饰的类进行增强
从请求消息头中获取消息头的值,并赋值给控制器方法形参
属性:
value、name:指定消息头名称,获取对应的值
required:表示是否必须有此消息头
defaultValue:如果没有消息头,则可设置默认值
获取cookie值
属性:
value、name:指定cookie名称,获取对应的值
required:表示是否必须有此cookie
defaultValue:如果没有cookie,则可设置默认值
可用于修饰方法、参数
当修饰方法时,表示执行控制器方法之前,被此注解修饰的方法都会执行
当修参数时,用于获取指定的数据给参数赋值
缺点:
该注解修饰方法一出现,则表示 不管控制器中有多少方法,在执行之前都会先执行该注解修饰的方法
分别对应从session域中取数据、存数据
@SessionAttributes只能出现在类上 或 接口上
请求域作用范围: 当前请求 及 当前请求的转发
会话域作用范围:多次请求间的数据共享
@SessionAttribute 只能写在参数前,表明是从会话域中获取数据时的名称
用于注释方法,表示当前方法是控制器执行异常后的处理方法
属性:
value : 指定异常的字节码
获得请求体的全部内容
前端通过ajax传json格式的内容,后端可通过@RequestBody获取json字符串
如果要实现自动封装,需导入第三方插件jackson,版本号要一致
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-coreartifactId>
<version>2.9.8version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.8version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-annotationsartifactId>
<version>2.9.8version>
dependency>
可写在类上 或者 方法上
用 流将返回值输出到浏览器上
@RestController 是@Controller+@ResponseBody的结合,在前后端通过json格式进行交互,响应数据通过流输出,则可直接使用该注解
PathVariable是Spring3.0开始加入,表明Spring全面支持Rest风格的请求url,用于获取请求url映射中占位符对应的值
跨域访问:跨站Http请求。指发起请求的资源所在域不同于该请求所指向资源所在域的HTTP请求
Html超链接中涉及src属性 访问资源 不算跨域
通过ajax请求 从当前域名访问另外一域名 则算跨域
刷新域名:在cmd命令行中输入
ipconfig /displaydns
ipconfig /flushdns
CrossOriginController:
@Controller
public class CrossOriginController {
/**
* 跨域访问
* @param user
* @return
*/
@RequestMapping("/useCrossOrigin")
public String useCrossOrigin(@RequestBody(required = false) User user){
System.out.println("user is"+user);
return "success";
}
}
使用过滤器给响应对象添加响应消息头,@CrossOrigin注解可以替代过滤器的作用
在不使用注解的情况下
CrossOriginFilter:
/**
* 通过过滤器 往响应对象里添加一下响应数据头
*/
public class CrossOriginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
try{
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
System.out.println("解决跨域问题的过滤器执行...");
//设置response响应消息头实现 跨域问题 的解决
/* 允许跨域的主机地址 */
response.setHeader("Access-Control-Allow-Origin", "*");
/* 允许跨域的请求方法GET, POST, HEAD 等 */
response.setHeader("Access-Control-Allow-Methods", "*");
/* 重新预检验跨域的缓存时间 (s) */
response.setHeader("Access-Control-Max-Age", "3600");
/* 允许跨域的请求头 */
response.setHeader("Access-Control-Allow-Headers", "*");
/* 是否携带cookie */
response.setHeader("Access-Control-Allow-Credentials", "true");
//放行
chain.doFilter(request,response);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void destroy() {
}
}
还需在Config配置类中添加:
//解决跨域的过滤器
FilterRegistration.Dynamic registration1 = servletContext.addFilter("crossOriginFilter",new CrossOriginFilter());
registration1.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST,DispatcherType.FORWARD,DispatcherType.INCLUDE),
false,"/*");
用于配置控制器
@Controller
@RestController
用于提供方法映射的
@RequestMapping
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
用于增强控制器方法的
@ModelAttribute
@ExceptionHandler
@InitBinder
用于给控制器方法提供通知的注解
@ControllerAdive
@RestControllerAdive
用于绑定控制器方法参数的注解
@RequestParam
@RequstBody
@RequestHeader
@CookieValue
@PathVariable
@SessionAttribute
用于改变响应方式的。响应的方式:转发,重定向,用流输出
@ResponeBody 用流输出的方式
用于在控制器方法提供Model或者ModelMap存入会话域的
@SessionAttribues
用于提供跨域访问
@CrossOrigin
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,
由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
1、doService方法
2、doDispatche方法
负责根据用户请求找到Handler 即处理器,SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
适配器模式
适配器模式就是把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
控制器三种编写方式:
1、Controller注解
用于注释控制器类
2、Controller接口
用于处理请求并返回ModelAndView
3、HttpRequestHandler接口
用于处理器请求,并由使用者提供相应
View
作用:渲染模型数据。spring中 视图是无状态的,即对每一个请求,都会创建一个View对象
分类 | 视图类型 | 说明 |
---|---|---|
URL视图 | InternalResourceView | 将JSP或者其他资源封装成一个视图,是InternaleResourceViewResolver默认使用的视图类型。 |
JstlView | 它是当我们在页面中使用了JSTL标签库的国际化标签后,需要采用的类型。 | |
文档类视图 | AbstractPdfView | PDF文档视图的抽象类 |
AbstractXlsView | Excel文档视图的抽象类,该类是4.2版本后的,之前使用的是AbstractExcelView | |
JSON视图 | MappingJackson2JsonView | 将模型数据封装成Json格式数据输出。它需要借助Jackson开源框架. |
XML视图 | MappingJackson2XmlView | 将模型数据封装成XML格式数据。它是从4.1版本之后才加入的。 |
View Resolver
作用:负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。视图对象是由视图解析器负责实例化。
视图解析器的作用是将逻辑视图转为物理视图,所有的视图解析器都必须实现ViewResolver接口。
SpringMVC为逻辑视图名的解析提供了不同的策略,可以在Spring WEB上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。程序员可以选择一种视图解析器或混用多种视图解析器。可以通过order属性指定解析器的优先顺序,order越小优先级越高,SpringMVC会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则抛出ServletException异常。
分类 | 解析器类型 | 说明 |
---|---|---|
解析为Bean的名称 | BeanNameViewResolver | Bean的id即为逻辑视图名称 |
解析为URL文件 | InternalResourceViewResolver | 将视图名解析成一个URL文件,一般就是一个jsp或者html文件。文件一般都存放在WEB-INF目录中。 |
解析指定XML文件 | XmlViewResolver | 解析指定位置的XML文件,默认在/WEB-INF/views.xml |
解析指定属性文件 | ResourceBundleViewResolver | 解析properties文件 |
参数绑定不只有请求参数,还包括请求消息头、Cookie、Session域中获取数据、基于Rest风格URL的数据、获取全部请求体的数据。
使用策略模式
定义一个接口,通过多态性的使用针对不同的注解 获取不同的参数,进行数据绑定
拦截器的责任链模式
一种常见的行为模式。它是使多个对象都有处理请求的机会,从而避免了请求的发送者与接收者之间的耦合关系。将这些对象串成一条链,并沿着这条链一直传递该请求,直到有对象处理它为止。
优势:
解耦了请求与处理;
具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待结果
通过改变链路结构动态新增或删减责任
易于扩展新的请求处理类(节点),符合开闭原则
弊端:
责任链路过长时,对请求传递处理效率有影响
若节点对象存在循环引用时,会造成死循环,导致系统崩溃;
通过实现Converter接口来自定义类型转换器
自定义Converter类:
@Component
public class StringToDateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
//判断来源是否有值
if (StringUtils.isEmpty(source)) throw new NullPointerException("空指针异常!");
try {
//定义转换器
DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
//转换并返回
return format.parse(source);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
在初始化数据绑定器中添加 类型转换器:
@ControllerAdvice
public class InitBinderAdvice {
@Autowired
private StringToDateConverter stringToDateConverter;
/**
* 初始化数据绑定器
* @param dataBinder
*/
@InitBinder
public void initBinder(WebDataBinder dataBinder){
//获取转换服务对象
ConversionService conversionService = dataBinder.getConversionService();
//判断conversionService是否为GenericConversionService类型
if (conversionService instanceof GenericConversionService) {
//强转
GenericConversionService genericConversionService = (GenericConversionService) conversionService;
//添加类型转换器
genericConversionService.addConverter(stringToDateConverter);
}
//dataBinder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
}