Spring循环依赖是什么
如上图所示,两个或两个以上bean互相持有对方,最终形成闭环,循环依赖的场景有个两种
- 构造器循环依赖
- set循环依赖
由于我们获取对象的引用时候,对象的属性可以延迟设置,因此我们可以使用set可以解决循环依赖,而构造器不能解决,因为构造器必须在获取引用之前.
使用set属性解决循环依赖,我们必须要知道三级缓存
代码语言:javascript复制/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
- singletonObject,一级缓存,保存完整的生命周期的bean,相当一个成品
- earlySingletonObjects,二级缓存,保存的是提前曝光的单例bean,相当一个半成品
- singletonFactories,三级缓存,保存对象工厂
再看看重要的几个方法
spring解决循环依赖的过程
- 调用doGetBean方法,想要获取beanA,于是调用getSingleton方法从缓存中查找beanA
- 在getSingletion方法中,从一级缓存中查找,返回null
- doGetBean方法中获取到的bean为null,走对应的逻辑,调用getSinleton的重载方法参数是ObjectFactory
- 在getSingleton方法中,先将beanA_Name添加到一个集合中,用于标记该bean正在创建中,然后调用匿名内部类的createBean方法
- 进入AbstractAutowireCapableBeanFactory.doCreateBean,先反射调用构造器创建beanA实例,然后判断,是否为单例,是否允许提前暴露引用,是否正在创建中,判断为true则将beanA添加到三级缓存中
- 对beanA属性填充,此时检测到beanA依赖beanB,于是开始查找beanB
- 调用doGetbean方法,和上面检测到beanA依赖beanB,于是开始查找beanB,没有则创建然后给beanB填充属性
- 此时beanB依赖beanA,调用getSingleton获取BeanA,依次从一级,二级三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时singletonObject指向的就是上面在doCreateBean方法实例化的beanA
- 这样beanB就获取到了beanA的依赖,于是beanB顺序完成实例化,并将beanA从三级缓存移到二级缓存中
- 随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也完成了创建,回到getSingleton方法中继续向下执行,将beana从二级缓存移到到一级缓存中
spring IOC的理解
首先我们知道如果没有Ioc,我们是如何开发的,比如,下面例子
代码语言:javascript复制public class MyController {
private MyService myService = new MyServiceImp();
private void getPost(){
myService.postImp();
}
}
MyController在使用Myservice的时候,我们必须实例化他的实现类,但是当我们在多个地方使用Myservice使用,而当我们修改了Myservice的时候,我们就必须修改多处,这样我就不但要承担工作量,还有一定的风险性,因此Spring IOC就应用而生
Spring IOC是如何处理的呢,如下图所示
正如上面例子,MyService的实例化,以及MyController都交给spring处理,以及实例化,当我们想在MyController引用MyServiec,只要添加对应的属性且加注解,就能引用对应的实现类,如下图
代码语言:javascript复制@Controller
public class MyController {
@Autowired
private MyService myService;
private void getPost(){
myService.postImp();
}
}
此时当我需要修改MyService的时候,只要修改对应的实现类即可,spring ioc最大的好处就是实现了类与类之间解耦,实例化所有的bean,管理bean之间的依赖注入,维护代码的时候可以更加的轻松便利
spring AOP理解
我们在开发中经常遇到重复的代码,比如给某个方法添加事物,以及添加日志,我们一般都是重复的写这些代码,因此当我们要把某个方法的之前添加的日志和事务删除,或者修改,涉及到许多地方,因此具有相同的修改的问题,我们都可以使用AOP解决。
比如下面方法,
代码语言:javascript复制public interface IBuy {
String buy();
}
代码语言:javascript复制@Component
public class Boy implements IBuy {
@Override
public String buy() {
System.out.println("男孩买了一个游戏机");
return "游戏机";
}
}
代码语言:javascript复制@Component
public class Girl implements IBuy {
@Override
public String buy() {
System.out.println("女孩买了一件漂亮的衣服");
return "衣服";
}
}
我们要在Boy和Girl在调用Buy之前添加打印一些其他语句,此时我们就可以使用AOP.我们把IBuy中的buy叫做切点,而我们要实现添加打印其他语句的处理就叫做通知(也就是增强处理),我们把这个切点和通知结合叫做切面,而当在应用执行过程中把增强处理插入到切点的点叫做连接点,而这个添加增强处理的过程就是织入,
我们上面例子的增强处理如下
代码语言:javascript复制@Aspect
@Component
public class BuyAspectJ {
@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void haha(){
System.out.println("男孩女孩都买自己喜欢的东西");
}
}
然后开启AOP配置如下
代码语言:javascript复制@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
打印语句如下
可以看到我们并没有修改原有的方法,也没有修改任何代码,然而给相应的接口添加了新的功能,几乎没有任何侵入,这就是AOP.而AOP的底层实现原理就是动态代理
动态代理
动态代理其实就是创建一个代理类,创建这个代理类的实例对象,这个里面引用你真正使用的类,所有的方法调用,都是走代理类的对象,他负责在代码上做一些增强,在去调用你的类
而spring aop 中,比如你对一批类和他们的方法进行做了一个切面,定义好要在这些类的方法增强代码,sprin必然会生成这些类的代理对象,在动态代理中执行你定义的代码
如果你的类是实现某个接口,spring 就会使用JDK动态代理,生一个跟你实现同样接口的一个代理类,构造一个实例对象出来,jdk代理对象,他其实是在你的类在有接口的时候使用
但是很多时候,我们的类并没有实现接口,因此我们就要用到了CGLIB生成代理对象,他是生成类的一个子类,他可以动态生成字节码,覆盖你的方法,在方法中加入增强的代码
JDK动态代理实例代码
代码语言:javascript复制public class PersonServiceImp implements PersonService {
@Override
public void eat() {
System.out.println("吃饭");
}
}
代码语言:javascript复制public class ProxyService implements InvocationHandler {
private Object target;
public ProxyService(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("洗手");
Object result = method.invoke(target, args);
System.out.println("洗碗");
return result;
}
public Object getProxy(){
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
this.target.getClass().getInterfaces(),this);
}
}
代码语言:javascript复制public class ProxyTest {
public static void main(String[] args) {
PersonService personService = new PersonServiceImp();
ProxyService proxyService=new ProxyService(personService);
PersonService proxy = (PersonService) proxyService.getProxy();
proxy.eat();
}
}
CGlib动态代理实例代码
代码语言:javascript复制public class Studtent {
public String study(String subject) {
System.out.println("study" subject "中......");
return subject;
}
}
代码语言:javascript复制public class StudentProxy implements MethodInterceptor {
// 获取代理对象,参数为目标对象的Class类对象
public Object getProxy(Class target){
// 以下四句代码记住即可,反正就是创建代理类对象的方法,底层不要多在意
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(target);
enhancer.setCallback(this);
return enhancer.create();
}
// 重写intercept方法
// 参数分别为代理对象,目标对象方法,方法参数,代理对象方法
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 增强方法
System.out.println("准备学习了!");
// 利用反射机制,通过代理对象的方法,实现目标对象的方法调用,这里可以有其他代码方式,后面介绍
Object invoke1 = methodProxy.invokeSuper(proxy, args);
// 增强方法
System.out.println("一个小时过去了,学习完了!");
// 返回方法执行后的返回值
return invoke1;
}
}
代码语言:javascript复制public class Test {
public static void main(String[] args) {
Studtent target = new Studtent();
// 创建代理对象,注意用到了强转
Studtent student_proxy = (Studtent) new StudentProxy().getProxy(target.getClass());
// 执行目标对象的方法,增强过的,并且有返回值
String subject1 = student_proxy.study("中文");
// 输出返回值,就是传入的参数
System.out.println(subject1);
}
}
spring中的bean是线程安全的吗
答案默认情况下不是线程安全的,首先我们了解一下spring 容器的bean作用域
- singleton,默认作用于容器中只有一个实例bean
- prototype,为每一个bean请求提供一个实例
- request,为每一个网络请求创建一个实例,在完成请求后,bean会失效并垃圾回收
- session,与request范围类似,确保每个session中有一个bean实例,在session过期后,bean会随之失效
- global-session,全局会话,所有会话共享一个实例。
对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。
如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。
对于有状态的bean,Spring官方提供的bean,一般提供了通过ThreadLocal去解决线程安全的方法,比如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等。
注:Spring容器本身并没有提供线程安全的策略,因此是否线程安全完全取决于Bean本身的特性
spring事务实现原理是什么以及传播机制的理解
事务的实现原理,如果说加了一个@Transactional注解,此时就spring会使用AOP是想,对你的这个方法执行之前,先去开启事务,执行完毕之后,根据你的方法是否报错,来决定回滚还是提交事务
- PROPAGATION_REQUIRED,如果当前没有事务,就创建一个新事务,如果当前存在事务就加入该事务,该设置是最常用的设置
@Transactional(propagation=Propagation.REQUIRED)
private void methodA(){
//do somethin
methodB()
}
@Transactional(propagation=Propagation.REQUIRED)
private void methodB(){
//do somethin
}
当调用methodA里面调用methodB的时候,methodB加入methodA的事务中,而当单独调用methodB就会开启一个新的事务
- PROPAGATION_SUPPORTS,支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就一非事务执行
@Transactional(propagation=Propagation.REQUIRED)
private void methodA(){
//do somethin
methodB()
}
@Transactional(propagation=Propagation.SUPPORTS)
private void methodB(){
//do somethin
}
如果methodA调用methodB的时候,methodB就会加入methodA开始的
事务中,当单独调methodB的时候,methodB就会以非事务执行
- PROPAGATION_MANDATORY,支持当前事务,如果当前存在事务,就加入该事务,如果不存在,则抛出异常
@Transactional(propagation=Propagation.REQUIRED)
private void methodA(){
//do somethin
methodB()
}
@Transactional(propagation=Propagation.MANDATORY)
private void methodB(){
//do somethin
}
如果methodA调用methodB的时候,methodB就会加入methodA开始的事务中,单独调methodB就抛出异常
- PROPAGATION.REQUIRES_NEW,创建一个新的事务并挂起当前事务
@Transactional(propagation=Propagation.REQUIRED)
private void methodA(){
dosomethingBefore();
methodB();
doSomethingAfter();
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
private void methodB(){
//do somethin
}
methodA开启一个事物1,然后调用methodB,也会开启一个事务2,两个事务互不干扰,当methodB进行回滚只会回滚自己的方法,当methodA也是会回滚自己的方法
- PROPAGATION.NOT_SUPPORTED.以非事务方式执行,如果存在事务则抛出异常
@Transactional(propagation=Propagation.REQUIRED)
private void methodA(){
methodB();
}
@Transactional(propagation=Propagation.NOT_SUPPORTED)
private void methodB(){
//do somethin
}
methodA调用methodB就会把当前事务挂起,单独使用methodB就会以非事务执行
- PROPAGATION.NEVER,以非事务方式执行,如果存在事务,则抛出异常
@Transactional(propagation=Propagation.REQUIRED)
private void methodA(){
methodB();
}
@Transactional(propagation=Propagation.NEVER)
private void methodB(){
//do somethin
}
如果methodA调用methodB就会抛出异常,单独代用methodB以非事务执行
- PROPAGATION.NESTED,如果当前存在事务,则在嵌套事务内执行,如果当前没有事务则按REQUIRED属性执行
@Transactional(propagation=Propagation.REQUIRED)
private void methodA(){
dosomethingBefore();
methodB();
doSomethingAfter();
}
@Transactional(propagation=Propagation.NESTED)
private void methodB(){
//do somethin
}
methodA开启事务,执行methodA代码,设置一个回滚点savepoint,
然后调用methodB执行代码,如果methodB抛出异常,此时回滚到之前的savePoint,执行methodA的代码,domethindAfter,提交或回滚事务,但是如果methodA进行回滚也会把methodB的事物进行回滚
也就是外层事务进行回滚会导致内层事务一起回滚,而内层事务只会回滚自己的代码
spring的bean的声明周期
- bean的实例化,对于beanFactory一般是延迟实例化,即使getBean方法才会实例化,但是对于ApplicaionContext,当容器初始化完成之后,就完成了所有bean的实例化,实例化对象被包装在beanWrapper对象中,BeanWrapper提供设置对象数据的接口,从而避免了使用反射机制设置属性
- 设置对象属性依赖注入,实例化的对象封装在beanWarpper对象中,紧接着,spring根据BeanDefinition中的信息以及通过BeanWarapper提供的设置的属性接口完成依赖注入
- 处理Aware接口,如果实现BeanNameAwate接口,则spring调用Bean的setBeanName传入bean的id,如果实现了BeanFactoryAware接口,则spring则setBeanFactory传入当前工作实例的引用,如果实现ApplicatonContextAware接口,则spring调用setApplicationContext传入当前ApplicationContext实例引用
- 如果实现了BeanPostProcessor,在初始化实例之前,对bean进行前置处理
- 如果实现了initializingBean接口,则调用afterPropertiesSet方法,仅做一些额外的逻辑处理
- 此时如果配置文件配置了init-method指定了初始化方法,则调用该初始化方法
- 如果实现了BeanPostProcessor,实例化之后,对bean进行后置处理
- 如果bean实现了DisposableBean接口,则sprign会调用destory将方法进行销毁,如果配置文件配置了destory-method属性指定了销毁方法,则对该方法对bean进行销毁
spring有哪些设计模式
工厂模式,我们使用的spring IOC使用的就是工厂模式,spring容器把所有的bean进行实例化,管理bean的生命周期
单例模式,spring的默认作用于就是使用的是单例模式,
代码语言:javascript复制/**
* 懒汉模式
*/
public class Singleton {
private static volatile Singleton singleton = null;
public Singleton() {
}
private Singleton getSingletonInstance(){
if (singleton==null) {
synchronized (Singleton.class){
if (singleton==null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
代理模式,如spring AOP的底层原理使用的动态代理。
Spring MVC执行流程
spring cloud核心架构原理
Eureka服务注册原理
服务注册发现
- 服务B注册到Eureka的注册表中
- Eureka里面有两级缓存,ReadOnly,ReadWrite缓存,这样做的原因是为了防止并发读写
- 当服务B注册到了之后,会立刻同步到ReadWrite缓存中,然后后台有一个定时任务,会定时发注册的信息,同步到ReadOnly缓存中
- 当注册到ReadOnly缓存中之后,服务A每隔几十秒就会从Readonly拉去注册信息,
- 当服务B的另外一个实例启动,注册到Eureka中,而此时就会把同步到ReadWrite中,然后再每隔几十秒就会就会同样同步到ReadOnly缓存
- 但是此时服务A,还是不知道服务B另外一个实例上线了
- 当过了十几秒之后,服务A发现ReadOnly缓存和ReadWrite缓存都已经没有信息了,此时就会把注册表的信息,同步到ReadWrite和ReadOnly缓存中,服务A就会得到最新的注册信息
- 此时服务A就会有服务B的两台实例的信息
服务心跳与故障
- 每个服务服务都会定时想Eureka发送心跳
- 而Eureka也会有一个线程检测心跳,此时发现服务B有一个实例已经很长时间发送心跳,就会把注册表的信息删除
- 但是此时并不是删除ReadWrite缓存的信息,而是清空ReadWrite缓存,然后后台线程个十几秒会更新信息到ReadOnly缓存,此时ReadWrite和ReadOnly的信息都是空,而此时服务A并并不知道服务B的一个实例已经下线,此时还会请求,但是请求失败
- 当服务A到了配置的时间,就会拉去readOnly缓存,和ReadWrite缓存发现没有,就会把注册表的信息,同步到ReadOnly和ReadWrite缓存中
- 此时服务A也会得到最新的注册信息,此时才会知道原来服务B的一个实例已经下线了。
所以我发现,当我们注册一个实例服务之后,发现很长时间都没有成功发现,就是由于你配置的的时间过长了。
最后我们知道spring cloud对外发布的其实就是http协议的接口,而其实feign,他就是对一个接口加上一个注解,然后对有这个注解的接口生成动态代理,然后针对fegin的动态代理代用他的方法的时候,此时会在底层生成http协议格式的请求,
使用http协议框架组件,httpClient,先得使用ribbon去本地的Eureka注册表缓存获取对方的机器列表,然后进行负载均衡,选择一台机器,接着针对这个机器发送http请求,
而其中zuul,是根据配置的请求路径和服务的关系,然后匹配到对应的服务,在转发请求到对应的服务。
Eureka和zookeeper区别
集群方式
Eureka集群
zookeeper集群
Eureka部署一个集群,但是集群的每个机器是平等的,各个服务可以向任何一个Eureka实例服务注册和服务发现,集群里任何一个Eureka实例接收到写请求之后,会自动同步给其他所有的Eureka实例
zk,是有一个leader节点接受数据,也就是服务注册,其他节点为follower,服务服务发现,leader节点会同步信息到其他机器
CAP
ZK,当leader节点挂了,要重新选举leader,在期间是不能继续写数据的,这里保证了一致性,但是牺牲了可用性,
Eureka,当一个服务注册到了Eureka的A,此时没有同步到其他服务,就挂掉了,此时从其他机器服务拉去的信息是旧的信息,看不到最新的数据,保证了服务的可用性,但是牺牲了一致性,而Eureka通过心跳往其他服务注册信息,保证了最终一致性。
服务注册发现的失效性
对应zk,失效性更好,注册或者宕机,都是秒级别的
而对于Eureka就非常糟糕,上面我们看看Eureka服务发现非常慢的,由于后台使用缓存机制,导致服务上线后,极端情况下要等到2-3分钟,才能感知到服务
容量
其实zk和Eureka都不适合大的容量,比如zk,每一个的服务注册,都会同步到其他机器,且要通知其他服务,若干后台有上万服务,可能就会占用大多数的资源和带宽
而Eureka也是一样,每一个Eureka实例对每一个服务的注册和心跳都是要同步的其他Eureka服务,比如Eureka要对上万台机器进行心跳检测,这个的检测结果同样也会同步到其他Eureka服务,这样服务也是支持不了的
持续关注,下一篇dubbo全面总结,如果对您有一丝丝帮助,麻烦点个关注,也欢迎转发,谢谢