1.Spring Core
IoC
IoC容器:BeanFacotry 和 applicationContext的本质区别
- BeanFactory: 懒加载
- applicationContext:非懒加载(可以指定为懒加载)
1.控制反转
什么是控制反转:
例子: 对象A依赖对象B,当对象A需要对象B的时候必须自己去创建。
但是,当系统引入IOC容器后,对象A和对象B之间就失去了联系。
此时,当对象A需要使用对象B的时候,可以指定IOC容器去创建一个对象B注入到对象A中
对象a获取对象b的过程,由主动行为变成了被动行为==>控制权翻转,这就是控制反转
把对象的控制权交给第三方
2.依赖注入
DI(Dependecy Inject,依赖注入)是实现控制反转的一种设计模式,依赖注入就是将实例变量传入到一个对象中去。
3.IoC容器
使用IOC之前:各个资源之间相互依赖!
这样各个对象之间相互依赖,耦合度非常高。对象使用其他资源必须考虑其他资源的存在
引入IOC
将对象的依赖都交给IOC容器,对象只需要通过第三方ioc容器来获取到其他对象即可!
大大降低了耦合度,也提高了资源管理能力
ioc核心思想:资源不由使用资源的双方管理,而由第三方管理(不使用资源)
4.好处
- 资源集中管理,实现资源的可配置和易于管理
- 降低了使用资源双方的依赖程度(耦合度)
===>工程模式
Bean
Bean创建
1.创建方式
XML
<bean id="xxx" class="com.roderick.xxx.xxx" name="xxx">
...
</bean>
注解
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.roderick.pojo"/>
<!--引入注解-->
<context:annotation-config/>
- @ComponentScan扫描器
- @Confifiguration表明该类是配置类
- @Component 指定把⼀个对象加⼊IOC容器—->@Name也可以实现相同的效果【⼀般少⽤】
- @Repository 作⽤同@Component; 在持久层使⽤
- @Service 作⽤同@Component; 在业务逻辑层使⽤
- @Controller 作⽤同@Component; 在控制层使⽤
- @Resource 依赖关系
- 如果@Resource不指定值,那么就根据类型来找,相同的类型在IOC容器中不能有两个
- 如果@Resource指定了值,那么就根据名字来找
JavaConfig
@Configuration
public class Configuration {
@Bean
public UserDao userDao() {
UserDao userDao = new UserDao();
System.out.println("我是在configuration中的"+userDao);
return userDao;
}
}
2.创建细节
scope
指定scope属性,确定是单例还是多例
- singleton 单例 ==>IoC容器之前就被创建
- prototype 多例. ==>使用时才被创建
Lazy-init
lazy-init只对 单例 对象有效
默认为false
声明为true后就会在使用时才创建对象
Method
声明对象时,指定创建时执行的方法和销毁时执行的方法
- init-method
- destroy-method
<bean id="user" class="User" scope="singleton" lazy-init="true" init-method="" destroy-method=""/>
3.注入方式
1.构造函数注入
<!--创建userDao对象-->
<bean id="userDao" class="UserDao"/>
<!--创建userService对象-->
<bean id="userService" class="UserService">
<!--要想在userService层中能够引⽤到userDao,就必须先创建userDao对象-->
<constructor-arg index="0" name="userDao" type="UserDao" ref="userDao"></constructor-arg>
</bean>
2.Set方法注入
public class UserService {
private UserDao userDao;
//通过Set方法给属性赋值
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
<!--创建userDao对象-->
<bean id="userDao" class="UserDao"/>
<!--创建userService对象-->
<bean id="userService" class="UserService">
<!--property节点给属性赋值-->
<property name="userDao" ref="userDao"/>
</bean>
4.自动装配
以上的几种注入方式都需要我们手动进行,Spring也有提供自动装配的方式
1.byName
<bean id="userDao" class="UserDao"/>
<!--
1.通过名字来⾃动装配
2.发现userService中有个叫userDao的属性
3.看看IOC容器中没有叫userDao的对象
4.如果有,就装配进去
-->
<bean id="userService" class="UserService" autowire="byName"/>
2.byType
<bean id="userDao" class="UserDao"/>
<!--
1.通过名字来⾃动装配
2.发现userService中有个叫userDao的属性
3.看看IOC容器UserDao类型的对象
4.如果有,就装配进去
-->
<bean id="userService" class="UserService" autowire="byType"/>
3.注解
@Autowired注解来实现⾃动装配:
- 可以在构造器上修饰
- 也可以在setter⽅法上修饰
//如果显示的定义了Autowired属性为false,说明这个对象可以为null。否则不允许为空
@Autowired(required = false)
private Cat cat;
@Autowired
private Dog dog;
PS: @Resource也有相同的功能,支持先byName后byType
Bean生命周期
1.简介
普通Java对象和Spring管理的对象在实例化上有所不同
普通Java对象:
- java文件被编译为class文件
- 等到类需要时进行【实例化加载】(new、反射)
- class文件被类加载器加载到JVM
- 初始化对象
简单的说,就是通过Class【模版】创建出具体实例的过程
但是!Spring管理的对象仅通过Class文件是无法完整的创建出来的。
Class描述了类信息,但是Spring对象还包含了一些其他的信息
例如:bean作用域(Scope)、bean的注入模型、bean是否懒加载等等
所以这里就需要使用BeanDefinition。用来描述Spring Bean的其他信息
BeanDefinition
是Spring Framework中定义Bean的配置元信息接口,包含:
- Bean的类明(全限定名)
- Bean行为配置元素:作用域、自动装配的模式、生命周期回调
- 其他Bean引用:又称为【合作者、依赖】Collaborators/Dependencies
- 配置信息,例如Bean属性(Properties)
读取配置/注解
- Spring启动—>扫描【XML/注解/JavaConfig】中的Bean信息(Scope、Lazy…)
- 将扫描到的Bean封装成BeanDefinition 【使用
BeanDefinitionParser
接口的parse
方法】 - 放入beanDefinitionMap中(key:beanName, value:BeanDifinition)
- 遍历beanDefinitionMap执行BeanFactoryPostProcessor(Bean工厂)的后置处理器逻辑
该接口可以在bean实例化前【获取bean信息,且可以修改(例如singleton->prototype)】
2.生命周期
—————————————————————————————————————-【实例化前】—————————————————————————————————————-
- 执行InstantiationAwareBeanPostProcessor的
postProcessBeforeInstantiation
- 该方法在实例化前调用,允许开发者自定义逻辑:比如返回一个代理对象
注意:如果这个阶段返回了一个不为null的实例,spring将会中断后续流程
- 该方法在实例化前调用,允许开发者自定义逻辑:比如返回一个代理对象
——————————————————————————————————————【实例化】——————————————————————————————————————
通过反射选择合适的构造器( 工厂、有参、无参)进行实例化!
此时,还未注入属性。例如UserService中依赖的属性SendService还是null
实例化后的对象被包装在BeanWrapper对象中
—————————————————————————————————————【实例化结束】——————————————————————————————————————
- 执行InstantiationAwareBeanPostProcessor的
postProcessAfterInstantiation
🌟🌟实例化后、执行赋值操作之前。其返回值为boolean,返回false时可以阻断属性赋值阶段
———————————————————————————————————————【初始化前】——————————————————————————————————————
- 【属性赋值】(@AutoWired发生在这个阶段)
- 通过名称查找依赖
- 通过类型查找依赖
- 判断Bean是否实现了Aware相关接口,如果存在则填充相关资源
- BeanPostProcessor处理器==>
postProcessBeforeInitialization
它的before方法在初始化前后执行
———————————————————————————————————————-【初始化】——————————————————————————————————————
- 执行初始化相关方法,包括(@PostConstruct、实现
InitializingBean
接口的、定义的init-method
方法)
———————————————————————————————————————-【初始化后】——————————————————————————————————————
- 执行BeanPostProcessor的初始化后处理==>
postProcessAfterInitialization
- 在该方法可以进行bean的实例化后处理,比如【自定义注解切换依赖对象的版本】
- 【AOP就是实现该接口来进行代理逻辑的】是实现AOP的关键🌟🌟
- 创建AOP代理对象的子类是AbstractAutoProxyCreator==>实现了该后置处理器方法!
- 之后就可以获取对象使用了
—————————————————————————————————————————-【销毁】——————————————————————————————————————
- 对象销毁时执行相关destroy方法
流程图
总结
- Spring使用
BeanDefinition
装载Bean相关的元数据 - 实例化Bean时遍历
BeanDefinitionMap
- 实例化和属性赋值是分开两步做的
- Bean生命周期有许多可扩展的点
- 实例化前:
BeanFactoryPostProcessor
- 实例化阶段:
InstantiationAwareBeanPostProcessor
- 实例化后:
Aware
相关接口【获得资源】 - 围绕初始化阶段:
BeanPostProcessor
【AOP关键】 - 初始化阶段有各种
init方法
可以自定义
- 实例化前:
3.归纳
Spring Bean的生命周期主要包括四个阶段:
- 实例化 Instantiation ==> 【通过反射获取构造方法创建实例】
- 属性赋值 Populate ==> 【
PopulateBean()
对bean依赖的属性进行填充】 - 初始化 Initialization ==> 【填充属性、将初始化好的bean放入spring缓存】
- 销毁 Destruction ==> 【将bean移除容器】
实例化 和 属性赋值 对应构造方法和setter方法的注入,初始化 和 销毁 是用户能自定义拓展的两个阶段
生命周期过程大致源码如下:doCreateBean方法
// 忽略了无关代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// 实例化阶段!
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 属性赋值阶段!
populateBean(beanName, mbd, instanceWrapper);
// 初始化阶段!
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
至于销毁,是在容器关闭时调用的,详见ConfigurableApplicationContext#close()
拓展点
Spring Bean生命周期中还有多个可拓展的点!
【运行开发者自定义拓展bean的初始化过程!】
Processor
- BeanPostProcessor🌟
- InstantiationAwareBeanPostProcessor
instantiationAwareBeanPostProcessor作用于实例化阶段的前后,BeanPostProcessor作用于初始化阶段的前后。
Aware
Aware类型的接口的作用就是让我们能够拿到Spring容器中的一些资源。基本都能够见名知意,Aware之前的名字就是可以拿到什么资源,例如BeanNameAware
可以拿到BeanName,以此类推。
4.三级缓存
循环依赖问题
Spring如何解决循环依赖问题?
有一个A对象,属性是B对象,而B对象的属性也是A对象
A依赖B,B依赖A
由SpringBean生命周期可知,对象属性的注入是在实例化之后进行的
大致过程:
- A对象实例化==>进行属性注入:发现依赖B对象
- 此时B对象还未实例化==>进行B对象实例化
- B对象实例化==>进行属性注入:发现依赖A对象【A对象以实例化,直接注入】
- 回到A对象的注入阶段==>注入实例化完毕的B对象
三级缓存
三级缓存对应三个Map
- singletonObject 【一级缓存,日常实际获取Bean的地方】==>存放单例Bean
- earlySingletonObjects【二级缓存,已实例化,还没进行属性注入】==>存储提前暴露的bean,解决循环依赖
- singletonFactories【三级缓存,Value是一个对象工厂】==>返回正在实例化的Bean,包括处理AOP代理
利用循环依赖的例子:
A对象实例化后,属性注入之前 会被放入【三级缓存】(key:BeanName,value:ObjectFactory)
A对象进行属性注入时,发现依赖B==>去实例化B
B属性依赖A需要获取A对象==>从三级缓存中获取【如果有AOP增强,三级缓存的ObjectFactory也能返回代理】
获取到A对象后,A对象就从三级缓存走到了二级缓存(避免多次执行ObjectFactory的方法)
等到完全初始化后,就会被放入一级缓存
获取Bean的逻辑=>getSingleton
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//先去一级缓存拿。新创建的bean,这里一定拿不到
Object singletonObject = this.singletonObjects.get(beanName);
//拿不到初始化完成的bean,且该bean正在被创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//优先去二级缓存拿,如果没有再去三级缓存拿。有了,就直接返回。
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//最后一步,去三级缓存拿
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//调用三级缓存ObjectFactory的getObject得到提前暴露的对象。
singletonObject = singletonFactory.getObject();
//放到二级缓存中,然后删除三级缓存。可见:同一个提前暴露的bean,只能要么在三级缓存,要么在二级缓存。
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
源码
三级缓存到底做了啥?
如图我们可以看到,在生命周期主要逻辑doCreateBean
方法中,在初始化前的阶段,将对象包装成ObjectFactory传给了addSingletonFacotry
方法!
addSingletonFacotry
方法主要就是把bean封装成ObjectFactory放到三级缓存protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { //bean已经被其他线程初始化完成放到一级缓存了,这里也没必要放到三级缓存 if (!this.singletonObjects.containsKey(beanName)) { //放到三级缓存,然后删除二级缓存(以防有值) this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }}
但是三级缓存具体做了什么呢?查看你们内部类
ObjectFactory
的实现:new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); }}
继续查看
getEarlyBeanReference
方法!
我们可以看到!在该方法中,有使用SmartInstantiationAwareBeanPostProcessor接口
进行特殊处理!!protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { //对于有SmartInstantiationAwareBeanPostProcessor,特殊处理🌟🌟🌟 for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { //特殊处理==>拓展点 SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; //调用它的实现方法getEarlyBeanReference exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); if (exposedObject == null) { return null; } } } } return exposedObject;}
最后我们查看它的实现方法getEarlyBeanReference发现:
该方法和**AbstractAutoProxyCreator.postProcessAfterInitialization**
简直一模一样!!!
这说明,在这个尚未初始化的阶段就提前进行了aop代理!!!==>一般是初始化后postProcessAfterInitialization
方法进行的AOP@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (!this.earlyProxyReferences.contains(cacheKey)) { this.earlyProxyReferences.add(cacheKey); } return wrapIfNecessary(bean, beanName, cacheKey);}
总结
为啥需要三级缓存:如果你依赖的对象是AOP代理,那么就需要用到第三级缓存暂存ObjectFactory。
三级缓存做getEarlyBeanReference
方法中:
对有SmartInstantiationAwareBeanPostProcessor
接口的对象做特殊处理
进行AOP代理【提前进行】【三级缓存存的是ObjectFactory,可获得代理对象】
AOP
1.介绍
AOP (Aspect Orient Programming) => 面向切面编程 【在方法前后增加非业务代码】
AOP是一种编程思想,是面向对象编程(OOP)的一种补充。
在面向对象中一些公共的行为,像日志记录,权限验证等如果都使用面向对象来做,会在每个业务方法中都写上重复的代码,造成代码的冗余。而AOP指的是面向切面编程,定义一个切面,用切面去切相应的方法,就可以织入相关的逻辑
横切关注点:给业务提供其他功能支持的模块
2.作用
在业务中,总有一些与业务没有直接关系,却为业务提供某些功能支持的模块【如日志、安全…】
比如执行一个方法时,在控制台打印一下日志
- 我们一般都想要业务专注于自己的功能,避免去集成这些与业务没有直接关系的功能!
- AOP就能够帮助我们将【这些模块】与【业务功能】分离出来。
使用AOP的好处:
- 减少重复代码(不需要每个业务都写一条业务功能)
- 降低模块间的耦合(将日志、安全等模块以AOP的形式独立出来)
- 有利于未来的可扩展性、可维护性
3.术语
- 通知(Advice):描述了切面何时执行以及如何执行增强处理。
- 前置通知 Before
- 后置通知 After
- 返回通知 After-returning
- 异常通知 After-throwing
- 环绕通知 Around
连接点( Join point):应用执行过程中能够插入切面的一个点(方法执行、异常抛出..)
在AOP中,连接点总是 方法的执行
切点(PointCut) :可以插入增强处理的连接点。
例子: execution( com.roderick.service.UserServiceImpl.(..))
切面(Aspect):是通知和切点的结合。
例如自定义的一个AOP类
4.代理
Spring AOP的底层技术就是动态代理!
代理模式:静态代理、JDK动态代理、CGLib动态代理
静态代理指的是代理类是在编译期就存在的,相反动态代理则是在程序运行期动态生成的,
什么是代理模式?
之前A类自己做一件事,在使用代理之后,A类不直接去做,而是由A类的代理类B来去做。
代理类其实是在之前类的基础上做了一层封装。
静态代理
在程序运行之前,代理类和被代理类的关系已经确定。
公共的接口
/** * 公共的接口 */public interface User { //接口定义两个方法 void save(); void find();}
普通类【被代理类/代理目标】
/** * 普通类【被代理类/代理目标类】 * 实现公共的接口 */public class UserDao implements User { public void save() { System.out.println("模拟保存对象"); } public void find() { System.out.println("模拟查找对象"); }}
不实用代理模式==>直接new普通类使用
/** * 不实用代理模式的情况 */public class Test01 { public static void main(String[] args) { //直接new普通类 UserDao userDao = new UserDao(); userDao.save(); userDao.find(); }}
创建代理类
/** * 代理类 * 实现同样的接口==>并且可以在方法上做一些增强 * 仍然是调用【被代理类】实现方法 */public class UserProxy implements User{ private UserDao user = new UserDao(); public void save() { System.out.println("========模拟保存前增强==========="); user.save(); System.out.println("========模拟保存后增强==========="); } public void find() { System.out.println("========模拟查找前增强==========="); user.find(); System.out.println("========模拟查找后增强==========="); }}
使用代理模式:
/** * 使用代理模式的情况 */public class Test02 { public static void main(String[] args) { UserProxy userProxy = new UserProxy(); userProxy.save(); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); userProxy.find(); }}
结论
对比,使用静态代理和不使用静态代理,可以发现使用了代理之后,可以在被代理方法的执行前或后加入别的代码,实现诸如权限及日志的操作。
但静态代理也存在一定的问题,如果被代理方法很多,就要为每个方法进行代理,增加了代码维护的成本。有没有其他的方式可以减少代码的维护,那就是动态代理。
JDK动态代理
JDK提供的动态代理需要实现 InvocationHandler
和invoke
方法
/** * 动态代理类 * 需要实现 InvocationHandler 且实现 invoke方法 */public class DynamicProxyDemo implements InvocationHandler { //被代理类的实例【沿用上个例子】 private User user = null; public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("======动态代理Start======"); Object result = method.invoke(user,args); //使用method就可以实现不同方法的代理增强 System.out.println("======动态代理End======"); return result; //方法返回值 } //构造方法【通过它传入被代理类】 public DynamicProxyDemo(User user){ this.user = user; }}
使用动态代理
/** * 使用动态代理 */public class Test01 { public static void main(String[] args) { UserDao user = new UserDao(); //传入【被代理类】得到【动态代理类】 DynamicProxyDemo proxy = new DynamicProxyDemo(user); //通过反射的方法【传入被代理类和动态代理类】得到一个代理类对象 User userProxy = (User) Proxy.newProxyInstance(user.getClass().getClassLoader(),user.getClass().getInterfaces(), proxy); userProxy.save(); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>"); userProxy.find(); }}
结论
代理类是由Proxy这个类通过newProxyInstance方法动态生成的,
生成对象后使用“实例调用方法”的方式进行方法调用,那么代理类的被代理类的关系只有在执行这行代码的时候才会生成,因此成为动态代理。
缺点:
被代理类必须要有实现的接口
如没有接口则无法使用JDK动态代理(从newProxyInstance方法的第二个参数可得知,必须传入被代理类的实现接口)
CGLib
CGLib动态代理需要引入第三方类库
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_3</version></dependency>
- 定义CGLib代理内容
import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;/** * 使用CGLib需要实现其MethodInterceptor接口 */public class MyMethodInterceptor implements MethodInterceptor { public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("开始CGLib动态代理"); Object result = methodProxy.invokeSuper(object, args); System.out.println("结束CGLib动态代理"); return result; }}
- 使用
public class Test01 { public static void main(String[] args) { //获得cglib类库的enhancer Enhancer enhancer = new Enhancer(); //CGLib的核心思想:用一个类去继承【被代理类】 enhancer.setSuperclass(UserDao.class); enhancer.setCallback(new MyMethodInterceptor()); UserDao user = (UserDao) enhancer.create(); user.save(); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>"); user.find(); }}
结论
CGLib动态代理是一个第三方实现的动态代理类库
不要求被代理类必须实现接口,它采用的是继承被代理类,使用其子类的方式
弥补了被代理类没有接口的不足。
总结AOP
Spring AOP就是基于动态代理的,有两种情况:
- 如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象
- 而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理
5.例子
用wiki项目的方法请求时的日志功能做例子:
@Aspect@Componentpublic class LogAspect { private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class); /** 定义一个切点 */ @Pointcut("execution(public * com.roderick.controller..*Controller.*(..))") public void controllerPointcut() {} @Resource private SnowFlake snowFlake; @Before("controllerPointcut()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 增加日志流水号 MDC.put("LOG_ID", String.valueOf(snowFlake.nextId())); // 开始打印请求日志 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Signature signature = joinPoint.getSignature(); String name = signature.getName(); // 打印请求信息 LOG.info("------------- 开始 -------------"); LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod()); LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name); LOG.info("远程地址: {}", request.getRemoteAddr()); //获取远程ip并放到线程本地变量 RequestContext.setRemoteAddr(getRemoteIp(request)); // 打印请求参数 Object[] args = joinPoint.getArgs(); LOG.info("请求参数: {}", JSONObject.toJSONString(args)); Object[] arguments = new Object[args.length]; for (int i = 0; i < args.length; i++) { if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) { continue; } arguments[i] = args[i]; } // 排除字段,敏感字段或太长的字段不显示 String[] excludeProperties = {"password", "file"}; PropertyPreFilters filters = new PropertyPreFilters(); PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter(); excludefilter.addExcludes(excludeProperties); LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter)); }}
6.创建流程
AOP代理对象创建流程
众所周知,Spring AOP对象的初始化是在 Bean【初始化】流程之后的后置处理中进行的!postProcessAfterInitialization
初始化
/** * bean的初始化流程*/protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { //省略。。。 Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // 调用BeanPostProcessor的postProcessBeforeInitialization前置处理方法 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { // 执行bean初始化方法 invokeInitMethods(beanName, wrappedBean, mbd); } //省略。。。 if (mbd == null || !mbd.isSynthetic()) { // 调用BeanPostProcessor的postProcessAfterInitialization后置处理方法 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean;}
后置处理器
进入到后置处理器方法:
@Overridepublic Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; // 为Bean添加所有BeanPostProcessor的后置处理器 for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { Object current = beanProcessor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result;}
主要是为Bean添加所有后置处理器 【也有自定义的(本身就是提供拓展的)】
BeanPostProcessor的bean初始化前置处理和初始化后置处理方法均委派其子类实现,其实现子类有很多
子类实现
关键!
创建AOP代理对象的子类是AbstractAutoProxyCreator,它实现了postProcessAfterInitialization方法
其实现的方法:
@Overridepublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException { if (bean != null) { // 根据给定的bean的class和name构建一个key Object cacheKey = getCacheKey(bean.getClass(), beanName); // 如果它适合被代理,则需要封装指定的bean if (!this.earlyProxyReferences.contains(cacheKey)) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean;}
如果需要被代理,则封装该bean!==>wrapIfNecessary()
wrapIfNecessary()方法:如果存在增强方法则创建代理
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { // 如果已经处理过了,直接返回 if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } // 无需增强 if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } // 判断是否是基础设施类,或者是否配置了无需自动代理。如果是,缓存key并直接返回 if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } // Create proxy if we have advice. // 如果存在增强方法则创建代理 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); // 创建代理 Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean;}
创建代理
创建代理==>createProxy()
protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) { ...... return proxyFactory.getProxy(getProxyClassLoader());}
proxyFactory.getProxy():
从图中可以看到提供了两种方式
不同方式
进入createAopProxy()方法,发现这个方法最终在DefaultAopProxyFactory类中被实现。
@Overridepublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { // isOptimize:用来控制通过CGLIB创建的代理是否使用激进的优化策略 // isProxyTargetClass:为true时,目标类本身被代理而不是目标类的接口,即使用CGLIB创建代理 // hasNoUserSuppliedProxyInterfaces:是否存在代理接口 if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } // 如果是接口或是一个代理对象就要jdk动态代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); }}
主要逻辑:
- 如果目标对象实现了接口,默认情况下会采用JDK动态代理,
- 但也可以通过配置(proxy-target-class=true)强制使用CGLIB。
- 如果目标对象没有实现接口,必须采用CGLIB库。
Spring DAO
事务
一般事务控制都是在Service层做的,因为:
- service层是业务逻辑层,service的⽅法⼀旦执⾏成功,那么说明该功能没有出错。
- ⼀个service⽅法可能要调⽤dao层的多个⽅法.如果事务在DAO层,则只会回滚一个DAO方法
1.配置方式
编程式事务管理
Jdbc代码:Conn.setAutoCommite(false);
编程式事务管理是侵入性事务管理==>在代码内手动开启事务
- 优点:细粒度的事务控制: 可以对指定的⽅法、指定的⽅法的某⼏⾏添加事务控制
- 缺点:开发比较繁琐,和业务代码混淆!较复杂
声明式事务管理
声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式Spring提供了对事务控制的实现==>基于Spring AOP实现
- 耦合度低+配置简单
- 粗粒度,只能给整个方法应用事务,不能针对几行代码
2.使用声明式
1.XML
<!--1.配置事务的管理器类:JDBC--><bean id="txManage" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--引⽤数据库连接池--> <property name="dataSource" ref="dataSource"/></bean><!--2.配置如何管理事务--><tx:advice id="txAdvice" transaction-manager="txManage"> <!--配置事务的属性--> <tx:attributes> <!--所有的⽅法,并不是只读--> <tx:method name="*" read-only="false"/> </tx:attributes></tx:advice><!--3.配置拦截哪些⽅法+事务的属性--><aop:config> <aop:pointcut id="pt" expression="execution(* bb.UserService.*(..) )"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor></aop:config>
2.注解
开启注解支持
==>使用@Transactional
@Transactionalpublic void save() { userDao.save(); int i = 1 / 0; userDao.save();}
注解也可以添加属性==>相对于事务管理器内容
- 传播性配置:@Transactional(propagation=Propagation.REQUIRED)
- 隔离性配置:@Transactional(isolation = Isolation.READ_UNCOMMITTED)
3.事务属性
配置事务管理器内容,其实就是在指定事务的属性!
事务的属性有哪些:
1.事务传播行为
事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。
常用传播行为属性:
- Propagation.REQUIRED【如果外层有事务了,加⼊当前该事务,一起提交一起回滚】🌟🌟【默认】
- Propagation.REQUIRED_NEW【每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行】
2.事务隔离级别
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 读未提交 |
ISOLATION_READ_COMMITTED | 读提交 |
ISOLATION_REPEATABLE_READ | (MYSQL默认级别)可重复读 |
ISOLATION_SERIALIZABLE | 串行化 |
3.回滚规则
在默认设置下,
- 事务只在出现运行时异常(runtime exception)时回滚,
- 而在出现受检查异常(checked exception)时不回滚(这一行为和EJB中的回滚行为是一致的)。
不过,可以声明在出现特定受检查异常时像运行时异常一样回滚。
同样,也可以声明一个事务在出现特定的异常时不回滚,即使特定的异常是运行时异常。
事务原理
1.两个例子
例子1
关于运行时异常,自动回滚
在Service层抛出Exception,在Controller层捕获,那么事务会回滚吗?
// Service⽅法@Transactionalpublic Employee addEmployee() throws Exception { Employee employee = new Employee("3y", 23); employeeRepository.save(employee); // 假设这⾥出了Exception int i = 1 / 0; return employee; }
// Controller调⽤@RequestMapping("/add")public Employee addEmployee() { Employee employee = null; try { employee = employeeService.addEmployee(); } catch (Exception e) { e.printStackTrace(); } return employee; }
既然异常被Controller捕获,那不是应该在Controller回滚吗?既然Controller没有做回滚动作,应该不会回滚
但是!经过测试==>还是回滚了
原来⽂档有说明:
By default checked exceptions do not result in the transactional interceptor marking the
transaction for rollback and instances of RuntimeException and its subclasses do
结论:如果是编译时异常不会⾃动回滚,如果是运⾏时异常,那会⾃动回滚!
例子2
如果在当前类下使⽤⼀个没有事务的⽅法去调⽤⼀个有事务的⽅法,那我们这次调⽤会怎么样?是否会有事务呢?
// 没有事务的⽅法去调⽤有事务的⽅法public Employee addEmployee2Controller() throws Exception { return this.addEmployee();}@Transactionalpublic Employee addEmployee() throws Exception { employeeRepository.deleteAll(); Employee employee = new Employee("3y", 23); // 模拟异常 int i = 1 / 0; return employee; }
注意:
Spring事务管理⽤的是AOP,AOP底层⽤的是动态代理。所以如果我们在类或者⽅法上标注注解@Transactional ,那么会⽣成⼀个代理对象。
也就是说,AOP针对addEmployee
方法生成了一个代理对象,但是addemployee2Controller
方法this.addEmployee
调用的还是原有对象的方法!所以不会有事务存在!!!
事务传播机制
如果含有事务的方法 嵌套调用了 其他方法,就属于Spring事务传播机制
众所周知,Spring事务基于Spring AOP。而AOP底层是使用的动态代理: JDK动态代理/CGLib动态代理
有以下情况无法使用AOP代理:
- JDK动态代理
- 基于接口代理,凡是方法非public修饰或用了static关键字,那么这些方法无法被AOP增强
- CgLib代理
- 基于子类代理,凡是类的方法使用了private、static、final修饰,则无法被AOP代理
但是!无法使用AOP代理并不代表不能在事务环境下工作了!
==>由于事务传播性机制,只要他们被外层带有事务的方法调用那么也同样会被该事务管理!
线程安全问题
事例
有关@Transaction + synchronized的问题
//Controller@RestControllerpublic class EmployeeController { @Autowired private EmployeeService employeeService; //开启1000个线程调用业务方法 @RequestMapping("/add") public void addEmployee() { for (int i = 0; i < 1000; i++) { new Thread(() -> employeeService.addEmployee()).start(); } }}//Service@Servicepublic class EmployeeService { @Autowired private EmployeeRepository employeeRepository; //业务方法==>操作数据库 @Transactional public synchronized void addEmployee() { // 查出ID为8的记录,然后每次将年龄增加⼀ Employee employee = employeeRepository.getOne(8); System.out.println(employee); Integer age = employee.getAge(); employee.setAge(age + 1); employeeRepository.save(employee); }}
预期结果:1000,结果多次测试都低于1000
按理来说,这个操作应该是线程安全的才对,synchronized保证了方法的原子性
有序性
可见性
那么问题应该是出在@Transaction
上
分析
@Transactionalpublic synchronized void addEmployee() { // 查出ID为8的记录,然后每次将年龄增加⼀ Employee employee = employeeRepository.getOne(8); System.out.println(employee); //打印 Integer age = employee.getAge(); employee.setAge(age + 1); employeeRepository.save(employee);}
通过日志分析
我们发现,SQL语句并没有串行的执行,这就导致对同⼀个值做重复的修改,所以最终的数值⽐1000要少。
为什么会出现这样的现象?
代理
我们知道AOP是基于代理模式的。
而Spring AOP在进行动态代理时,主要处理如下:TransactionAspectSupport类中的invokeWithinTransaction()
它是环绕着原方法开启事务的
主要步骤
- 方法执行前,开始事务
- 调用原方法
- 方法执行后,提交事务
这就会导致一个问题!!!!
方法执行完毕==>synchronized释放了锁,但是此时事务还未提交!
如果其他线程进入到了该方法,那么查询得到的数据还是事务提及前的数据,所以就会导致最终结果小于预期结果!!
解决
从上⾯我们可以发现,问题所在是因为 @Transcational 注解和 synchronized ⼀起使⽤了, 加锁的范围没有包括到整个事务。
所以我们只要扩大锁的访问即可!
@RestControllerpublic class EmployeeController { @Autowired private SynchronizedService synchronizedService ; @RequestMapping("/add") public void addEmployee() { for (int i = 0; i < 1000; i++) { new Thread(() -> synchronizedService.synchronizedAddEmployee()).start(); } }}// 新建的Service类@Servicepublic class SynchronizedService { @Autowired private EmployeeService employeeService ; // 同步 锁住该方法,在其中调用 addEmployee就可以利用锁包裹住整个事务了! public synchronized void synchronizedAddEmployee() { employeeService.addEmployee(); }}@Servicepublic class EmployeeService { @Autowired private EmployeeRepository employeeRepository; @Transactional public void addEmployee() { // 查出ID为8的记录,然后每次将年龄增加⼀ Employee employee = employeeRepository.getOne(8); System.out.println(Thread.currentThread().getName() + employee); Integer age = employee.getAge(); employee.setAge(age + 1); employeeRepository.save(employee); }}
这样就解决了线程安全问题,虽然速度会变慢!
2.SpringMVC
SpringMVC是对Servlet的封装,屏蔽掉了Servlet很多细节!
处理请求流程
例子:用户请求url:[http://localhost:8080/hello](http://localhost:8080/hello)
- 用户发送请求,请求交给核心控制器【DispatcherServlet】
- 核心控制器将请求url交给 映射器【HandlerMapping】 进行解析
/hello
==>查找对应的Controller路径 - 根据路径找到具体的Controller
- 将解析后的信息发送给DispaterServlet
- 核心控制器找到 适配器【HandlerAdapter】 去执行Controller
- 执行Controller,处理业务
- 返回ModelAndView对象
- 将其交给核心控制器
- 核心控制器找到视图解析器【Viewresolver】解析返回值中的视图
- 跳转到对应的页面
主要就是结果以下几个步骤:
- 核心控制器DispatcherServlet
- 映射器 handlerMapping ==>解析url
- 适配器 handlerAdapter ==>找到Controller执行
- 视图解析器 ViewResolver ==>解析返回值视图
DispatcherServlet
在web.xml
中注册
<!--1.注册DispatcherServlet--><servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--关联一个springmvc的配置文件:【servlet-name】-servlet.xml--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!--启动级别-1--> <load-on-startup>1</load-on-startup></servlet><!--/ 匹配所有的请求;(不包括.jsp)--><!--/* 匹配所有的请求;(包括.jsp)--><servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern></servlet-mapping>
HandlerMapping
映射器:接收请求时解析url
映射器就是用于处理“什么样的请求提交给Controller执行”
在容器中定义
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
比如:在容器中配置Controller的Bean
<!--Handler--><bean id="/hello" class="com.roderick.controller.HelloController"/>
那么映射器解析到/hello
路径时就会映射到该Controller的class路径!
HandlerAdapter
适配器:当映射器找到了对应的Controller路径,DispatcherServlet就会让适配器去找该类是否实现了Controller接口
适配器就是去找实现了Controller接口的类
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
ViewResolver
视图解析器:把结果封装到ModelAndView
之后,就会用视图解析器来对其进行解析
<!--视图解析器:DispatcherServlet给他的ModelAndView 1.获得了ModelAndView的数据 2.解析ModelAndView的视图名字 3.拼接视图名字,找到对应的视图 /WEB-INF/jsp/hello.jsp 4.将数据发送到页面--><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver"> <!--前缀--> <property name="prefix" value="/WEB-INF/jsp/"/> <!--后缀--> <property name="suffix" value=".jsp"/></bean>
Controller
实现Controller接口
//实现Controller接口public class HelloController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //ModelAndView 模型和视图 ModelAndView mv = new ModelAndView(); //封装对象,放在ModelAndView中。Model mv.addObject("msg", "HelloSpringMVC!"); //封装要跳转的视图,放在ModelAndView中 mv.setViewName("hello"); //: /WEB-INF/jsp/hello.jsp return mv; }}
使用注解
@Controller@RequestMapping("/hello") //多级路径public class HelloController { // localhost:8080/../hello/h1 @RequestMapping("/h1") public String hello(Model model){ //封装数据(像模型中添加属性msg与值,可以在jsp页面中取出并渲染 model.addAttribute("msg","hello,SpringMVCAnnotation!"); return "hello"; //会被视图解析器处理(拼接前缀后缀然后寻找hello.jsp页面 }}
参数处理
名称一致
请求url为://localhost:8080/04/user/t1?name=xxx
@GetMapping("/t1") public String test1(String name, Model model){ //1-接收前端参数 System.out.println("接收到的参数为:"+name); //2-将返回的结果传递给前端 -> Model model.addAttribute("msg",name); //3-跳转视图 return "test";}
- url路径中参数名称和方法参数名称都为name,可以直接接收到
名称不一致
请求url为://localhost:8080/04/user/t1?username=xxx
@GetMapping("/t1") public String test1(@RequetParam("username") String name, Model model){ //1-接收前端参数 System.out.println("接收到的参数为:"+name); //2-将返回的结果传递给前端 -> Model model.addAttribute("msg",name); //3-跳转视图 return "test";}
- 在请求方法参数前加上@RequestParam(“xxx”)注解,可以给参数设置别名以达到和请求参数一致到效果
- 一般情况下都推荐使用该注解
参数是对象
//接收一个对象 ": id name age/* * 1-接收前端用户传递的参数,判断参数的名字,假设名字直接在方法上,可以直接使用 * 2-假设传递的是一个对象(User),匹配User对象的属性,如果和参数一致则接收并封装 * 2-1-如果不一致则接收为null */@GetMapping("/t2")public String test2(User user){ System.out.println(user); return "test";}输出:User(id=1, name=xxx, age=22)
- User类中包含 id ,name ,age三个属性
- 当url中参数和对象属性一致时,则正常接收到
- 如果url中参数不一致,例如name被替换为username,就会被null替代
- 得到结果:User(id=1, name=null, age=22)
拦截器
自定义拦截器,需要实现 HandlerInterceptor接口
public class MyInterceptor implements HandlerInterceptor { //return true : 执行下一个拦截器,放行 //return false : 不执行下一个拦截器(不放行) public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("===========处理前==========="); return true; } //一般用于拦截日志的记录 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("===========处理后============"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("============清理============="); }}
例子:
登陆拦截
/** * 拦截器:Spring框架特有的,常用于登录校验,权限校验,请求日志打印 */@Componentpublic class LoginInterceptor implements HandlerInterceptor { private static final Logger LOG = LoggerFactory.getLogger(LoginInterceptor.class); @Resource private RedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 打印请求信息 LOG.info("------------- LoginInterceptor 开始 -------------"); long startTime = System.currentTimeMillis(); request.setAttribute("requestStartTime", startTime); // OPTIONS请求不做校验, // 前后端分离的架构, 前端会发一个OPTIONS请求先做预检, 对预检请求不做校验 if(request.getMethod().toUpperCase().equals("OPTIONS")){ return true; } String path = request.getRequestURL().toString(); LOG.info("接口登录拦截:,path:{}", path); //获取header的token参数 String token = request.getHeader("token"); LOG.info("登录校验开始,token:{}", token); if (token == null || token.isEmpty()) { LOG.info( "token为空,请求被拦截" ); response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } Object object = redisTemplate.opsForValue().get(token); if (object == null) { LOG.warn( "token无效,请求被拦截" ); response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } else { LOG.info("已登录:{}", object); //LoginUserContext.setUser(JSON.parseObject((String) object, UserLoginResp.class)); return true; } } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { long startTime = (Long) request.getAttribute("requestStartTime"); LOG.info("------------- LoginInterceptor 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// LOG.info("LogInterceptor 结束"); }}