Spring5源码底层剖析之Bean

Spring是一个开源的轻量级Java开发框架,它提供了一套完整的解决方案来开发企业级应用程序。Spring框架的核心是IoC(控制反转)和AOP(面向切面编程),它们使得开发者可以更加专注于业务逻辑的实现,而不需要过多关注底层的技术细节,很多都离不开Java底层的IO,多线程,反射等。gitte已经上传spring5源码,可以作为本地镜像下载阅读学习。learn: 学习资料,源码或者部分代码 (gitee.com)icon-default.png?t=N7T8https://gitee.com/springLuojiawei/learn

后续内容主要围绕底层的IOC和AOP进行讲解。

以下部分是@Bean注解事务的源码

//该注解可以应用在方法和注解类型上。
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
//该注解在运行时可用。
@Retention(RetentionPolicy.RUNTIME)
//该注解会包含在JavaDoc中。
@Documented
public @interface Bean {
/*用于声明value属性和name属性之间的别名关系。如果使用了value属性,则name属性也会被赋予相同的值。*/
   @AliasFor("name")
   String[] value() default {};

    
   @AliasFor("value")
   String[] name() default {};
  //标记为废弃的注解属性,不推荐使用。
   @Deprecated
   Autowire autowire() default Autowire.NO;/*用于指定自动装配的行为,默认值是Autowire.NO,表示禁用自动装配*/

   
   boolean autowireCandidate() default true;// 标记为废弃的注解属性,不推荐使用。

 
   String initMethod() default "";// 用于指定初始化方法的名称,默认为空字符串

   
   String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;/*用于指定销毁方法的名称,默认值是AbstractBeanDefinition.INFER_METHOD,表示根据约定推断销毁方法。*/

}

谈到关于SpringBean那么肯定离不开SpringBean的生命周期了,SpringBean的生命周期吧简单来说无非是实例化-初始化-销毁三个阶段(当然实例化之后需要进行属性赋值通过setter/构造函数等方法),下面是细讲:

 
首先实例化 bean,调用createBean方法进行初始化,Bean容器利用Java Reflection API(反射)创建一个Bean的实例。
执行lnitializingBean方法:
Spring根据beanDefination,设置对象属性
如果Bean实现了InitializingBean实现接口,执行afterPropertiesSet()方法。
处理Aware接口:
Spring会检测该对象是否实现了xxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的一些资源:
BeanPostProcessor前置处理:
如果想对Bean进行一些自定义的前置处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforelnitialization(Object obj, String s)方法。
BeanPostProcessor后置处理:
如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterinitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
DisposableBean:
当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy)方法。

图解Bean的生命周期

那么在Spring中对于SpringBean的管理方式又有哪些呢?

四种方法

lnitializingBean和 DisposableBean 回调接口
针对特殊行为的其他Aware接口
Bean配置文件中的Custom init(方法和destroy()方法
@PostConstruct和@PreDestroy 注解方式

生命周期结束了那么谈一谈对于SpringBean的作用域吧

singleton:这种 bean范围是默认的,这种范围确保不管接受到多少个请求,每一个容器中只有一个bean的实例,单例的模式由bean factory自身来维护。
prototype:原型范围与单例范围相反,为每一个bean请求提供一个实例。
request:在请求bean范围内会每一个来自客户端的网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
Session:与请求范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效
global- session: global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet 共用全局的存储变量的话,那么这全局变量需要存储在global-session中。
那么如何修改Bean的作用域呢?
修改Bean 的作用域使用@Scope()注解修饰Bean(参数填上作用域名),即可修改Bean 的作用域,不加注解默认为singleton即单例模式!

这里有个问题,或许面试会问应届生吧?

Spring中的单例Bean是线程安全的吗?

在Spring中并没有对单例模式的Bean进行任何的多线程封装处理,在涉及到单例bean的线程安全以及并发的情况下作为开发者的我们应该自己去搞定。

在某种程度上说,单例Bean是线程安全的。这是因为Spring容器会在创建单例Bean时,使用锁来保证只有一个线程可以访问和使用该Bean。在单例Bean的初始化完成后,多个线程可以并发地访问和使用该单例Bean,而不会出现线程安全问题,然而,需要注意的是,如果在单例Bean中存在可变的共享状态,例如实例变量,那么在多线程环境下,对该状态的修改可能会导致线程安全问题。在这种情况下,需要采取适当的措施来确保线程安全,例如使用线程安全的数据结构,或者在关键代码块上使用同步机制,如synchronized关键字。

另外,需要注意的是,如果将单例Bean注入到多个线程作用域的Bean中,或者将单例Bean作为实例变量注入到多个原型作用域的Bean中,那么就需要谨慎处理线程安全问题,因为多个线程可能会同时访问和修改这些共享的Bean实例。

单例Bean在Spring中是线程安全的,默认情况下Spring容器会保证单例Bean的线程安全性,但是需要注意在单例Bean中的共享状态,需要采取适当的措施来确保线程安全性。

一个好的解决办法就是将多态的Bean作用域原本的singleton 改变为 prototype,示例如下

@Component
@Scope("prototype")
public class ThreadSafeBean {
    private int count;

    public ThreadSafeBean() {
        count = 0;
    }

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

我们使用了Spring的@Scope注解将ThreadSafeBean的作用域设置为prototype,即每次获取Bean时都会创建一个新的实例。这样每个线程都会拥有自己独立的ThreadSafeBean实例,避免了并发访问的问题。

需要注意的是,将作用域设置为prototype可能会导致创建大量的对象,对内存和性能造成压力,所以还是得根据业务场景来设计方案。

以下部分代码示例,设计一个线程安全的Bean类,以解决多线程环境下的并发访问问题:

 
public class ThreadSafeBean { 
private volatile int count; 
private final Object lock;
public ThreadSafeBean() {
 count = 0; lock = new Object(); 
} public void increment() { 
synchronized (lock) { count++; 
} 
} public int getCount() { 
synchronized (lock) { return count; }
 } 
}

在上述示例中,我们使用一个私有的Object类型的lock对象作为同步锁。通过synchronized关键字控制对count变量的访问。

注意,在count变量声明之前加上了volatile关键字。这是为了确保当一个线程修改了count的值时,其他线程能够立即看到最新的值。volatile关键字保证了可见性。

使用对象级别的锁,每次修改或访问count变量时都要先获得该锁。这样可以确保在同一时间只有一个线程能够访问或修改count变量,从而避免了线程安全问题。

今天就暂时到这里吧,做个自我介绍吧我是一名大二的大专生同时也是打算死磕Java开发的狠人啦!博客写得不怎么好请多多提出问题啦!感谢你的阅读希望有用啦!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值