IOC
Spring是一个容器,这个容器里边放的是对象 ,也就是对象工厂,也就是beans,在spring的思想中IOC(控制反转)和AOP(切面编程)是其中的重要部分,对象我们通过applicationContext.xml的配置文件进行配置,bean,也就是创建了对象 实现控制反转,而实现控制反转的方法是DI依赖注入(依赖容器生成对象(通过无参构造方法,有参的话指定name参数属性),通过properties进行 注入)
<bean id="对象名" class="全限定类名(包名+类名)">
<properties name="属性名" value="具体的属性值"/>
<array>
<value></value>
<value></value>
</array>//针对数组进行注入
<list>集合注入
</bean>
# 通过 new ClassPathXml...().getBean()去获取对象
注解说明
上述是通过配置文件进行注入的,在这里接触一种最为常用的方法,就是注解开发,首先注解开发要在配置文件中加入注解驱动和组件扫描 Context:componet-scan然后我们就可以使用注解,
装配注解
-@Autowired:自动进行装配对象的属性等,通过byType,byName,类型和名字
-@Qualifier(value="xxx"),如果有多个对象重名,可以通过这个注解指定唯一的一个
-@Nullabe:如果字段上有这个注解,表示这个字段可以为空
-@Resource: 同样是自动装配,java的,它通过名字、类型进行装配
-@Componet:这是一个spring的组件,放在类之上,相当于<bean id="" class=""/>,放入容器中,就是说明这个类被spring接管了bean,对象的名字跟类名相同
-@value: 上边的组件相当于创建了对象,这个注解放在属性之上,相当于<properties/>,为属性注入了一个值,当然类类型的需要@Autowired,如果属性偏多,那么配置文件更快捷
-@Componet的衍生注解,由于我们在Web开发之中,会按照mvc三层的架构分层,衍生注解的功能和本身是一致的,只是名字不同
dao层 --@Repository
Service层--@Service
controller层--@Controller
-@Scope(value="singletn“)//这个注解设置作用域,可以单例(一个类生成一个对象,多个调用),可以多例(protoType每次调用都会生成一个对象)*/
XMl与注解
xml:更加万能,适用于各种场合,维护简单方便
注解:不是自己的类用不了 维护相对复杂
XMl与注解的最佳实践
就是xml负责生成对象,由注解去完成属性注入,sping的项目会使用配置文件和注解,但是springboot全是注解。*/
使用java的方式配置Spring
我们现在完全不使用Spring的xml配置啦,全权交给java来做,javaConfig是spring的一个子项目,在spring4之后,它成为了一个核心功能。
@Configuration
public class MyConfig{
@Bean //就相当于以前写的一样 就是注册一个bean
public User getUser(){
return new User;
}
}
@Bean 单独使用,不会注入ioc容器,配合@Componet使用,会注入容器,但是每次都是新创建的对象,配合@Configuration使用,每次从ioc容器中取对象。
@Configuration与@Componet的区别
-
@Component :通用的注解,可标注任意类为 Spring 的组件。如果一个 Bean 不知道属于哪个层,可以使用 @Component 注解标注。
-
@Configuration :声明该类为一个配置类,可以在此类中声明一个或多个 @Bean 方法。
这两个注解都是配置类注解,作用于类上,申明该类为组件。不同之处在于:
-
@Component是一个元注解,可以注解其他类注解。@Configuration注解里面也是被@Component注解修饰的
-
返回bean实例不同
@Configuration 注解修饰的类,并且该注解中的 proxyBeanMethods 属性的值为 true(默认的),则会使用cglib动态代理,为这个 bean 创建一个代理类,该代理类会拦截所有被 @Bean 修饰的方法,在拦截的方法逻辑中,会从容器中返回所需要的单例对象。 @Component 注解修饰的类,则不会为这个 bean 创建一个代理类。 那么我们就会直接执行用户的方法,所以每次都会返回一个新的对象。
-
DI
DI(Dependency Injection)即依赖注入,对象之间的依赖由容器在运行期决定,即容器动态的将某个依赖注入到对象自身
1、构造方法注入
public class Student {
private Integer id;
private String name;
public Student(Integer id, String name) {
this.id = id;
this.name = name;
}
}
配置文件中赋值
<!--通过有参构造注入依赖-->
<bean id="student3" class="com.test.pojo.Student">
<!--id属性注入-->
<constructor-arg name="id" value="11"/>
<!--name属性注入-->
<constructor-arg name="name" value="图论"/>
</bean>
2、set注入
public class Student {
private Integer id;
private String name;
public void setId(Integer id) {this.id = id;}
public void setName(String name) {this.name = name;}
}
配置文件中赋值
<!--通过setter方法注入依赖-->
<bean id="student4" class="com.test.pojo.Student">
<property name="id" value="12"/>
<property name="name" value="Java31"/>
</bean>
3、自定义类型
<!--注入自定义类型-->
<bean id="user" class="com.test.pojo.User">
<constructor-arg name="name" value="Java"/>
</bean>
<bean id="student5" class="com.test.pojo.Student">
<property name="id" value="12"/>
<property name="name" value="Java31"/>
<!--value属性:将参数按照Syringe类型类解析 ref类型:Spring中管理的对象的Id值-->
<property name="user" ref="user"/>
</bean>
基于注解注入
- @Value :注入普通类型属性
- @Resource :注入对象类型,Java 提供的,默认按照名称来查找并注入类,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
- @Autowired :注入对象类型,是Spring框架提供的,按照类型来注入,默认情况下必须要求依赖对象存在,如果要允许null值,可以设置它的required属性为false。如果想使用名称装配可以结合@Qualifier注解进行使用。
AOP
代理相当于中介的概念,就是帮助别人做一些事情,为什么要学习代理模式,因为这就是springAOP的底层 代理模式的分类:
- 静态代理
- 动态代理
静态代理(租房子中介 房东和中介都有租房的需求)
抽象角色:一般会使用接口和抽象类来解决
真实角色:被代理的角色; 代理角色:代理真实角色,代理真实角色之后会做一些附属操作 客户:访问代理对象的人 代理模式的好处 可以让真实角色更加的纯粹,不用去关注一些公共的业务,公共也就交给代理角色,主要是通过代理的附加功能,做一些自己没有做的事情。 缺点,一个真实角色就会产生一个代理角色,代码量翻倍
动态代理
动态代理的底层是反射,动态代理类是自动生成的,不是我们直接写好的 动态代理分为两大类,基于接口的动态代理,基于类的动态代理 基于接口---JDK动态代理 {我们在这里使用} 基于类----cglib java字节码的实现javasist 需要了解两个类 proxy 代理 InvocationHandler 调动处理程序,不需要写代理类,自动生成
什么是AOP
面向切面编程,动态的去修改加强程序。横切关注点,可以做一些日志、切面、事物等。使用的过程中加入织入的依赖。动态代理代理的是接口,要接口 a =new 实现类;
第一种方式:使用spring-api的原生接口
配置
<aop:config>
//切入点,expression是表达式,第一个*,返回类型,下的包下的所有方法,参数也是所有参数(..)可以定义多个切入点
<aop:pointcut id="pointcut",expression="execution(* com.shilei.controlller.userServiceimpl.*(..))"/>
<aop:pointcut id="pointcut1",expression="execution(* com.shilei.controlller.userServiceimpl.*(..))"/>
//执行环绕增强
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
</aop:config>
第二种方式,自定义类
public class PointCut {
public void before(){
System.out.println("====方法执行前=======");
}
public void after(){
System.out.println("=====方法执行后=======");
}
}
<bean id="pointCut" class="com.test.PointCut"/>
<bean id="userService" class="com.test.UserServiceImpl"/>
<aop:config>
<!--自定义切面,ref为要引入的类-->
<aop:aspect ref="pointCut">
<!--切入点-->
<aop:pointcut id="point"
expression="execution(* com.test.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
第三种方式 :注解实现AOP
package com.web.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class Log {
@Before("execution(* com.web.pojo.Service.*(..))")
public void before() {
System.out.println("============== Before Method =============");
}
@After("execution(* com.web.pojo.Service.*(..))")
public void after() {
System.out.println("============== After Method =============");
}
}
spring循环依赖问题
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A,属性循环依赖,构造器循环依赖,
检测循环依赖
检测循环依赖相对比较容易,Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了
解决循环依赖
Spring解决循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的属性是可以延后设置的(但是构造器必须是在获取引用之前)。 Spring的单例对象的初始化主要分为三步:
-
实例化:其实也就是调用对象的构造方法实例化对象
-
注入:填充属性,这一步主要是对bean的依赖属性进行填充
-
初始化:属性注入后,执行自定义初始化
循环依赖主要发生在第一、第二步。也就是构造器循环依赖和属性循环依赖
那么我们要解决循环引用也应该从bean初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
获取单例对象的三级缓存
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 一集缓存中获取
Object singletonObject = this.singletonObjects.get(beanName);
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) {
singletonObject = singletonFactory.getObject();
//获取到对象加入二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 在三级缓存中删除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
调用createBeanInstance实例化后,如果bean是单例,且允许从singletonFactories获取bean,并且当前bean正在创建中,那么就把beanName放入三级缓存(singletonFactories)中,实例化后还没进行属性设置和初始化,但是如果正在创建中,则提前暴露 加入三级缓存
总结一句话 ,先暴露引用,不设置属性,先形成引用关系,属性从缓存中取。
A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象
循环依赖的场景
-
单例setter注入 能解决
-
多例setter注入不能解决
-
构造器注入 不能解决