

Spring 笔记
一、解决的问题
1. IOC 解偶
透过 Spring 的 IOC 容器,将对象间的依赖关系交给 Spring 来控制,避免硬编码造成的程序耦合。
2. 简化 AOP 编程
透过 Spring 的 AOP 功能,让我们可以更方便地进行面向切面编程。
3. 声明式事务 @Transaction
可以通过注解方式进行事务控制。
4. 集成各种框架、API,降低使用难度
框架 : Struts、 Hibernate、MyBatis、 Hessian、Quartz
API : JDBCTemplate、 JavaMail、RestTemplate
二、核心思想
1. IOC Inversion of Control 控制反转
将 Java 对象的创建、管理的权力 交给第三方 (Spring 框架)
目的 : 解偶
原理 : 透过反射调用无参构造函数实例化对象并存到容器中。【没有无参构造将实例化失败】
2. DI Dependancy Injection 依赖注入
将被管理的 Java 对象注入给某个属性
目的 : 解偶
原理 : 使用反射技术设置目标对象属性
3. AOP Aspect oriented Programming 面向切面编程
一种横向抽取技术,在特定的方法前、方法后,运行特定的逻辑
目的 : 减少重复代码
应用场景 : 事务控制、权限较验、日志纪录 、性能监控
三、特殊类
- BeanFactory - 容器类的顶层接口 - 基础规范
- ApplictaionContext - 容器类的高级接口 - 国际化、资源访问( XML、配置类 )
- ClassPathXmlApplicationContext - 项目根路径下加载配置文档
- FileSystemXmlApplicationContext - 硬盘路径下加载配置文档
- AnnotationConfigApplicationContext - 从注解配置类加载配置
- ApplictaionContext - 容器类的高级接口 - 国际化、资源访问( XML、配置类 )
- FactoryBean - 自定义复杂 Bean 的创建过程
- BeanFactoryPostProcessor - BeanFactory 初始化完成后,进行后置处理
- BeanPostProcessor - Bean 对象实例化、依赖注入后,进行 Bean 级别的后置处理
四、注解
1. IOC 类型
- @Component("Bean的ID") 【类上】 - 默认 ID 为类名首字母小写,等价以下三个注解
- @Controller
- @Service
- @Repository
- @Scope("Bean的生命周期") 【类上】
- singleton【默认】- 与容器生命周期相同
- prototype - 每次获取都是新的
- request - 一个HTTP请求范围内相同
- session - Session 范围内相同
- globalSession - portlet-based 应用范围内相同
- @PostConstruct 【方法上】- 初始化后调用
- @PreDestory 【方法上】- 销毁前调用
2. DI 类型
- @Autoweird - 依照类型注入
类型对应的对象非唯一时,可以搭配 @Qualifier(name="Bean的ID") 使用 - @Resource(name="Bean的ID", type=类) - 默认依照ID注入,如果ID找不到则按类型注入
3. 配置类型
- @ComponentScan - 需要扫描的包路径
- @Configuration - 标明此类是配置类
- @PropertySource - 引入外部配置文档
- @Import - 加载其它配置类
- @Value - 将配置文档的数据赋值到属性上
- @Bean - 将方法返回的对象存入 IOC 容器中,对象ID为方法名,也可以手动指定
4. AOP 类型
- @Pointcut - 配置切入点
- @Before - 前置通知
- @AfterReturning - 后置通知
- @AfterThrowing - 异常通知
- @After - 最终通知
- @Around - 环绕通知
五、AOP
1. 术语
- Joinpoint 连接点 : 所有的方法
- PointCut 切入点 : 具体想要影响的方法
- Advice 通知/增强 : 横切逻辑
- Target 目标 : 被代理对象
- Proxy 代理 : 被 AOP 织入增强后的类
- Weaving 织入 : 把 Advice 应用到 Target 产生 Proxy 的过程
- Aspect 切面 : 切入点 + 增强
目的 : 为了锁定在哪个地方插入什么横切逻辑
@Component
@Aspect
public class LogUtil {
/**
* 切入点表达式
* [访问修饰符] 返回值 包名.包名.包名.类名.方法名(参数表表)
*
* 【.】 用在包,表示任意包,有几级包写几个
* 【..】用在包,表示当前包及其子包
*
* 【..】用在参数表表,表示有无参数均可
* 【*】 用在参数表表,表示至少一个参数
*/
@Pointcut("execution(* com.lagou.service.impl.*.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void beforePrintLog(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("前置通知:beforePrintLog,参数是:" + Arrays.toString(args));
}
@AfterReturning(value = "pointcut()",returning = "rtValue")
public void afterReturningPrintLog(Object rtValue){
System.out.println("后置通知:afterReturningPrintLog,返回值是:"+ rtValue);
}
@AfterThrowing(value = "pointcut()",throwing = "e")
public void afterThrowingPrintLog(Throwable e){
System.out.println("异常通知:afterThrowingPrintLog,异常是:"+ e);
}
@After("pointcut()")
public void afterPrintLog(){
System.out.println("最终通知:afterPrintLog");
}
/**
* 环绕通知
*/
@Around("pointcut()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
// 定义返回值
Object rtValue = null;
try{
// 前置通知
System.out.println("前置通知");
// 1.获取参数
Object[] args = pjp.getArgs();
// 2.运行切入点方法
rtValue = pjp.proceed(args);
// 后置通知
System.out.println("后置通知");
} catch (Throwable t){
// 异常通知
System.out.println("异常通知");
t.printStackTrace();
}finally {
// 最终通知
System.out.println("最终通知");
}
return rtValue;
}
}
六、声明式事务 @Transaction
1. 四大特性
原子性 Atomicity : 操作要码都发生,要码都不发生
一致性 Consistency : 数据库从一个一致状态变换到另一个一致状态
隔离性 Isolation : 事务不能被其它的事务所干扰
持久性 Durability : 数据的改变是永久性的
2. 隔离级别
脏读 : 读取到另一个线程未提交的数据 - Read Committed 读已提交
不可重复读 : 读到另一个线程 update 的数据,两次读取到的数据 内容 不一样 - Repeatable Read 可重复读
幻读 : 读取到另一个线程 insert 或 update 的数据,两次读取到的数据 数量 不一样 - Serializable 串行化
3. 传播行为
- REQUIRED【默认】 - 当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中,加入到这个事务中
- SUPPORTS - 支持当前事务,如果当前没有事务,就以非事务方式运行 - 查找
- MANDATORY - 使用当前的事务,如果当前没有事务,就抛出异常
- REQUIRES_NEW - 新建事务,如果当前存在事务,把当前事务挂起
- NOT_SUPPORTED - 以非事务方式运行操作,如果当前存在事务,就把当前事务挂起
- NEVER - 以非事务方式运行,如果当前存在事务,则抛出异常
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
4. 失效场景
- 应用在非 public 修饰的方法上
- 同一个类中方法调用
- 异常被 try catch 吃掉
- propagation 设置错误
- rollbackFor 设置错误
- 数据库引擎不支持事务
5. 原理
透过 JDK 动态代理 与 Cglib 动态代理 实现,数据库事务归根结柢是Connection的事务。
Connection是从连接池(C3P0、Druid)拿来的,Connection可以产生prepareStatement,preparedStatement可以运行execute()方法直接运行SQL语句。
在 JDK 1.8 的环境下,JDK 动态代理的性能已经优于 Cglib 动态代理,但缺点是使用 JDK 动态代理的被代理类需要至少实现一个接口。
1. JDK 动态代理
被代理类至少需实现一个接口
public class JdkDynamicProxyTest implements InvocationHandler {
private Target target;
private JdkDynamicProxyTest(Target target) {
this.target = target;
}
public static Target newProxyInstance(Target target) {
return (Target) Proxy.newProxyInstance(JdkDynamicProxyTest.class.getClassLoader(),
new Class<?>[]{Target.class},
new JdkDynamicProxyTest(target));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
2. Cglib 动态代理
通过字节码底层继承要代理类来实现
public class CglibProxyTest implements MethodInterceptor {
private CglibProxyTest() {
}
public static <T extends Target> Target newProxyInstance(Class<T> targetInstanceClazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetInstanceClazz);
enhancer.setCallback(new CglibProxyTest());
return (Target) enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
}
测试
Target targetImpl = new TargetImpl();
Target dynamicProxy = JdkDynamicProxyTest.newProxyInstance(targetImpl);
Target cglibProxy = CglibProxyTest.newProxyInstance(TargetImpl.class);