Spring
Spring IOC(控制反正)和DI(依赖注入)是什么?
IOC (Inverse of Control)控制反转:
- 曾经:
静态依赖:类中的依赖的对象是通过New关键字在堆中创建对象并被引用进行依赖对象调用方法
动态依赖:通过反射,获取依赖对象类的Class对象,创建实例并依赖对象实例调用方法 - 现在:
将依赖的对象交给Spring IOC容器中统一管理,需要依赖某个对象时通过IOC容器依赖注入到对应的类中
,那么这么一来依赖的对象控制权在第三方的手中。可能是xml配置文件、可能是JavaBean注解开发
DI (Dependency Injection) 依赖注入:
- 基于控制反转思想进行实现,对底层原生代码进行封装提供了对应的API,将需要依赖的对象剥离到Spring IOC容器中管理的类通过Spring API进行注入到需要依赖的位置
IOC和DI的关系
- IOC控制反转的抽象思想将对象进行管理,DI是注入的具体实现行为。IOC和DI互相搭档,实现了降低解耦。
DI让对象和对象依赖的功能实现,而对象在IOC中进行统一管理,所以依赖的功能需要基于IOC容器为DI提供数据Bean对象
- 曾经:
多态关系进行赋值依赖,类中维护对象之间的依赖关系。若发生变动,当需要修改依赖子为实现B时,需要修改代码,重构系统应用
- 现在:
通过resource外部文件IOC容器维护类依赖关系。若依赖关系发生变动,无需修改java代码,仅修resource配置文件
使用前不得不知道的知识点?
如何从java目录中读取resource目录中的IOC容器?
Bean的作用范围(既多次获取是否是同一个地址值对象)
[默认模式:单例模式]
<bean id="" class="" scope="singleton">
[其他模式:原型模式] 每次获取都创建新的地址值对象
<bean id="" class="" scope="prototype">
生命周期方法如何触发执行任务?(销毁方法触发需要执行特定的方法,否则无法执行)
- 自己配置触发执行
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
//表示bean初始化对应的操作
public void init(){
System.out.println("init...");
}
//表示bean销毁前对应的操作
public void destory(){
System.out.println("destory...");
}
}
<bean id="" class="" init-method="绑定类中方法名" destroy-method="绑定类中方法名">
- 通过实现规范触发执行
public class Main implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}
@Override
public void destroy() throws Exception {
System.out.println("service destroy");
}
}
[实现规范] 即可简单配置
<bean id="" class="">
解决销毁方法无法执行的问题,原因是因为JVM退出后,IOC容器没有来得及关闭
- 手工关闭容器
ConfigurableApplicationContext
接口close()
操作- 注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
ConfigurableApplicationContext
接口registerShutdownHook()
操作
Spring实例化和依赖注入的方式分别有什么?
Spring实现实例化的四种方式(即管理对象,仅创建对象,并不初始化成员变量)
- 构造器实例化Bean : 使用无参或无参
[无参实例化]
<bean id="user" class="con.xy.pojo.User"/>
[有参实例化] 需要额外加入构造依赖注入:分为1.name形参名匹配 2.type类型匹配 3.index索引匹配(0开始)
<bean id="user" class="con.xy.pojo.User">
<constructor-arg ref="需要引用IOC中的Bean•setter方法后属性名"/>
<constructor-arg name="基本类型成员变量•setter方法后属性名" value="赋值"/>
</bean>
注解型
类@Component @Controller @Service @Reposity
范围@Scope(“singleton”)
- 静态工厂方式实例化Bean:
[静态工厂]随着类的加载而加载,即类名可以直接调用静态方法。无需对工厂产品进行注册Bean
<bean id="user•将会返回产品实例" class="con.xy.utils.UserStaticFactory" factory-method="getUser•拥有返回工厂产品的静态方法名"/>
注解型
方法返回值@Bean
- 实例工厂方法实例化Bean:
[实例工厂]顾名思义,需要实例化获取工厂对象,才能调用getter方法获取产品,分两步。无需对工厂产品注册Bean
<bean id="实例化工厂" class="con.xy.utils.UserInstanceFactory" />
<bean id="user•将会返回产品实例" factory-method="getUser•拥有返回工厂产品的实例方法名" factory-bean="引用IOC容器中实例化的工厂Bean"/>
注解型
方法返回值@Bean
- 通用Spring工厂实例化Bean:
[步骤一 java目录:继承springFrameWork的工厂规范接口]
class UserInstanceFactory implements FactoryBean<T•工厂产生的商品类型•用接口可以多态多样产品>
[步骤二 resource目录:Spring规范通用工厂]
<bean id="user" class="con.xy.utils.UserFactory"/>
依赖注入的四种方式(即实例化对象,又成员初始化赋值)
- 构造方法注入使用此方式需要注入形参所有的属性
[有参构造器注入] 无参构造器注入个辣子🌶️,无需考虑
- 形参名 匹配注入
<bean id="" class="">
<constructor-arg name="" value="基本类型"/>
<constructor-arg name="" ref="引用类型•需要关联IOC容器中其他Bean的id"/>
</bean>
- 形参类型 匹配注入
<bean id="" class="">
<constructor-arg type="" value="基本类型"/>
<constructor-arg type="" ref="引用类型•需要关联IOC容器中其他Bean的id"/>
</bean>
- 形参索引 匹配注入
<bean id="" class="">
<constructor-arg index="" value="基本类型"/>
<constructor-arg index="" ref="引用类型•需要关联IOC容器中其他Bean的id"/>
<constructor-arg>
<array value-type="数组类型">
<value></value>
<value></value>
<value></value>
</array>
</constructor-arg>
<constructor-arg>
<list value-type="arrayList集合类型">
<value></value>
<value></value>
<value></value>
</list>
</constructor-arg>
<constructor-arg>
<set value-type="hashSet集合类型">
<value></value>
<value></value>
<value></value>
</set>
</constructor-arg>
<constructor-arg>
<map key-type="hashMap集合类型" value-type="">
<entry key="" value=""/>
<entry key="" value=""/>
<entry key="" value=""/>
</map>
</constructor-arg>
<constructor-arg>
<props value-type="properties配置集合">
<prop key=""></prop>
<prop key=""></prop>
<prop key=""></prop>
</props>
</constructor-arg>
</bean>
- Setter方法注入 (设值注入) 可选的依赖关系,但需要提供无参构造或者无参的静态工厂方法创建对象集合数组Proerties注入仅与构造constructor-arg不同,可以传入多个相同集合不同属性名,推荐使用Setter方式注入,因为灵活
[Setter方法注入] 灵活优于构造器注入,可以对部分成员不进行初始化
<bean id="" class="">
<property name="Setter方法后的属性名•首字母变小写" value="基本类型的值"/>
<property name="Setter方法后的属性名•首字母变小写" ref="引用类型•需要关联IOC容器中其他Bean的id"/>
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
</set>
</property>
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
</bean>
- 自动装配注入基于Setter方法
[自动装配注入] 自动从IOC容器中匹配其他Bean进行填充
- 类型自动注入:务必保证IOC中Bean的Type唯一性
<bean id="" class="" autowire="byType"/>
- 属性名自动注入: 务必保证IOC中Bean的id名符合Setter后属性名
<bean id="" class="" autowire="byName"/>
- 注解注入
@Autowired :按照类型注入 [ 组合@Qualifier 按照名称装配 ]
、
@Value: 普通类型注入 或 读取properties 配置文件 “${}” 无需Setter
、
@Resource :
指定名称name和类型type会进行唯一匹配,找不到则抛出异常
指定仅名称name,按照名称(id)匹配Bean进行装配,找不到抛出异常
指定名称type,按照类型匹配Bean进行装配,找不到抛出异常
不进行任何指定,自动按照优先byName方式进行匹配,无法匹配尝试进行type匹配
容器类
配置@Coinfiguration + 扫描目录下Bean @Component(多目录1,多目录2) + @Import(获取子容器1,获取子容器2)
属性配置读取 @PropertySource(“classpath:jdbc.properties”)
测试类(pom依赖容易发生不兼容问题)
spring测试启动器
@RunWith(SpringJUnit4ClassRunner.class)
获取java容器
@ContextConfiguration(classes = {SpringConfiguration.class})
Data Access/Integration (数据 访问/一体化) (简化与第三方框架整合)
【1.Dependency依赖准备】
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 核心Spring框架 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- 第三方框架: mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!-- 第三方框架: Mysql Jdbc -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<!-- 第三方框架: DataSource数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!-- 第三方框架: DataSource数据源 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- 第三方框架: Junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- 第三方框架: AOP切面织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
【2.配置Bean】
【解决应配置问题】(多属性配置文件可合并)
【3.创建容器】
//类路径加载单个配置文件(常规)
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//类路径加载多个配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("context_1.xml", "context_2.xml");
//文件路径加载配置文件(绝对路径)
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");
//类路径加载配置文件(java目录下)
Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);
//注解式类配置容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
Spring AOP(面向切面编程)(非入侵式,不需要修改原处代码即可对功能增强)
切面织入依赖
<!-- 第三方框架: AOP切面织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
核心概念
连接点(JoinPoint) 被增强的方法
切入点(Pointcut) 增强的代理方法
通知(Advice) 对增强方法的执行时机
通知类 定义多个通知时机
切面(Aspect) 执行的过程 或 是对应关系
通知类 @Aspect 标记为切面类
容器类 @EnableAspectJAutoProxy 配置开启
切入点 @PointCut(execution(public * com.itheima..UserService.find()))
任意单词*
execution(public * com.itheima..UserService.find*(*))
多级路径..executio\n(public User com…UserService.findById(…))
子级匹配+
execution(* *…Service+.(…))
为什么使用AOP?
- 降低耦合度
- 侵略性小,避免了修改代码
- 复用性高,制定增强的代码对切入点进行注入
AOP如何使用?用到了什么地方?
前置通知:@Before
后置通知:@After
返回后通知:@AfterReturning 类似于finlly
异常通知:@AfterThrowing 仅发生异常
环绕通知:@Around 前置+后置混合+异常捕捉+返回后通知
日志处理(执行次数追踪,性能监测,异常记录)
事务处理(对增删改这类操作进行事务管理)
事务管理
配置类开启事务管理
@EnableTransactionManagement
事务传播级别(业务处理层)
@Transactional(propagation = Propagation.REQUIRES_NEW,readOnly=true)
什么是动态代理?动态代理都有哪些?
不修改代码的情况下,对代码进行增强,使用时字节码随用随创建,随用随加载
- 基于接口的动态代理,要求:被代理类最少实现一个接口
提供者:JDK官方,涉及类:Proxy
创建代理对象方法:newProxyInstance
涉及到创建对象方法里的参数:ClassLoader:类加载器【固定写法】
负责加载代理对象使用的字节码,需要和被代理对象使用相同类加载器:
Class[]:字节码数组【固定写法】
负责让生成的代理对象具有和被代理对象相同的方法。
写什么要看被代理对象是一个接口还是一个实现类
如果是一个接口:new Class[]{接口}
如果是一个实现类:XXX.getClass().getInterfaces()
InvocationHandler:一个接口,需要我们提高该接口的实现,写的是一个接口的实现类
增强代码,谁用写谁,通常是匿名内部类,不是绝对的
- 基于子类的动态代理,要求:需要导入第三方cglib的坐标依赖。
提供者:Cglib第三方,涉及类:Enhancer
创建代理对象的方法:create
方法的参数:
Class: 字节码对象,用于加载代理对象字节码,写的是被代理对象的字节码
Callback:如何代理。提供增强代码。
(它是个接口,需要实现这个接口没有的方法,需要使用它的子接口 MethodInterceptor)
动态代理使用场景
- 基于AOP思想的方法增强
- 自动以连接池中,实现Connection的cloase方法将连接还回池中的操作,可以使用动态代理或装饰者模式
- 解决全站中文乱码,get和post两种提交方式
在get方式需要对一下三个方法增强,用于解决乱码
(tomcat8.5底层解决)
String value= getParameter(String name);
String[]value=getParameterValues(String name);
Map<String,String[]>map = getParameterMa();
什么是静态代理?
JDK动态代理 与 CGLIB动态代理的区别?
相同点:
- 均属于Spring AOP技术动态代理的实现方式
不同点:
-
jdk动态代理:通过反射接收被代理的类,核心InvocationHandler和Proxy类
缺点:被代理类必须实现接口,也就是说被代理的类必须有接口的实现关系 -
cglib动态代理:适用于没有实现接口的类,cglib是一个代码生成类库,运行阶段动态生成某个类的子类(目标类),cglib通过继承的方式动态代理
缺点:类将需要能够被继承,不能被标记为final,那么无法cglib动态代理
<!--配置平台事务管理器-->
<!--注入连接池-->
<!--配置切面类(Aspenct):TransactionDefiniton设置方法-->
<!--
read-only:是否只读事务,默认false
isolation:指定隔离级别
propagation:指定事务的传播行为
timeout:超时时间,默认-1永不超时
rollback-for:用于指定异常,当异常出发时,被指定的异常事务回滚
no-rollback-for:用于指定异常,当异常出发时,被指定的异常事务不回滚
-->
<!--声明AOP-->
<!--配置切入点指定事务服务的包以及织入id-->
<!--织入id指定切面类-->
Bean的作用域
- singleton(默认:单例模式)IOC仅创建一个Bean实例,每次返回同一个实例
生命周期与容器同长 - prorotype(原型[多例]模式)IOC创建多个实例,每次返回一个新的实例
生命周期使用完被垃圾回收 - request(Http请求)每次HTTP请求都会创建一个新的Bean,使用WebApplicatonContext
- session(当前会话)一个Session共享一个Bean,不同Session使用不同的实例
Spring事务声明
不考虑事务隔离性引发的安全问题?
- 脏读: 读取到其他事务未提交回滚的数据
- 不可重复读: 事务需要重复读一些数据,但没有执行完时,读取到其他事务操作后的数据,导致重复读取的数据不一致
- 幻读: 事务执行时,其他事务执行修改的数据符合当前逻辑,导致当前事务查询多次结果不一致
事务的隔离界别
- read uncommited读未提交: 三种安全问题都会发生
- read commited只读已提交: 重复读 幻读
- repeatable read可重读读: 幻读
- serializable串行化: 三种安全问题都被解决
隔离界级别越高,安全性高,效率越低
Spring如何管理事务?
- 编程式事务管理: Spring推荐使用TransactionTemplate工具类(但是开发中声明式事务使用较多)
缺点:
1.事务管理的代码和业务代码将会同时出现,分层不明确
2.使用工具类进行类与类的依赖,耦合性强 - 声明式事务管理: 利用AOP面向切面编程思想,选择目标,对目标进行环绕时拦截。在方法目标执行前加入或创建一个事务,在方法执行后,根据情况选择提交或是回滚事务.
优点:
- 业务代码与事务分离,事务部分统一在Spring的xml配置文件中集中管理
- 降低耦合性
Spring的事务管理器有什么?
Spring不会直接管理事务,对其他多平台提供的事务管理器接口,事务管理器充当一个工具类,从而用户在Spring使用事务,不要用关心事务的实现
Spring的事务只读是强制的吗?
选择只读Spring将会对查询的方法进行性能优化
对于事务需要设置多长超时时间?
事务的执行时间不能过长,如果超时需要记录保存日志
项目中如何实现使用Spring事务?
Spring提供三个接口,事务需要这三个的实现共同执行
- PlatFormTransactionManager
commit 事务提交
rollback 事务回滚
getTransaction 获取事务 - TRansactonDefinition
getIsonlationLevel:获取隔离级别
getPropagetionBehavior:获取传播行为
getTimeout获取超时时间
isReadOnly是否只读(false可读写,true只读) - TransactionStatus
hasSavepoint返回事务内部是否包含一个保存点
isComplated返回事务是否已经完成提交或回滚
isNewTransaction判断是否是一个新事物
Spring中用到哪些设计者模式?
- 工厂模式BeanFactory以及ApplciationContext
- 模板模式BeanFactory以及ApplciationContext
- 代理模式AOP的JDK动态代理
- 单例模式创建Bean的时候
- 策略模式JdbcTmplate通过SQL获取数据,但获取数据的分组类型
RowMapper接口以及BeanPropertyRowMapper的是实现类
RowMapper接口定义规范,而实现类提供不同的策略 - 观察者模式 WebApplciatonContext,是通过ContextLoaderListener监听器实现创建的.监听器就是观察者模式的具体实现 (事件源 时间 监听者)
- 适配器模式
@Controller注解
实现Controller接口
实现HttpRequestHandler接口
8.装饰者模式