文章目录
Spring 简介
1. 是什么
1. 轻量级,最受欢迎的企业级 Java 应用程序开发框架
2. Spring 基础框架,可以视为 Spring 基础设施,基本上任何其他 Spring 项目都是以 Spring Framework为基础的。
2. Spring Framework特性 :
非侵入式 : 使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅
控制反转 : IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入
面向切面编程 : 在不修改源代码的基础上增强代码功能
容器 : Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率 (eg. bean 的生命周期hhh)
组件化 : Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统
声明式 : 很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现 (eg.事务)
一站式 : 在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 SpringFramework 的基础上全部使用 Spring 来实现
3. 具体功能模块
功能模块 | 功能介绍 |
---|---|
Core Container | 核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器 |
AOP&Aspects | 面向切面编程 |
Testing | 提供了对 junit 或 TestNG 测试框架的整合 |
Data Access/Integration | 提供了对数据访问/集成的功能 |
Spring MVC | 提供了面向Web应用程序的集成功能 |
IOC
1. demo
1、什么是 IOC
(1)控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
(2)使用 IOC 目的: 为了耦合度降低
2、DI
DI:Dependency Injection,翻译过来是依赖注入。
DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
所以结论是:
IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。
3、IOC 底层原理
(1)xml 解析、工厂模式、反射
4、IOC容器在Spring中的实现
Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建bean 之前,首先需要创建 IOC 容器。
Spring 提供了 IOC 容器的两种实现方式:
(1)BeanFactory :
这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。
(2)ApplicationContext :
BeanFactory 的子接口, 提供了更多高级特性.
面向 Spring 的使用者, 几乎所有场合都使用ApplicationContext 而不是底层的 BeanFactory.
ApplicationContext的主要实现类 :
2. coding
2.1 引入需要的 jar
<dependencies>
<!--springMVC jar-->
<!--会引入 Spring 的很多相关 jar-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.16</version>
</dependency>
<!--lombok jar-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!--test jar-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
2.2 JavaBean
- Address
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Address {
private String address;
}
- Student
@Data
@ToString
public class Student {
private String name;
private Address address;
private String[] hobbies;
private List<String> books;
private List<Address> list;
private Map<String, String> info;
private Object obj;
private Properties nuo;
}
2.3 xml
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:Db.properties"/>
<!--scope="" 作用域-->
<!--默认单例 singleton-->
<!--原型类型 prototype-->
<!--在创建容器的时候并没有实例化,而是当 getBean() 时才会去创建一个对象,而且每次获取到的对象都不是同一个对象-->
<!--...-->
<!--p(属性: properties)命名空间-->
<bean id="address" class="com.nuo.pojo.Address" p:address="nuo"/>
<!--c(构造: Constructor)命名空间-->
<!--<bean id="address" class="com.nuo.pojo.Address" c:address="nuo"/>-->
<!--setter 注入-->
<bean id="student" class="com.nuo.pojo.Student">
<!--autowire="byName"按名字自动装配-->
<!--autowire="byType"按类型自动装配-->
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) -->
<!--<property name="name" value="nuo"/>-->
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<!--<property name="name" value="a < b"/>-->
<property name="name">
<!-- 解决方案二:使用CDATA节(CD) -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<value><![CDATA[a < b]]></value>
</property>
<!--1. ref-->
<!--<property name="address" ref="address"/>-->
<!--2. 内部 bean-->
<property name="address">
<!-- 在一个bean中再声明一个bean就是内部bean -->
<!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
<bean id="address" class="com.nuo.pojo.Address">
<property name="address" value="nuo"/>
</bean>
</property>
<!--3. 级联属性赋值-->
<!--一定先引用某个bean为属性赋值,才可以使用级联方式更新属性-->
<!--相当于重赋值-->
<!--<property name="address" ref="address">-->
<!--<property name="address.address" value="nuo"></property>-->
<!--list-->
<property name="books">
<list>
<value>Chinese</value>
<value>math</value>
<value>English</value>
</list>
</property>
<!--arr-->
<property name="hobbies">
<array>
<value>run</value>
<value>swimming</value>
</array>
</property>
<!--null-->
<!--<property name="obj" value=""/>-->
<property name="obj">
<null/>
</property>
<!--map-->
<property name="info">
<map>
<entry key="age" value="3"/>
</map>
</property>
<!--prop-->
<property name="nuo">
<props>
<prop key="nuo1">1</prop>
<prop key="nuo2">2</prop>
</props>
</property>
<!--list集合类型的bean-->
<util:list id="list">
<ref bean="address"></ref>
</util:list>
</bean>
2.4 获取bean
- 通过 id
- 通过 type
- 通过 id & type
- if 同类型 bean 不唯一,通过 type 获取时会报错 :
org.springframework.beans.factory.NoUniqueBeanDefinitionException
- 如果组件类实现了接口,根据接口类型可以获取 bean , 前提是bean唯一
- 如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型不可以获取 bean ,因为bean不唯一
- 结论 => 根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到
public class TestDemo {
@Test
public void demo(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean("student", Student.class);
System.out.println(student);
}
}
2.5 Bean 的生命周期
4、bean 的后置处理器,bean 生命周期有七步
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
--(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
--(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
(6)bean 可以使用了(对象获取到了)
(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
public class MyBeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("☆☆☆" + beanName + " = " + bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("★★★" + beanName + " = " + bean);
return bean;
}
}
<!-- bean的后置处理器要放入IOC容器才能生效 -->
<bean id="myBeanProcessor" class="com.nuo.process.MyBeanProcessor"/>
2.6 FactoryBean
FactoryBean是Spring提供的一种整合第三方框架的常用机制。
和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。
通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
3. 自动装配
3.1 基于 xml 的自动装配
<bean id="cat" class="com.nuo.pojo.Cat"/>
<bean id="dog" class="com.nuo.pojo.Dog"/>
<bean id="people" class="com.nuo.pojo.People" autowire="byType"/>
3.2 基于注解管理bean
- 开启注解支持
<!--组件扫描-->
<context:component-scan
base-package="com.nuo">
<!-- context:exclude-filter标签:指定排除规则 --> <!--type:设置排除或包含的依据 type="annotation",根据注解排除,expression中设置要排除的注解的全类名 type="assignable",根据类型排除,expression中设置要排除的类型的全类名 -->
<!--include-filter 扫描什么 -->
<!--exclude-filter 不扫描什么 -->
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<!--<context:exclude-filter type="assignable" expression="com.nuo.controller.UserController"/>-->
</context:component-scan>
<context:annotation-config/>
3.3 基于 annotation 的自动装配
@Autowired
3.3.1 @Autowired工作流程
1. bytype
2. byname
3. if 没有该类型的 bean : 装配失败
4. if 有该类型的 bean, 不止一个
-> 没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配
-> 能够找到:执行装配
->找不到:装配失败
-> 使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的 id 进行匹配
-> 能够找到:执行装配
-> 找不到:装配失败
【PS】:
1. @Autowired 中有属性 required,默认值为 true,因此在自动装配无法找到相应的bean时,会装配失败, 并报错
2. 可以将属性 required 的值设置为false,则表示能装就装,装不上就不装,此时自动装配的属性为默认值
3. 但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性。
AOP => 代理模式
1. 静态代理
代理模式
- 静态代理
抽象角色 : 一般使用接口或者抽象类来实现
真实角色 : 被代理的角色
代理角色 : 代理真实角色 ; 代理真实角色后,一般会做一些附属的操作.
客户 : 使用代理角色来进行一些操作.
eg.真实角色:房东 代理:中介 接口:出租 我:客户
分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代
理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。
静态代理的好处:
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
缺点 :
- 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
2. 动态代理
public class ProxyFactory {
private final Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxy() {
/* newProxyInstance():
* 创建一个代理实例
* 其中有三个参数:
* 1、classLoader:加载动态生成的代理类的类加载器
* 2、interfaces:目标对象实现的所有接口的class对象所组成的数组
* 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接 口中的抽象方法
*/
ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/* proxy:代理对象
* method:代理对象需要实现的方法, 即其中需要重写的方法
* args:method所对应方法的参数
*/
Object result = null;
try {
System.out.println("[动态代理][日志] " + method.getName() + ",参 数:" + Arrays.toString(args));
result = method.invoke(target, args);
System.out.println("[动态代理][日志] " + method.getName() + ",结 果:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("[动态代理][日志] " + method.getName() + ",异 常:" + e.getMessage());
} finally {
System.out.println("[动态代理][日志] " + method.getName() + ",方法 执行完毕");
}
return result;
}
};
return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
}
}
3. 基于注解的 AOP
动态代理(InvocationHandler):
1. JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
2. cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
3. AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LogAspect {
@Before("execution(public int com.nuo.aop.annotation.CalculatorImpl.* (..))")
public void beforeMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:" + methodName + ",参 数:" + args);
}
@After("execution(* com.nuo.aop.annotation.CalculatorImpl.*(..))")
public void afterMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->后置通知,方法名:" + methodName);
}
@AfterReturning(value = "execution(* com.nuo.aop.annotation.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:" + methodName + ",结 果:" + result);
}
@AfterThrowing(value = "execution(* com.nuo.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:" + methodName + ",异常:" + ex);
}
@Around("execution(* com.nuo.aop.annotation.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
Object result = null;
try {
System.out.println("环绕通知-->目标对象方法执行之前"); //目标对象(连接点)方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
} finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
}
return result;
}
}
<!--基于注解的AOP的实现:
1、将目标对象和切面交给IOC容器管理(注解+扫描)
2、开启AspectJ的自动代理,为目标对象自动生成代理
3、将切面类通过注解@Aspect标识
-->
<context:component-scan base-package="com.nuo.aop.annotation">
</context:component-scan> <aop:aspectj-autoproxy />
3. 基于 xml 的 AOP
<context:component-scan base-package="com.nuo.aop.xml"></context:component- scan>
<aop:config> <!--配置切面类-->
<aop:aspect ref="loggerAspect">
<aop:pointcut id="pointCut" expression="execution(* com.nuo.aop.xml.CalculatorImpl.*(..))"/>
<aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
<aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
<aop:after-returning method="afterReturningMethod" returning="result"
pointcut-ref="pointCut"></aop:after-returning>
<aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
<aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
<aop:aspect ref="validateAspect" order="1">
<aop:before method="validateBeforeMethod" pointcut-ref="pointCut"></aop:before>
</aop:aspect>
</aop:config>
事务
1. 编程式
Connection conn = ...;
try {
// 开启事务: 关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作: 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
2. 声明式
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务的注解驱动 通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务 -->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就 是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
// 只读
@Transactional(readOnly = true)
// 超时回滚,释放资源
@Transactional(timeout = 3)
3. 事务隔离级别
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
隔离级别一共有四种:
1. 读未提交:READ UNCOMMITTED:
允许Transaction01读取Transaction02未提交的修改。
2. 读已提交:READ COMMITTED:
要求Transaction01只能读取Transaction02已提交的修改。
3. 可重复读:REPEATABLE READ:
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
4. 串行化:SERIALIZABLE:
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
@Transactional(isolation = Isolation.DEFAULT) //使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED) //读未提交
@Transactional(isolation = Isolation.READ_COMMITTED) //读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ) //可重复读
@Transactional(isolation = Isolation.SERIALIZABLE) //串行化
4. 事务传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行
@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。所购买的两本图书的格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了
@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本
5. 基于XML的声明式事务
<aop:config>
<!-- 配置事务通知和切入点表达式 -->
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* com.nuo.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- tx:method标签:配置具体的事务方法 -->
<!-- name属性:指定方法名,可以使用星号代表多个字符 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*"
read-only="true"/>
<!-- read-only属性:设置只读属性 -->
<!-- rollback-for属性:设置回滚的异常 -->
<!-- no-rollback-for属性:设置不回滚的异常 -->
<!-- isolation属性:设置事务的隔离级别 -->
<!-- timeout属性:设置事务的超时属性 -->
<!-- propagation属性:设置事务的传播行为 -->
<tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>