1 Spring
1.1 基础理解
-
定义:
-
轻量级JavaEE解决方案开源框架;整合众多优秀设计模式;用来帮助管理JavaBean(除Entity,它由持久层框架管理)
-
解决方案即分层处理各个问题
-
Mybatis等只专注处理其中一层;灵活的Spring不要求必须完全使用自己,反而可以整合Mybatis,及第三方类库
-
-
以反转控制(Inverse of Control)和面向切面编程(Aspect Oriented Programming)为内核,提供了展现层Spring MVC、持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术
-
-
框架体系:
-
实体
-
Entity:与数据库的表对应
-
Bo:business object即业务对象,是对业务逻辑实现过程中需要用到的属性和方法的封装
-
Vo:value object即值对象,常用于控制层与视图层数据传递的封装
-
Po:persistent object即持久层数据封装,可理解为数据库中数据的一条记录
-
Dto:data transfer object即数据传输对象
-
Pojo:简单无规则对象
-
-
Dao:数据库数据的CRUD;事务管理层
-
Service / ServiceImpl:业务层
-
Controller:视图层,与前端交互
-
-
Spring启动流程:
-
(标签< load-on-startup>对应)类的onStartup()方法让Tomcat加载WebApplicationInitializer(开发使用最底层的有更多实现内容的AbstractAnnotationConfigDispatcherServletInitializer)完成容器的启动和初始化,包括注册MVC的Servlet,Filter,Listener三大组件,及创建根容器WebApplicationContext并将配置类保存到容器中
-
listener:创建监听器;< listener>< listener-class>ContextLoaderListner;< context-param>设置配置文件或配置类(即上下文参数)的位置或new ContextLoaderListner
-
-
Sring组成及核心:
-
优点:
-
低侵入式设计,代码的污染极低
-
独立于各种应用服务器,基于Spring框架的应用,可以真正实现写一次后在任何地方调用的承诺
-
IoC:降低组件之间的耦合性即方便解耦,降低业务对象替换的复杂性即简化开发
-
AOP:支持将一些通用任务如安全、事务、日志等进行集中式管理,以提高复用
-
Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化底层的数据库访问
-
Spring高度开放,并不强制应用完全依赖Spring,开发者可自由选用Spring框架的部分或全部
-
-
Spring中用到的设计模式:工厂模式(BeanFactory)、单例模式(Bean作用域)、适配器(Adapter)、装饰器(BeanWrapper)、(动态)代理、观察者(listener,event,multicast)、策略、模板方法
-
Spring流程图:
1.2 Bean
-
传统EJB(Enterprise企业级 Java Bean):重量级框架
-
运行环境苛刻:运行在EJB容器中;服务器都包含了Servlet引擎,Oracle的Weblogic或IBM的WebSphere还包括了EJB容器,收费,而免费的Tomcat没有EJB容器
-
代码(在服务器间的)移植性差
-
-
Spring Bean轻量级,没有EJB存在的问题;容器作为超级大工厂,由BeanFactory负责创建、管理所有的Java对象;属性都是私有,对外提供访问的getter()和setter()方便扩展
-
对于开发者来说,使用Spring框架主要做两件事:①开发Bean;②配置Bean。对于Spring框架来说,框架要做的就是根据配置文件来创建Bean实例,并调用Bean实例的方法完成IoC
-
与普通java对象的区别:表面区别是一个从配置或注解中获取,因为自动注入而有值,如@Component标注Service,即Spring为我们创建了这个Bean,而Service中@Autowired一个Pojo,这个Pojo有值;而普通对象是通过new,可能无值
-
Bean的定义方式:XML,Annotation,配置类,其他
-
FactoryBean(工厂Bean):特殊Bean,Spring提供的一种灵活创建Bean的方式,实现FactoryBean中的接口
@Component public class MyFactoryBean implements FactoryBean { // 返回最终Bean对象;当与返回Bean对象不符时异常BeansNotOfRequiredTypeException // 如果想得到FactoryBean对象,则beanName前面加上&符号 @Override public Object getObject() throws Exception { return new User(); } // 定义数据库连接 // @Override // public Connection getObject() { // Class.forName("com.mysql.jdbc.Driver"); // Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/..","root","123"); // return conn; // } @Override public Class<?> getObjectType() { return User.class; } @Override puvlic boolean isSingleton() { return ture; } }
-
-
Spring容器本身没有提供Bean的线程安全策略;即Bean不是线程安全的;但对于原型Bean,每次获取Bean时都创建新对象,即线程间不共享,不存在线程安全问题;对于单例Bean,所有线程共享一个Bean,存在竞争资源关系,若此Bean无状态即不存储数据,线程只对其做查询操作等,则线程安全,如Service ,Dao ,Controller 大都无状态
-
BeanDefinition:Bean定义对象,即容器以此来创建Bean ,包含的属性用来描述Bean;@Component系列注解、@Bean、< bean/>都被解析为BeanDefinition对象;@Component标注普通Bean,@Reposity标注Dao组件,@Service标注业务逻辑组件,@Controller标注web组件;它们都默认省略value="beanId"属性,所以同一种类可以定义为多个Bean(区别于单例);name属性或< alias>标签取别名;@RequestScope、@SessionScope、@ApplicationScope定义Bean的作用域;实现类GenericBeanDefinition;属性包括:
-
beanClass:Bean的类型,如User.class
-
scope:Bean的作用域
-
isLazy:是否需要懒加载(针对单例);懒加载机制让Bean在第一次getBean()时生成,否则会提前在容器启动过程中生成
-
dependsOn:依赖;Bean被创建之前,它所依赖的需要先创建好
-
primary:标注当前Bean为主Bean;类可以创建多个对象,在依赖注入时需要判断是否存在主Bean,有则直接注入
-
initMethodName:Bean的初始化方法,自定义的Bean初始化逻辑;自定义方法而不是实现Spring提供的方法,则避免了框架的侵入;两种方法都存在时,先调用Spring的,再执行自定义的
-
其他:constructorArgumentValues参数值,parentName父类名,factoryBeanName工厂名,propertyValues属性,destroyMethodName销毁方法名,autowireMode注入类型,factoryMehtodName工厂方法名
-
-
BeanFacotry:Bean工厂,Spring容器,所有BeanDefinition对象的组合;在容器中利用BeanDifinition创建和管理Bean;核心简单容器,提供基本的Ioc和DI功能,适合测试和非生产环境 ;Spring内部核心接口,核心简单容器,提供基本的Ioc和DI功能,适合测试和非生产环境,一般不供开发者使用;在加载配置文件时未创建对象,在获取或使用时才创建,即getBean()时通过名字检索容器中管理的Bean;提供扩展和更多功能的核心接口和实现类有:
-
ApplicationContext/ ConfigurableApplicationContext:
-
ListableBeanFactory / DefaultListableBeanFactory:支持Bean的单例、别名、类型转化、后置处理、自动装配;Spring默认以它为IoC容器;核心属性ConcurrentHashMap <String, BeanDifinition> beanDifinitionMap用于存储BeanDifinition,key为beanId
// 源码 public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { // ... private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap(16); private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap(256); private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap(64); private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap(64); private volatile List<String> beanDefinitionNames = new ArrayList(256); private volatile Set<String> manualSingletonNames = new LinkedHashSet(16); // ... }
-
ConfiguratbleBeanFactory:
-
AutowireCapableBeanFactory:
-
AbstractBeanFactory:
-
-
ApplicationContext应用上下文(核心API):BeanFactory的高级形态,Context模块的核心接口,扩展了BeanFactory的功能
-
还继承的其他接口包括:
-
EnvironmentCapable:可获取操作系统和JVM的环境变量
-
ListableBeanFactory:可获取所有beanNames,某个类型的所有beanNames,判断某个beanName是否存在对应的BeanDifinition,统计BeanDifinition个数
-
HierarchicalBeanFactory:可获取父BeanFactory,判断某个name是否存在对应的Bean对象
-
MessageSource:i18n国际化
-
ApplicationEventPublisher:事件发布
-
ResourcePatternResolver:可加载并获取文件、图片等URL资源
-
-
主要实现类:ApplicationContext接口采用工厂设计模式(所以主要作用是创建bean),屏蔽了实现的差异
-
ClassPathXmlApplicationContext:从类加载路径下搜索配置文件bean.xml,其中是< bean>的配置;适合非Web环境如main()和Junit中
-
构造器中先读取配置文件信息并存储为字符串数组,比如期间会调用doResolvePlaceholders()做配置文件中${}的解析和替换;然后调用refresh()
-
refresh():容器的创建和初始化
-
prepareRefresh():容器创建之前的预先准备,如记录容器启动时间和标记
-
obtainFreshBeanFactory():创建BeanFactory即bean工厂,如果已存在则销毁;然后装载(加载并存储)BeanDefinition
-
refreshBeanFactory():调用createBeanFactory()返回DefaultListableBeanFactory即工厂创建
-
工厂传入调用的loadBeanDeifinitions()中,用来创建XmlBeanDefinitionReader,用它初始化环境信息,资源加载器和entity解析器,然后去加载和解析Bean定义的xml配置文件(过程很复杂,包括但不限于判断文件前后缀,数据流化,Bean别名解析,解析自定义信息等)并封装为BeanDifiniton,解析结果是一个beanDifinition的话,BeanDifinitionReaderUtils.registerBeanDefinition()注册该Bean到容器中即beanDifinitionMap.put(beanName, beanDifinition);所有的beanNames还会保存到一个beanNames的List中
-
-
prepareBeanFactory():配置Bean工厂的标准上下文特性,如类加载器,后处理器等
-
postProcessBeanFactory():模板方法,装载BeanDefinition后的一些处理,如提供Bean工厂的一个入口
-
invokeBeanFactoryPostProcessors():提供对BeanDefinition的修改的入口
-
registerBeanPostProcessors():注册用于拦截Bean创建过程的BeanPostProcessors
-
完成实现的接口的方法,如初始化MessageResouce,初始化事件等
-
finishBeanFactoryInitialization():完成容器的初始化;完成单例对象的实例化
-
preInstantiateSingletons():循环beanNames的List,判断其对应的Bean对象是否已经被实例化(调用getMergedLocalBeanDifinition()判断,即属性是否填充,是否合并了父类信息);如果没有实例化且是单例且没有懒加载,则调用getBean()完成实例化,保存到缓存中
-
-
finishRefresh():
-
-
构造器执行完毕后,使用getBean():从缓存中查找需要返回的Bean
-
-
FileSystemXmlApplicationContext:从绝对路径下去搜索配置文件
-
AnnotationConfigApplicationContext:从配置类中搜索Bean
-
XmlWebApplicationContext:适合Web环境
-
-
重量级;占内存但一个应用只创建一个工厂对象,被多线程并发访问但线程安全
-
-
ClassPathResource:获取配置文件资源;不同来源对应不同实现类;封装为Resource类型后传给读取器的构造器;getBean():通过名字检索容器中管理的Bean;使用BeanFactory时仅实例化Bean,而使用ApplicationContext时已经实例化完成,它在容器启动即new时实例化;带参数则检索时类型检查
-
Bean装载过程/生命周期:创建-初始化-使用-销毁
-
Bean定义:容器扫描到类或加载配置文件后,解析文件得到BeanDefinition对象,而不是直接new对象,因为需要根据描述分析该Bean的配置,如Bean类型,是否懒加载等
-
方法执行链:refresh() -》refreshContext() -》finishBeanFactoryInitialization() -》 getBean() -》doGetBean() -》 getSingleton()和getObjectForBeanInstance()
-
方法主要工作:容器中产生一个用来存储BeanDefinition的Map<beanName, BeanDefinition>结构;如果map中不包括beanName,则尝试从parentBeanFatory中检测,也就没有后续方法操作;doGetBean()中提取校验和转换beanName,包括如果是别名则获取最终neanName,如果是FactoryBean则去掉特有的修饰符后取值,如&user取user;如果beanName是子Bean,则合并父Bean的相关属性;如果使用XML配置方式,则bean信息存储在GenericBeanDefinition中,而所有bean的后续处理都是针对RootBeanDefinition,此时需要转换
-
getSingleton(beanName):依赖检查,并尝试从缓存(按照一二三级缓存顺序)中获取对象;因为创建单例时可能存在循环依赖,为了解决它,Spring不等bean创建完成,将其放入实例工厂ObjectFactory并曝光,在被依赖时直接使用
-
getObjectForBeanInstance():bean的实例化;缓存中获取的bean是原始状态
-
-
实例化Bean:依据BeanDefinition,默认调用无参构造器(实际可能需要推断选出构造器)或方法上使用@PostConstruct,通过反射方式在堆中申请空间,创建对象并赋予默认值
-
配置属性:setter()的调用,自动依赖注入
-
检查Aware相关接口并设置相关需要引用的容器内部的依赖对象:主要调用invokeAwareMethod()方法,完成BeanName,BeanFacotry,BeanClassLoader对象的属性设置
-
实例传给Bean前处理器BeanPostProcessor进行前置处理工作(AOP)
-
初始化Bean:对其他属性的赋值和校验,如实现InitializingBean的afterPropertiesSet(),或xml中配置inti-method属性指向自定义方法,来手动做一些初始化(由Spring工厂自动调用方法);而后可能将实例传给Bean后处理器处理
-
实例传给Bean前处理器BeanPostProcessor进行后置处理工作(AOP),如生成代理对象
-
常见的Bean此时已就绪,驻留在上下文或单例池中供程序使用;getBean()获取;如果Bean经过AOP,则单例池中存放的是Bean的代理对象
-
销毁:工厂关闭即context.close()时,实现DisposableBean的destory(),或destory-method属性指向自定义销毁方法;或使用@PreDestroy销毁前的方法;销毁只适用单例
-
-
Bean作用域:
-
singleton:默认单例,无论创建多少次都是同一个实例,实例化两个Bean对象,bean1 == bean2为true;使用ThreadLocal保证线程安全;加载配置文件时就会创建单例对象;针对单例模式,不需要在启动时立即加载Bean到容器中,可延迟加载(即等到调用getBean()时初始化):< bean id class lazy-init="true"/>或注解@Lazy(true)
-
多次请求,但Spring容器里只实例化一个Bean,并保存到缓存的map中;后续请求先从缓存中查找,有则直接使用,无则再实例化
-
为何Spring的Bean设置为单例:减少实例化(反射或cglib创建对象并分配内存)时的消耗;减少JVM垃圾回收;快速获取到Bean,第一次生成,后面都是从缓存中获取
-
劣势:所有线程共享此Bean,如果此Bean是有状态的,则非线程安全,可能在并发场景下出现问题
-
-
prototype:singleton=”false”;原型、多例,每次获取Bean时,如调用getBean(),都将产生一个新的Bean实例,没有从缓存查找的过程;销毁代价比较大
-
request:Web环境每一次HTTP请求(每一次请求方法的调用)都创建一个实例
-
(global) session:Web环境每个全局的HTTP Session共享一个实例,不同session创建不同实例
-
标注作用域:< scope=””>做< bean>标签属性或Bean上使用注解@Scope(“prototype”);常用更安全的类封装的字段常量,如ConfigurableBeanFactory.SCOPE_PROTOTYPE
-
request和session作用域的Bean有proxyMode属性,即设置代理并延迟加载:当此Bean注入到一个单例Bean时,单例已经初始化,但此Bean还不存在;委托相同功能的代理Bean解析和延迟加载,真正调用时才使用此Bean
-
Bean为接口,则值ScopedProxyMode.INTERFACES
-
Bean为类,则需要使用CGLIB生成代理类,值ScopedProxyMode.TARGET_CLASS;它对应aop命名空间中的标签< aop:scoped-proxy>,默认使用CGLIB创建目标类的代理,属性proxy-targer-class值为false时创建接口的代理
-
-
协调作用域不同步的Bean:
-
不同步现象:如当singleton作用域的Bean依赖于prototype作用域的Bean时产生,因为Spring容器初始化时,预初始化容器中所有的单例,而这些Bean依赖于prototype Bean,于是Spring会先创建prototypeBean,再创建singleton Bean,再将prototype Bean注入singleton Bean
-
解决方法:
-
放弃依赖注入: singleton作用域的Bean每次需要prototype作用域的Bean时,主动向容器请求新的Bean实例,即可保证每次注入的prototype Bean实例都是最新的实例
-
利用方法注入: 推荐;方法注入通常使用lookup方法注入,该方法注入可以让Spring容器重写容器中Bean的抽象或具体方法,返回查找容器中其他Bean的结果,被查找的Bean通常是一个non-singleton Bean。Spring通过使用JDK动态代理或cglib库(Spring4.0的spring-core-xxx.jar包中已集成了cglib类库)修改客户端的二进制码,从而实现上述要求;使用lookup方法注入的两步:将调用者Bean的实现类定义为抽象类,并定义一个抽象方法来获取被依赖的Bean;在< bean…/ >元素中添加< lookup-method…/ >子元素让Spring为调用者Bean的实现类实现指定的抽象方法Notes;Spring会采用运行时动态增强的方式来实现<lookup-method.../>元素所指定的抽象方法,如果目标抽象类实现过接口,Spring会采用JDK动态代理来实现该抽象类和抽象方法;如果目标抽象类没有实现过接口,则采用cglib
-
-
-
-
BeanPostProcessor:Bean后处理器,Spring提供的一种扩展机制(AOP),对Bean进行加工;良好的扩展性是框架的必备;特殊的Bean接口;不对外提供服务,它甚至可以无须id属性;主要负责对容器中Bean进行后处理或额外加强,如为容器中的目标Bean生成代理,或在Bean实例创建成功之后对Bean实例进行进一步的增强处理。一旦注册了Bean后处理器,它就会自动启动,在Bean创建时自动工作;Bean后处理器必须实现BeanPostProcessor接口及方法:
-
InstantiationAwareBeanPostProcessor:子接口,提供postProcessBeforeInstantiation()实例化前置处理方法,postProcessAfterInstantiation()实例化后置处理方法,postProcessProperties()属性出入后处理方法
-
Object postProcessAfterinitialization(Object bean, String name) throws BeansException:(初始化)后置处理方法
-
Object postProcessBeforeInitialization(Object bean, String name) throws BeansException:(初始化)前置处理方法,参数1为目标Bean实例,参数2为该Bean的配置id
-
-
BeanFactoryPostProcessor:工厂后处理器:对IoC容器本身进行后处理或增强容器功能;postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)方法体对Spring容器进行的处理,如对容器进行自定义扩展,对BeanDefinition对象进行加工
-
配置文件参数化:将需要频繁修改的字符串转义到小配置文件中,如数据库连接信息,DataSource的相关Bean信息全部配置到了ymal等文件中,便于修改和运维
-
Spring整合小配置文件:< context:property-placeholder location="classpath:/...">引入;配置文件放在resources目录下,项目编译后该文件就在classes目录下,即与java目录同级,用classpath指定
-
使用${key}获取值
-
-
复杂组件:不能直接通过new等简单创建的对象,或者< bean>等方式创建Bean时需要大量配置信息的,创建方式不灵活,便可使用编码方式定制创建Bean;如build()创建的流,jdbc创建Connection等;实例工厂方式创建:
- 实现FctoryBean的方法:Spring根据id获取Bean标签信息,instanceof判断Bean类型,如果是实现了FactoryBean的,则Spring去调用自定义的getObject()生成实例,否则为普通的new实例;它隐藏了一些复杂的实例化细节;class指定的是实现FctoryBean的类,因此与平常通过id获取对象时不同,它获取出来的是实现方法里返回的Bean即FacoryBean#getObject()的返回代理对象;如果想获取该类,在id前加&,如ctx.getBean("&user")
-
XML方式:编写Factory,创建Bean并注册Factory;然后配置指定它为FactoryBean并指明方法
<bean id="factory" class="..."></bean> <bean id="" factory-bean="factory" factory-method="getObject">
1.3 IoC
-
原始做法: 调用者与被依赖对象硬编码耦合,不利于项目的升级与维护
-
调用者主动new被依赖对象,调用被依赖对象的方法
-
简单工厂模式: 调用者先找到被依赖对象的生产工厂,由工厂get()去获取被依赖对象,再调用被依赖对象的方法;没有最低限度降低耦合
-
-
使用IoC:调用者无需主动获取被依赖对象,调用者只要被动接受Spring容器为调用者的成员变量赋值即可;主动变被动,即控制反转;从容器角度看,容器负责依赖对象的赋值,即依赖注入(dependency injection)
-
控制:对成员变量赋值的控制权:代码,容器(配置文件或注解 + Spring工厂)
-
反转:从耦合的代码转移到解耦的容器
-
依赖倒置DI:通过IoC容器,将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。将类和类之间的依赖从代码中脱离出来,用配置的方式进行依赖关系描述,由IoC容器负责依赖类之间的创建、拼接、管理、获取等工作;如不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,更加专注于上层应用;通过框架或IoC容器在对象生成或初始化时注入数据或所依赖对象的引用并管理
-
存储:容器采用三级缓存机制,map结构来存储对象
-
-
底层原理:配置解析+工厂模式+反射 - 实现IoC容器
-
通过creatBeanFactory()创建Bean工厂
-
使用工厂循环创建Bean对象,由于Bean默认单例,所以先通过getBean()、doGetBean()从容器中查找,找不到再创建;解析xml文件,获取id等信息
-
通过createBean()、doCreateBean()以反射方式创建Bean对象,反射< bean>标签forName()得到Class,newInstance()实例化后得到对象;反射会调用无参构造器,包括私有;< property>标签等依赖注入同理;然后属性填充populateBean()
-
其他初始化操作initializiBean()
-
-
核心机制:管理Bean:主要为Bean创建和依赖注入
-
装配后默认beanId为全限定名+”#数字”,用作计数区分,区别于打印类时输出类名@地址
-
不能装配原生数据类型;IoC是为了解耦,基本数据类型不存在耦合问题;注入的< property>列表构成的整个对象而不是注入单个< property>;但可以用@Value引入,用动态表达式从属性配置文件中获取单个值
-
两种管理方式:
-
xml配置方式:
-
前几行引入库/模式用来管理xml,如使用< bean>标签则需要spring-bean模式
-
Bean发现机制:< context:component-scan base-package="包列表或简化写为上层">命名空间规定扫描范围;< context:include/exclude -filter type=”” expression=”….*Dao” />
-
可选属性use-default-filters="是否使用默认filter";< context:include-filter type="" expression="">标识自定义filter,即仅扫描expression满足type要求的;而exclude-filter不去扫描符合的
-
-
组合:引入外部文件,< import resource="其他xml.xml>
-
< bean id="对象唯一标识,一般为类名首字母小写" class="类全路径" autowire=“byName / byType">标签标识对象:
-
手动装配:< property>列表,标识注入的依赖,name为依赖名,value为属性值;ref为引用的beanId
-
自动装配:autowire属性,省略< property>的手动装配;default-autowire对配置文件中所有的Bean起作用,autowire对当前Bean起作用;可取值:
-
no:默认值;不使用自动装配。Bean依赖必须通过ref元素定义。推荐不修改此值,显式配置合作者能够得到更清晰的依赖关系
-
byName:根据setter()名进行自动装配;Spring容器查找容器中全部Bean,找出其id与setter()名去掉set前缀并小写首字母后,同名的Bean来完成注入。如果没有找到匹配的Bean实例,则不进行任何注入
-
byType:根据setter()的形参的类型自动装配。Spring容器查找容器中的全部Bean,Bean类型与setter()的形参类型匹配,则自动注入这个Bean;如果找到多个这样的Bean,则抛出异常;如果没有找到这样的Bean,则什么都不会发生,setter()方法不会被调用
-
constructor:与byType类似,区别是用于自动匹配构造器的参数
-
autodetect:Spring容器根据Bean内部结构,自行决定使用constructor或byType策略。如果找到一个默认的构造函数,那么就会应用byType策略
-
-
手动装配覆盖自动装配;装配第三方库中的Bean只能依靠手动,如SqlSessionFactoryBean,各种DataSource
-
-
默认执行无参构造器,不灵活;自定义有参而未写无参构造器时,可能报错NoSuchMethodException
-
排除Bean在自动装配之外:< beanId="" autowire-candidate="false" />;或< beans>中以模式字符串指定,如排除所有以abc结尾的Bean:< beans default-autowire-candidates="*abc" />
-
常见的xml方式注入对象,如连接池:
<!--使用context名称空间引入文件;value值一般都是外部属性配置文件如spring.properties中读取;--> <context:property-placeholder location="classpath:spring.properties"></context:property-placeholder> <!--配置德鲁伊数据库连接池;value值替换为动态表达式获取属性配置文件中的key,如${spring.druid.driverClass}--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <propertry name="url" value="jdbc:mysql://localhost:3306/DB"></propertry> <property name="username" value=""></property> <property name="password" value=""></property> </bean> <bean id="user" class="com...User" name="别名"> <!-- 默认beanId为类名首字母小写,上面id配置可省;beanName为class后+#0序列 --> </bean>
-
测试:
@Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); DruidDataSource source = (DruidDataSource)context.getBean("dataSource"); // 参数为id或name值,区别是id唯一 // source = context.getBean("dataSource", DruidDataSource.class); // source = context.getBean(DruidDataSource.class); 这种方法要求注入的bean唯一,配置文件中支持定义不同id,但class相同,因此需要注意 String[] beanDefinitionNames = context.getBeanDefinitionNames(); // getBeanNamesForType(User.class) // containsBeanDefinition("user");containsBean(); }
-
-
注解方式:
-
Annotation是代码特殊标记;简化xml配置甚至零配置,但降低了依赖关系的清晰性和透明性,依靠源文件的属性名或属性类型装配,导致Bean与Bean之间的耦合降低到代码层次,不利于高层次解耦;需引入spring-aop依赖包
-
@注解名(key=value列表);使用在类、方法或属性上
-
Bean发现机制:@ComponentScan开启组件扫描;程序启动类或配置类上使用@ComponentScan(value="包");属性value或basePackages以字符串形式指定扫描基础包列表,类型不安全;或属性basePackageClasses={A.class,B.class}指定具体类或接口,安全
-
自动注入:
-
@Autowired:byType方式;可用于setter()、普通方法、实例变量和构造器;常用于接口或父类。所以可能出现红色下划线而项目依然可运行;当符合的Bean实例有多个时,可能引起异常NoUniqueBeanDefinitionException;对Bean设置为首选@Primary,但有局限,在设计期间不确实谁是首选;或者@Autowired用于具体实现类
-
@Qualifier:byName方式;在多个实现类时配合@Autowired使用,可用value指定具体类;否则可能NoSuchBeanDefinitionException
-
@Resource:byType方式,也可用name属性设置为byName,位于javax.annotation包即java提供的注解
-
required属性为false设置Bean暂时处于未装配状态,否则若没有匹配的Bean,上下文在创建时抛异常;或有多个满足的Bean时抛出异常,而后进行null检查;可换为@Inject(来自javaDI规范)
-
-
-
配置类:@Configuration标注的类,替代xml配置文件;java显式配置,更解耦和无侵入,类型安全,重构友好:加载配置文件改为对应Context的实现类来加载配置类即参数为类.class
-
配合@ComponentScan扫描Bean
-
不扫描,而在类中定义Bean:创建返回Bean对象的方法且以@Bean标注,属性name的值或重命名该方法名为beanId;SpringBoot常用
-
组合:@Import(配置类列表.class);@ImportResource(“classpath:配置文件.xml”);
-
配置类PropertySourcePlaceholderConfigurer,用于支持动态表达式,等同xml中引入命名空间
-
-
动态表达式-运行时值注入:改善直接value=””的硬编码:
-
属性占位符:
-
配置属性配置文件,内容一般为key=value列表;它被Spring加载到Environment;@PropertySource(“classpath:….properties”)引用属性源
-
方式1:类中@Autowired注入Environment后使用其方法getProperty(key[,defaultValue])获取value;key不存在时IllegalStateException;相关方法boolean containsProperty();getPropertyAsClass()
-
方式2:占位符${key};在每一个参数前@Value(“${key}”),或xml配置中value=”${key}”
-
-
SpringEL表达式:“#{java代码表达式}”:
-
-
-
-
依赖注入:建立创建好的Bean之间的依赖关系injection
-
setter()设值注入:
-
bean.xml配置方式:即普通new对象后调用setter(),这耦合,用配置文件解耦,反射调用setter()
-
在管理Bean的标签< bean>中,添加配置< property name="filed名" ref="引用的对象beanId或引用其他的配置Bean,即级联赋值" value="field赋值">列表;value做属性是当类型为基本类型和字符串时的< value>的简写
-
注入自定义对象类型:外部Bean常见于一对一关系;内部Bean:常见于依赖的对象存在一对多关系
-
< property>内嵌< bean class="依赖的Bean">,或用ref指向引用的Bean,< ref bean="beanId">,减少Bean的解析和创建
-
当name值是ref指向的对象的属性时,以.级联,如依赖User,则< property name="user" ref="user">;< property name="user.name",value="">;类中需要提供User的getter()
-
-
注入集合类型:当依赖类型是数组或集合
-
< property>中嵌套< array>、< set>、< list>或< map>标签,标签中为< value>列表;list包括数组;map内嵌标签或< entry key="" value="">
-
抽取为公共:引入名称空间xmlns:util及spring-util后,使用< util>标签及< value>或< ref>列表;在注入处ref引用utilId
-
-
当value为null,其下加入< null/>替代;等同于默认不注入
-
当value存在特殊符号,使用CDATA表达式;当特殊符号是尖括号< >,可写为& lt;和& gt;
-
-
-
基于P-名称空间方式的简化配置(property):< beans>中添加xmlns:p属性引入该模式,然后取消< property>,直接在< bean>中添加属性:< bean id class p:bname / bauthor / bname-ref / bauther-ref>;用参数名不灵活,可用参数索引,如p:_0 / _0-ref;只有一个参数时0可省;标识符不以数字开头,加了下划线;无法针对集合参数
-
简单、直观,与传统的JavaBean的写法更相似,程序开发人员更容易理解、接受,因此常用
-
问题:配置代码冗余;被注入的对象多次创建(每一个bean标签解析都创建一次Bean),消耗内存
-
-
有参构造器注入:
-
bean.xml配置方式:反射时执行有参构造器,利用参数对成员变量进行初始化;配置< constructor-arg name="" ref=”引用的beanId”“ value=”field字面量” index=“参数索引,type="参数类型",三选一但建议不用type,多参数且参数类型相同时无法区分>
-
简化配置:使用C命名空间(constructor-arg),c-作为bean标签的属性
-
构造器中参数顺序决定依赖关系的注入顺序;顺序和个数保持一致;参数个数任意(重载);重载时参数个数相同时,标签上需要type明确需要注入参数的类型,否则按顺序注入,结果不正确;(类型相同则不是重载方法了)
-
没有setter方法,所有的依赖关系全部在构造器内设定,无须担心后续的代码对依赖关系产生破坏;所以对于依赖关系无需变化的Bean,构造注入更有用处
-
依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系,对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则
-
构造器过于臃肿,难以阅读
-
Spring在创建Bean实例时,需要同时实例化其依赖的全部实例,因而导致性能下降
-
-
-
循环依赖与三级缓存 :
-
发生在Bean属性设置-初始化阶段: 多个Bean之间相互依赖 ,即依赖被递归,形成闭环;如A类拥有属性B类,B类拥有属性C类,C类拥有属性A类
-
遵循Spring底层,即依赖的注入方式是setter()且依赖是单例的,则不会出现循环依赖问题;假如使用构造器注入,则实例化时,new A(new B(new A...))产生无限嵌套,显然不合理;而使用set时,分别实例化A和B后,依次调用a.setB(b)和b.set(a)毫无问题;在spring容器中还要求类是单例的(纯javaCode此无要求)
-
三级缓存-DefaultSingletonBeanRegistry:解决循环依赖问题:核心:实例化与初始化分开
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { // 一级缓存:单例池,即单例Bean的缓存,存放已初始化完成的bean对象 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256); // 三级缓存:ObjectFactory的缓存,存放可以生成Bean的工厂;三级缓存的value类型ObjeectFactory是一个函数式接口,它能保证整个容器运行过程中,同名Bean对象唯一 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16); // 二级缓存:早期Bean实例的缓存,即暴露了但Bean的生命周期还未结束的早期Bean对象(属性未填充完) private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16); // 尝试从容器中获取Bean public Object getSingleton(){...} } // 非单例Bean每次从容器获取,都会新建而没有缓存;所以循环依赖后会异常 /* 流程: 创建A,需要依赖B且容器中找不到,则暂将A放入三级缓存,先建B; 建B,需要依赖A,则按一级、二级、三级的顺序依次查找A,在三级中找到A,将A放入二级缓存并删除三级中的A; B建完,放入一级缓存,此时B依赖的A还是创建中(实例化但未初始化)状态,即A提前暴露; 接着创建A,直接从一级缓存中获取B,完成创建,并放入一级缓存 */
-
-
Bean注入异常:org.springframework.beans.factory.BeanCreateException:Error creating bean with name "...",NoSuchBeanDefinitionException:可能原因:Bean未添加注解;或注解添加错误,如错选成Dubbo的@Servcie
-
实现一个IoC容器:
-
先准备一个基本的容器对象,包含一些map结构的集合,用来存储Bean对象
-
进行配置文件的读取工作或注解的解析工作,将需要创建的Bean对象封装成BeanDefinition对象存储在容器中
-
容器将BeanDifinition对象通过反射方式实例化,以完成Bean对象的实例化
-
为Bean对象的属性赋值即初始化,依赖注入,以完成整个对象的创建
-
通过容器获取Bean对象,进行业务操作
-
提供销毁操作,对象不用或容器关闭时,Bean销毁
-
1.4 AOP
-
与OOP互为补充,即用来应对许多不容易用传统面向对象编程实现的功能,业务逻辑各部分之间的耦合度,提高程序复用性;比如处理系统中分布于各个模块(不同方法)中的交叉/横切关注点的问题,处理一些具有横切性质的系统级服务,如Authentication权限、Caching缓存、Context passing内容传递、Error handling错误处理,Lazy loading懒加载、Debugging 调试、logging日志,tracing,profiling and monitoring 记录跟踪优化校准、Performance optimization性能优化、Persistence持久化、Resource pooling资源池、 Synchronization同步、Transactions事务处理等,将程序运行过程分解成各个切面,即横切逻辑的编程思想
-
以切面为基本单位的程序开发;OOP以对象为基本单位,POP以过程(方法)为基本单位
-
如登录功能,查询数据库中密码并判断后跳转页面;添加权限判断功能,原始方法为if(管理员),else(普通用户),这将修改登录功能源码,而AOP将功能抽取出来后切入
-
-
底层原理:AOP其实是IoC的一个扩展功能,在IoC的Bean创建的后置处理过程BeanPostProcessor中完成对象的动态代理,它通过jdk或cglib的方式生成代理对象,方法调用DynamicAdvisorInterceptor类的intercept()开始创建,创建过程中创建出advice,pointcut等
-
基本概念:
-
横切关注点:散布于应用中的功能
-
通知、增强处理Advice:将横切关注点抽象和模块化,成为增强方法,是动态代理中的额外功能;增强类是一个Bean即需要@Component或< bean>配置
-
连接点Joinpoint:执行增强的目标方法,如增删查改方法;方法级别;程序执行过程中明确的点,如方法的调用,或者异常的抛出;在Spring AOP中,连接点总是方法的调用
-
切入点Pointcut:实际执行增强处理的连接点,如只对删除方法做了增强;通知不能阻止切入点的执行,除非通知中抛出异常
-
切面Aspect或Advisor:整合通知与切入点的过程
-
织入:把切面应用到目标对象(的编译期如AspectJ、类加载期、运行期)并创建代理对象
-
-
AspectJ:独立的框架,定义了基于注解的AOP语法;引入依赖即可使用
-
编写实现MethodBeforeAdevice的before()等不同切入位置的代理类,创建该Bean;MethodInterceptor接口提供更多方法
-
根据需求使用传入的参数:分别封装了Method目标方法,Object[]目标方法的参数,Object目标对象
-
-
切入点表达式:execution(* 完全限定名.*(..));用作注解的value或标签的expression
-
第一个 * 表示任意访问权限,..表示方法参数列表; * 可写为具体类(类切入点,影响类中所有方法)、包或具体切入点方法;对包下的子包不起作用
-
切入点函数:execution最常用,args和within;函数的逻辑运算and和or;xml配置中不能用&&;and不用于同一种函数
-
-
注解方式(推荐)及对应xml配置方式:
-
@EnableAspectJAutoProxy(proxyTargetClass):开启AspectJ,生成代理对象,使得增强类可用;配合@Configutation和@ComponentScan实现零配置的完全注解方式《==》< aop:aspectj-autoproxy>
-
@Aspect:标注类为增强类
-
@Order(数字):一个切入点被多个增强时,设置优先级;越小优先级越高;越先执行越后结束
-
对增强类中的增强标注类型及执行时机:
-
@Before:切入点的前置执行;《==》< aop:before>
-
@Around:此通知方法必有参数ProceedingJoinPoint,在方法体中任意位置通过proceed()方法来调用切入点,即控制转移,执行完后返回继续执行后边代码;《==》< aop:around>
-
@After:切入点后置执行,一定执行;《==》< aop:after>
-
@AfterReturning:切入点正常执行后执行;《==》< aop:after-retuining>
-
@AfterThrowing:切入点异常执行后执行;它若执行,导致AfterReturning和Around的后置不被执行;《==》< aop:after-throwing>
-
以上为Spring4(SpringBoot1)版本的执行顺序;Spring5(springBoot2)的顺序将@After改为在@AfterReturning或@AfterThrowing之后执行
-
-
@Pointcut():提取公共切入点,即如果切入点表达式相同,则抽取成一个有空方法体的方法,方法名作为各个增强类型的value值,以实现复用;《==》< aop:pointcut id=”方法名” expression=”execution(…)”>
-
注解方式不需要标注切入点,被用在切点表达式中的方法自动成为切入点;而xml配置需要:< aop:config>配置增强,其下的:
-
<aop:aspectj-autoproxy />:开启注解支持;属性proxy-target-class为true时指定动态代理为cglib形式
-
< aop:pointcut id="" expression="">列表指明切入点
-
< aop:aspect ref>列表指明切面,其下的< aop:before等 method="" pointcut-ref="pointcutId">
-
< aop:advisor advice-ref="" pointcut-ref="">
-
-
Spring中如果类被代理,则通过原始bBan的id获取的对象是代理Bean;Proxy动态代理类由Spring通过动态字节码技术在JVM中创建和管理
-
-
-
-
拦截器Interceptor:AOP运用
-
拦截器依赖Web框架而不依赖Servlet容器,是Spring容器内的;因此能够深入到方法前后、异常抛出前后,更具有弹性
-
基于动态代理和反射
-
可以被多次调用
-
只能拦截Controller请求,不能拦截静态资源的访问
-
能访问Spring里的任何资源和对象,如获取IoC容器中的Bean
-
与过滤器Filter的区别
-
过滤器依赖Servlet容器,只能用于Web程序,是Servlet容器内的;只在Servlet前后起作用
-
基于函数回调
-
只能在容器初始化时调用一次
-
能拦截所有请求
-
不能访问域中数据,不能获取Bean
/** * 声明本类为切面@Aspect * 声明切点@Pointcut,切在execution指定的所有方法(第一个*)处;切点是连接点的集合;spring中每一个方法称为连接点/最小单位 * 声明通知advice,@Before,@After...,属性指定切点及指定位置/切入时机,方法体为该切点的具体实现/切入内容;(有点儿类似继承了方法并重写,各通知之间为重载) * * AOP使用代理模式: * 获取bean,断点可见原生类(target,一般就为bean id)和代理类(proxy);查看源码可知代理类是在spring容器初始化时产生 */ @Aspect @Component public class SpringAOP { @Pointcut("execution(* com.zgh.springbootstudy.service.*.*(..))") private void addPointcut() { } @Before("addPointcut()") public void beforeAdvice() { // 具体的实现 } @AfterReturning("addPointcut()") public void afterReturningAdvice() { System.out.println("切点================================="); } }
-
-
1.5 数据持久化与事务管理
-
传统JDBC:太底层,即使用户执行一条最简单的数据查询操作,过程繁琐:获取连接→创建Statement→执行数据操作→获取结果→关闭Statement→关闭结果集→关闭连接,还需要异常处理
-
方式1:JdbcTemplate:Spring对JDBC的薄层封装,减少样板代码;xml中< bean>定义该类,注入它的dataSource属性;CRUD方法参数为SQL语句和动态赋值语句中?处的可变参数;queryForObject()返回想要的自定义类型数据;batchXXX()批量操作
-
方式2:Spring整合第三方ORM框架,如Mybatis,Hibernate(JPA)
-
事务:
-
编程式事务:传统事务管理:建立连接 -》 开启事务 -》异常捕获 -》无异常,则提交;有异常,则回滚;硬编码,重复且复杂
-
声明式事务:交由Spring基于AOP的事务管理
-
Spring提供事务管理器接口PlatformTransactionManager,不同框架有不同实现类,如JpaTransactionManager,DataSourceTransctionManager,HibernateTransactionManager
-
配置事务管理器:将具体事务管理器定义为Bean,且注入数据源DataSource
-
开启事务注解支持:< tx:annotation-driven transaction-manager="事务管理器">;类或方法上添加注解@Transactional;查询方法没必要事务管理
-
配置事务属性:@Transactional(参数设置)
-
隔离级别isloation:读已提交READ_COMMITTED,可重复读REPETABLE_READ(MySQL默认),串行SERIALIZABLE;ISOLATION_DEFAULT使用默认数据库隔离属性
-
传播行为propagation:REQUIRED(默认,增删改),SUPPORTS(查时推荐);一个查询模块调用了另一个模块的方法或接口,因为跨事务而可能报权限(增删改)问题,就需要将该方法的propagation设置为REQUIRED
-
是否只读readOnly:用于只有查询的业务,提高效率;默认值false
-
事务提交超时时间timeout:默认-1秒,最终由数据库指定
-
回滚机制rollbackFor/noRollbackFor:Spring默认对RuntimeException及子类采用回滚策略,对Exception及子类采用提交策略
-
-
xml方式:配置通知< tx:advice>
-
零配置/完全注解方式:配置类上@EnableTransactionManagement;类中@Bean定义数据库、JdbcTemplate和事务管理器
-
可手动指明执行事务操作:getTransaction()开启事务,rollback()回滚,commit()提交;消除JDBC的自动提交:connection.setAutoCommit(false)
-
@TransactionalEventListener:配置事务的回调方法
-
-
1.6 参数校验
-
JSR303数据校验:JavaEE6的规范提案的一项子规范:Bean Validate;而Hibernate Validator是参考实现,内置了一些实现如@Length,@Range,@Email;这两套字段校验注解说明:
注解 | 说明 |
---|---|
@Null/@NotNull | 只能/不能为null |
@NotEmpty/@NotBlank | 不为null且不能为空字符串或集合大小大于0/只针对字符串 |
@AssertTrue/@AssertFalse | 只能为true/false |
@DecimalMax(value)/@DecimalMin(value) | 不大于/不小于指定数字的数值 |
@Digits(i,f) | 只能为小数,整数部分位数最多i位,小数部分最多f位 |
@Future/@Past | 只能为将来/过去的日期 |
@Max(value)/@Min(value)/@Size(max,min) | 不大于/不小于/指定数字/指定区间的数值 |
@Pattern(regexp="") | 满足指定的正则表达式 |
邮箱地址 | |
@Length | 字符长长度限制 |
@Range | 元素限制 |
-
Bean层在属性上使用单层或多个注解组合:
/**
* @ConfigurationProperties
* 用于创建到配置文件中的映射;前提是此类被注册为了一个组件,如@Component
* 默认加载全局配置文件;如果需要加载自定义文件,使用@PropertySource指定
* @Value:
* 如果特定指定某一个属性,则使用@Value("${key}")或@Value("#{SpEL}")
* 这等同于XML配置文件写法:
* <bean class = "StudentTest">
* <property name="id" value="${key}"或value="#{SpEL}"></property>
* 松散语法绑定:
* @ConfigurationProperties,lastName等同于last-name等同于last_name且不区分大小写(无论在配置文件还是类中);不支持SpEL
* 而@Value严格遵守命名;支持"#{SpEL}"
* @ConfigurationProperties可以配合@NotEmpty等校验注解,@Value不支持
* 复杂类型封装:@ConfigurationProperties支持,@Value不支持(所以适用于项目各处需要零散取值的地方使用)
*/
// @PropertySource(value = {"classpath:userDefined.properties"})
@Component
@ConfigurationProperties(prefix = "test")
@Validated
public class User {
// 取值写法:@Value("${test.id}")
// 自定义值(表达式)写法:@Value("#{2 * 5}"),@Value("true")
private Integer id;
private String name;
private boolean max;
private Date date;
@Email(message = "邮箱地址不符合规范!")
// @NotEmpty("该字段必填!")
private String email;
}
-
控制层使用@Valid校验,并将校验结果放入BindingResult对象中,校验不通过时可获取到自定义的message错误提示信息
public ResponseResult login(@Valid User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
for (ObjectError error: bindingResult.getAllErrors()) {
return ResponseResult.failure(errror.getDefaultMessage());
}
}
}
// 此处可以抽离出来做一个全局异常处理
-
如果是单个参数校验,则可直接将校验注解写到接口参数中,如:public ResponseResult login(@NotEmpty(message = "密码必要!") String password)
-
自定义参数校验注解:
-
注意日期格式化@JsonFormat不属于Validator;将对应数据库时间输出到前端的格式化,在字段上使用注解:@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8");时间在数据库中存储为DATETIME格式,Java中使用Date类,Dao中映射如果是jdbcType=DATE,则时间的时分秒全为0,需要改映射为TIMESTAMP才能显示时分秒
-
在非控制层的参数校验:由于不能使用BindingResult,需要自定义校验器Validator;在需要校验的地方调用此校验器并传入要校验的对象即可
public static <T> void beanValidate(T object) { Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); // 初始化校验器 Set<ConstraintViolation<T>> constraintViolations = validator.validate(object); // 校验 if (CollectionUtils.isNotEmpty(constraintViolations)) { String message = constraintViolations.iterator().next().getMessage(); throw new ValidationException(message); } }
-
Assert类:异常处理与参数校验,避免过多使用Throw new ServiceException;常用方法:
-
逻辑判断:isTrue()、state()
-
对象或类型断言:notNull()、isNull()、isInstanceOf()、isAssignable()、
-
文本断言:hasLength()、hasText()、doesNotContain()
-
集合或数组判断:notEmpty()、noNullElements()
-
1.7 整合日志
-
log4j:
-
引入依赖;配置log4j.properties并设置日志级别为info,将日志输入到控制台和指定文件
log4j.rootLogger=DEBUG,console # dubug模式,控制台输出 log4j.logger.org.mybatis = DEBUG log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.Target=System.out # 标准输出 log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n # 日期格式,类名,方法名,行号,换行等
-
编写增强类LogAspect,通过连接点类ProceedingJoinPoint获取目标类名和方法,调用LOG.info()记录进入方法时日志信息,peoceed()被try-catch,且LOG.error记录异常信息
-
1.8 数据缓存
-
启用缓存支持:@EnableCaching标注配置类;对应< cache:annotation-driver>;工作方式:它们会创建aspect并触发缓存pointcut,aspect从缓存中添加和提取数据
-
如果没有使用缓存,则每次请求或者方法的调用都会查询数据库,输出语句每次都打印;使用缓存,则第一次查询数据库后,后面的相同查询从缓存中获取(以key搜索),而不会调用方法,即输出语句不打印,日志不打印
-
配置类中配置缓存管理器Bean:ConcurrentMapCacheManager:基于内存(生命周期随应用);SimpleCacheManage:;NoOpCacheManager:;CompositeCacheManager:;EhCacheCacheManager:;RedisCacheManager:SpringDataRedis提供;CompostiteCacheManager构建List,迭代使用多个管理器
-
缓存设置:不同缓存方式对应不同标签和配置;< cache>< cache name="缓存名" maxBytesLocalHeap="最大堆存储" timeToLiveSeconds="存活时间">
-
使用缓存:
-
以上标签被包裹于< chche:advice>:定义缓存通知,配合< aop:advisor advice-ref=”adciveId” pointcut>将通知应用到切点
-
@Caching:缓存注解的组合;< cachechching>
可用于类上(对所有方法作用)
-
@CacheEvict:在缓存中清除值;常用于remove();< cache:cache-evict>
-
共有属性:cache;value缓存名;condition是否将缓存应用到方法调用上,SpringEL表达式,不应用则不查找也不缓存;key通过key查找,如通过id查找的方法,key为传递到方法的id参数,SpringEL表达式自定义key,略;method;unless是否将返回值存放缓存(true为不放),之前会从缓存查找
-
@CachePut:不检查缓存,方法始终被调用,值存放到缓存;< cache:cache-put>
-
@Cacheable:标注的方法调用之前,从缓存查找方法返回值;无则方法调用并将值存储到缓存;常用于频繁请求如findOne();< cache-cacheble>
-
缓存技术:系统性能提升,业务效率提升,分担数据库操作压力
-
如数据保存到数据库,但高频数据每次查询数据库则低效,耗时,低性能
-
将热门数据/中间临时数据(如10分钟有效的验证码)保存到缓存,有则从缓存查询获取,无则再查数据库
-
JSR107:java caching(Java缓存技术,难度较大)
-
5个核心接口:
-
CachingProvider:创建、配置、获取、管理、控制CacheManager(监控CacheManager的生命周期)
-
CacheManager:监控多个但名称唯一的Cache的生命周期(类似数据库连接池,每一个Cache是一个连接)
-
cacheNames/value:Cache名
-
key:缓存使用的key,默认为方法参数的值;可自定义key生成策略
-
cacheManager:缓存器
-
-
condition:符合条件才缓存,如缓存经常登录的用户信息
-
unless:否定缓存,即默认缓存,除非条件成立时才不缓存
-
sync:异步模式
-
Cache(主要,重要):类似map的数据结构,临时存储以key为索引的值
-
Entry:存储在Cache中的key-value对
-
Expiry:缓存有效期
-
-
Spring缓存抽象:可看做降低难度的JSR107
-
@Cacheable:注解方法,根据方法参数,缓存结果,一般用于service层
-
缓存查询时,以key搜索是否存在;key存在即缓存中存在,则不需要调用方法(查数据库)了
-
@CacheEvict:清空缓存
-
allEntries:是否清楚此缓存中的所有数据
-
beforeInvocation:是否在方法执行之前执行,默认在方法之后执行清除缓存,所以当方法出现异常,缓存不会被清除
-
@CachePut:保证方法一定被调用,即先调用方法,缓存结果
-
@Cacheable的区别:同步更新
-
如执行查询-更新-查询的步骤,在更新后的查询,@Cacheable从缓存中的查询没有更新,而@CachePut的缓存中与数据库同步更新了
-
@EnableCaching:开启基于注解的缓存,用于启动类上
-
@Cache:组合缓存规则
-
-
第三方:Redis:
-
Redis可用作数据库、缓存、消息的中间件
-
-
-
-
-
异步任务:
-
启动类上开启注解@EnableAsync
-
request方法上使用注解@Async
-
-
定时任务:
-
开启注解@EnableScheduling
-
方法使用@Scheduled,指定cron属性值:(core = "")
-
-
邮件任务:
1.9 注解驱动编程
-
Spring注解发展:
-
2.0:提供基础注解,如@Component,@Service,为了简化配置但不能完全替代
-
3.0:提供更多配置,如@Configuration,@Bean,提倡注解编程
-
4.0:完全注解编程,产生SpringBoot
-
-
配置文件和注解同时存在时,配置覆盖注解,且@Bean优先级高于@Component
-
注解:
-
@Component:工厂创建对象,等同于< bean>;为了更准确表达类型及作用,衍生注解:
-
@Repository:Dao层(不整合mybatis情形)
-
@Service:Service层
-
@Controller:Controller层
-
@Configuration:配置类
-
@Bean:支持创建复杂对象;属性自定义beanId
-
-
-
@Scope:设置bean作用域
-
@Lazy:懒加载即延迟创建单例对象;默认非懒加载
-
@Conditional:判断条件,满足的才注册bean
-
@PreDestroy,@PostConstrut,@Product:JSR250提供,在bean被容器销毁之前的一些扩展工作,在Bean创建完成后的一些扩展工作
-
@Autowired:对setter()注入的封装,基于对象类型;配合@Qualifier可设置基于名字;可位于setter()或属性上;JSR250提供@Resource基于名字,且名字不匹配时再基于类型注入
-
@Value:等同于标签中的value属性,值存储在key-value形式的配置文件中,以${key}获取;不能用于静态属性
-
@PropertySource:读取配置文件件,等同< context:property-placeholder>
-
@Import:常用于导入第三方包中的组件
-
@ComponentScan:bean扫描,等同< context:component-scan>
-
包含策略:< context:include-filter type="" expression="" use-default-filters="false">
-
排除策略:< comtext:exclude-filter type="" expression="">
-
type可取值及expression指定值:
-
assignable:特定类型
-
annotation:特定注解
-
aspectj:切点表达式,expression指定包或类
-
regex:正则表达式
-
custom:自定义
-
-
-
1.10 代码
-
自定义单元测试:
// 使用Junit来做单元测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootstudyApplicationTests {
@Autowired
private DataSource dataSource;
@Test
public void testConnectDataSource() throws SQLException {
// 连接数据库测试:打印出
// class com.zaxxer.hikari.HikariDataSource;使用debug可以查看配置文件中的属性是否有效
// HikariProxyConnection@715602332 wrapping com.mysql.jdbc.JDBC4Connection@11eed657
// 即使用自带Tomcat连接池的HikariDataSource数据源;使用JDBC4Connection来连接数据源
// 可自定义连接池如c3p0,druid,数据源;源码在jdbc包下
System.out.println(dataSource.getClass());
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
// 打印出配置类中获取的值
// 测试使用配置类来注册bean组件:应用上下文中是否包含了某一个bean组件
@Autowired
private StudentTest studentTest;
/**
* spring容器是管理对象的地方:创建-装配-销毁;此控制权交由spring,因此成为IOC容器
* 核心接口:BeanFactory(生产),ApplicationContext(应用上下文,管理)
*/
@Autowired
private ApplicationContext ioc;
@Test
public void contextLoads() throws SQLException {
System.out.println(studentTest);
}
@Test
public void testUserService() {
System.out.println("添加组件:userService");
boolean exist = ioc.containsBean("userService");
System.out.println(exist);
}
// 日志测试
@Test
public void testLog() {
new LogUtils().testLog();
}
}
-
一些注解的使用:
/** * 组合注解 * @Target({ElementType.TYPE}) * @Retention(RetentionPolicy.RUNTIME) * @Documented * @Inherited * @SpringBootConfiguration:标注配置类,使用类而不是配置XML文件的方式 * @EnableAutoConfiguration:标注可自动导入组件:@AutoConfigurationPackage,@Import * 配置文件读取的原理,组件bean的装配原理,主要就在这几个注解的源码中;其实配置文件中的属性就是封装的类属性,有什么属性说明源码中都有对应属性的定义 * @ComponentScan( * excludeFilters = {@Filter( * type = FilterType.CUSTOM, * classes = {TypeExcludeFilter.class} * ), @Filter( * type = FilterType.CUSTOM, * classes = {AutoConfigurationExcludeFilter.class} * )} * ) */ // SpringBoot不自动识别XML组件配置文件,如果像spring一样自己编写配置文件,则需要@ImportResource导入; // 但Springboot推荐配置类,即使用注解:@@Configuration,@Bean // @ImportResource(locations = {"classpath:userDefined.xml"}) // 如果只需要简单的启动运行,则可加exclude值,否则需要在配置文件中配置DataSource @SpringBootApplication @EnableCaching public class SpringbootstudyApplication { public static void main(String[] args) { SpringApplication.run(SpringbootstudyApplication.class, args); } }
-
@Configuration:
/** * 向容器添加映射组件@Configuration,自定义映射Druid数据源,绑定和映射配置文件中黄色背景的属性 * Druid的优点在于监控,监控发送到数据库的SQL语句,方便排错 */ @Configuration public class DruidDataSourceConfig { @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource useDruid() { return new DruidDataSource(); } // 配置web监控、过滤器;高版本Druid可在yml中配置:url-pattern,web-stat-filter,exclusions } @Configuration public class MybatisConfig { // 用于设置实体bean的属性命名与数据库列名映射 @Bean public ConfigurationCustomizer configurationCustomizer() { return new ConfigurationCustomizer() { @Override public void customize(org.apache.ibatis.session.Configuration configuration) configuration.setMapUnderscoreToCamelCase(true); } }; } } // 指明当前类是一个配置类(显式配置),替代spring的配置文件XML(<bean id="" class=""></bean>)(显式配置) // 装配机制还有一种隐式配置(推荐):自动发现(组件扫描)和装配, @Configuration public class UserConfig { // 将方法返回值添加到容器,容器中此组件id默认为方法名; // 这种方式常用于装配第三方bean;即此处直接在UserService类上使用@Service即可 @Bean public UserService userService() { return new UserServiceImpl(); } }
-
权限校验:
/** * 安全,权限校验,验证 * 1、控制请求的访问权限 */ // 需要编写配置类或配置文件;此注解已包含@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // super.configure(http); } }
-
扩展SpringMVC:
/** * 扩展springMVC:可以理解为springboot独特的controller层 * 因此:springboot即保留了springMVC,也能使用自己的配置类 * 使用@EnableWebMvc全面接管springMVC,即对springMVC的自动配置controller不再有效(核心@Import失效),包括静态资源都访问不了 */ // @EnableWebMvc @Configuration public class WebMVCConfig implements WebMvcConfigurer { // ctrl+o打开本类拥有的所有方法(包括继承的),如此快速导入需要重写的父类方法 @Override public void addViewControllers(ViewControllerRegistry registry) { // 参数1为请求的URL,参数2位返回值,即资源文件HTML名; // 这等同于controller中自定义的@RequestMapping等标注的方法;页面同理可访问;是为扩展 // 因此,一条语句便可代替controller中一个方法;这通常用于返回首页等方法体不复杂的controller替换 registry.addViewController("success").setViewName("ymldefinetest"); registry.addViewController("/").setViewName("index"); // registry.addViewController("/login.html").setViewName("login"); } @Bean public LocaleResolver localeResolver() { return new I18NResolverUtil(); } } @RestController public class EmployeeController { @Autowired private EmployeeMapper employeeMapper; /** * get请求的两种参数设置方式: * 1、将参数作为路径的一部分,如下实现; * 2、将参数拼接在问号?后面,多个参数用&符号;使用的注解@RequestParam */ @GetMapping("/employee/{id}") public Employee getEmployee(@PathVariable("id") Integer id) { return employeeMapper.queryEmployeeById(id); } @GetMapping("/employees") public List<Employee> getEmployees() { return employeeMapper.queryEmployees(); } // 使用了get方式,根据SQL语句,在页面上输入/employee?name=xxx来完成请求发送 @GetMapping("/employee") public Employee addEmployee(Employee employee) { employeeMapper.addEmployee(employee); return employee; } }
-
原生JDBC连接和操作数据库:
/**
* 使用原生JDBC连接和操作数据库
*/
@Controller
public class JDBConnectController {
@Autowired
private JdbcTemplate jdbcTemplate;
@ResponseBody
@GetMapping("/queryUser")
public Map<String, Object> queryUser() {
String sql = "select * from user";
List<Map<String, Object>> users = jdbcTemplate.queryForList(sql);
return users.get(0);
}
}
-
AOP:
/**
* 声明本类为切面@Aspect
* 声明切点@Pointcut,切在execution指定的所有方法(第一个*)处;切点是连接点的集合;spring中每一个方法称为连接点/最小单位
* 声明通知advice,@Before,@After...,属性指定切点及指定位置/切入时机,方法体为该切点的具体实现/切入内容;(有点儿类似继承了方法并重写,各通知之间为重载)
*
* AOP使用代理模式:
* 获取bean,断点可见原生类(target,一般就为bean id)和代理类(proxy);查看源码可知代理类是在spring容器初始化时产生
*/
@Aspect
@Component
public class SpringAOP {
@Pointcut("execution(* com.zgh.springbootstudy.service.*.*(..))")
private void addPointcut() {
}
@Before("addPointcut()")
public void beforeAdvice() {
// 具体的实现
}
@AfterReturning("addPointcut()")
public void afterReturningAdvice() {
System.out.println("切点=================================");
}
}
-
Log4J,slg4j日志:
/**
* log4j为先出现的日志记录工具(实现层),后来出现slg4j(抽象层)
* 简单使用一些接口,则slf4j即可;一些复杂的实现,则同时导入各自API设置中间的适配器
* spring底层日志使用commons-logging,hibernate使用
* springboot使用slf4j+logback(实现层);
* 统一日志使用:
* spring、hibernate框架移除(不导入)自己的日志,如springboot的maven中引入spring-core的时候就有<exclusion>把commons-logging移除了
* 使用中间包替换(适配器模式;否则启动失败,因为没有导入),转为slf4j
* logback-classic.jar,jul-to-slf4j.jar,log4j-over-slf4j.jar;它们的源码中都是实例化的slf4j生产工厂对象或者调用其接口
* 使用slf4j及其实现
*/
public class LogUtils {
final Logger LOGGER = LoggerFactory.getLogger(getClass());
/**
* 日志级别依次升高;springboot默认info,即只会输出info及以后的;
* 配置文件配置:
* logging.level:指定文件的日志级别
* logging.path:指定日志生成的路径,springboot默认日志文件名spring.log
* logging.file:指定日志生成文件,包含了路径,当前相对路径为项目下
* logging.console:指定日志输出格式
*/
public void testLog() {
LOGGER.trace("===trace: ");
LOGGER.debug("===debug: ");
LOGGER.info("===info: ");
LOGGER.warn("===warn: ");
LOGGER.error("===error: ");
}
}
-
针对实体Bean的一些注解:
/**
* @ConfigurationProperties
* 用于创建到配置文件中的映射;前提是此类被注册为了一个组件,如@Component
* 默认加载全局配置文件;如果需要加载自定义文件,使用@@PropertySource指定
* @Value:
* 如果特定指定某一个属性,则使用@Value("${key}")或@Value("#{SpEL}")
* 这等同于XML配置文件写法:
* <bean class = "StudentTest">
* <property name="id" value="${key}"或value="#{SpEL}"></property>
* 松散语法绑定:
* @ConfigurationProperties,lastName等同于last-name等同于last_name且不区分大小写(无论在配置文件还是类中);不支持SpEL
* 而@Value严格遵守命名;支持"#{SpEL}"
* JSR303数据校验(JavaEE6的规范提案的一项子规范:Bean Validate;Hibernate Validator是参考实现,内置了一些实现如@Null,@Max,@Email):
* @ConfigurationProperties可以配合@NotEmpty等校验注解,@Value不支持
* 复杂类型封装:@ConfigurationProperties支持,@Value不支持(所以适用于项目各处需要零散取值的地方使用)
*/
// @PropertySource(value = {"classpath:userDefined.properties"})
@Component
@ConfigurationProperties(prefix = "test")
@Validated
public class StudentTest {
// 取值写法:@Value("${test.id}")
// 自定义值(表达式)写法:@Value("#{2 * 5}"),@Value("true")
private Integer id;
private String name;
private boolean max;
private Date date;
@Email
// @NotEmpty
private String email;
// ...
}
-
消息队列:
/**
* 消息服务/队列:通过消息服务中间件来提升系统异步通信、扩展解耦能力、削峰
* 原来为程序A向其他N个程序提供服务如提供数据;使用中间件后,即中间插入一个MQ,A只负责把数据发送到MQ,需要数据的自行去MQ中获取消费
* A不再关心向谁提供数据,不关心目标是否连接成功,调用成功,失败超时等问题;适用于系统间相互依赖和频繁调用的场景
*
* 消息代理:消息发送者发送的消息被消息代理接管
* 目的地:消息代理保证消息传递到指定的目的地;消息队列的两种目的地:
* 队列:点对点通信
* 消息有唯一发送(发布)者;消息在队列中,可以拥有多个接收(消费)者,但一旦被某一个接收者获取,则消息从队列移除即消息有唯一接受(消费)者
* 如商品秒杀:将用户秒杀成功的信息保存到队列,直到队满;然后慢慢处理队列中的消息,因为每个消息对应唯一消费者
* 主题:发布和订阅通信
* 消息被发布成为主题,任意消费者可以订阅(监听)此主题,然后消费者都会消费此消息
* 如短信、邮件发送,消费者都订阅了短信主题,邮件主题,消费者都能获取短信,邮件消息
*
* JMS:Java消息服务(j2ee消息规范):基于JVM消息代理的规范(API,即Java语言,Java平台);
* 支持的消息类型:text,map,bytes,stream,object
* ActiveMQ实现:
* HornetMQ实现:
*
* AMQP:高级消息队列协议(协议,即跨语言,跨平台),兼容JMS;更细分了两种目的地为5种
* 支持的消息类型:将消息序列化后以byte[]发送
* RabbitMQ实现:
* Message:消息头+消息体,消息头可选属性routing-key(路由键),priority(优先权),delivery-mode(是否持久化)
* Publisher/Producer:消息生产者,向交换器发布消息的客户端程序
* Exchange:交换器,接收消息并根据Binding把消息路由给服务器中的队列;4中类型(转发策略不同):
* direct(默认):点对点,routing-key与Binding-key严格相同
* fanout(最快):不考虑routing-key,散发消息到与该交换器绑定的所有队列
* topic(模糊匹配):模糊匹配routing-key与Binding-key
* headers():
* Queue:消息队列,保存消息直到消息被消费;一个消息可投入多个队列
* Binding:绑定,消息队列与交换器的关联关系和路由规则,可多对多
* Connection:网络连接,服务器与消费者之间的通信连接,如TCP连接
* Channel:信道,网络连接中划分多条通信链路,为节省资源,复用链路
* Consumer:消费者
* Virtual Host:虚拟主机,服务器中相对独立的服务器域,包含一批交换器,消息队列和相关对象;服务器相当于多个虚拟主机组成
* Broker:服务器
*
* 使用:spring引入相关依赖包
*/
2 Spring + JDBC
-
数据访问对象DAO实现/委托Repository接口;spring提供大量继承自DataAccessException的数据库操作异常体系;spring根据数据的固定和可变,将类分为模板和回调;模板类如JdbcTemplate处理数据访问的固定部分(事务管理,资源管理和异常处理);回调实现数据访问(语句,参数绑定,处理结果集)
-
配置数据源:
-
NDI数据源:
-
此种配置让数据源在应用程序之外进行处理,仅在访问时查找即可;JndiObjectFactoryBean实现查找dataSource;对应jee命名空间<jee:jndi-lookup id=”dataSource” jndi-name=”源名” resource-ref=”true”>检索任何JNDI对象及数据源
-
-
JDBC数据源(每次请求都会新建连接,费性能):spring自带的DriverManagerDataSource/SimpleDriverDataSource/SingleConnectionDataSource(单线程)
-
连接池数据源(推荐):DBCP/c3p0/JDBC的<bean id class="….dbcp.BasicDataSource”">;<driverClassName,url,username,password,initalSize,maxActive…>对应实现java类及setter()
-
嵌入式数据库
-
@Profile()运行时选择数据源;属性值”development”使用开发数据源;”qa”QA数据源;”production”生产环境数据源;对应< bean profile="">
-