V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
NoKey
V2EX  ›  程序员

新手求教,获取 bean 可以 return this 么

  •  
  •   NoKey · 70 天前 · 2226 次点击
    这是一个创建于 70 天前的主题,其中的信息可能已经有所发展或是发生改变。

    有一个 bean

    @Component
    public class DemoParam {
    }
    

    然后,我有个类,不能通过注入的方式拿到这个 bean 的对象

    常规的方式是通过 context 去获取

    但是我觉得这样写出来不好看,还无法复用

    于是我在DemoParam中写了几个方法

     private static DemoParam demoParam;
     @PostConstruct
     public void init(){
     	demoParam = this;
     }
     public static DemoParam instance(){
     	return demoParam;
     }
    

    这样,我只需要调用instance()方法,就可以拿到 spring 创建的这个 bean 对象

    请教一下大家,这样有没啥弊端呢,谢谢

    31 条回复    2022-09-30 18:56:44 +08:00
    Bronya
        1
    Bronya  
       70 天前
    应该需要注意一下调用的先后顺序吧,instance()是静态方法,如果调用时间比 bean 初始化还早就空指针了。
    (我也是菜鸟 0.0 )
    NoKey
        2
    NoKey  
    OP
       70 天前
    @Bronya @PostConstruct 这个可以保证是在构造函数执行完成后执行,那就是对象已经创建完成了吧
    zmal
        3
    zmal  
       70 天前
    如果是单例的,这样写使用上没啥问题。只是说不能替换实现了。
    rookie4show
        4
    rookie4show  
       70 天前
    public static DemoParam instance(){
    return context.getBean("");
    }
    Bronya
        5
    Bronya  
       70 天前
    @NoKey #2 spring 容器起来之后就没啥问题,我指的是 spring 启动过程中,也就是这个 bean 还没初始化的时候,你就调用了那就肯定获取不到,当然这种情况估计比较少(因为不知道你这个 bean 是干啥的)。

    另外 4 楼的方式每次都会查找一次,反而不如楼主的方式吧(个人感觉)。
    facelezz
        6
    facelezz  
       70 天前
    理论上讲肯定是不对的,demoParam 对不同的线程没有 JMM 的约束,那么调用者拿到的值可能是"this"也可能是 null (即使 demoParam = this 先执行)
    GuuJiang
        7
    GuuJiang  
       70 天前   ❤️ 1
    不可以,很多时候 context.getBean 拿到的是一个经过了代理的对象,这也是 Spring 的各种黑科技能够发挥作用的基础,你这样的方式拿到的 bean 和 context.getBean 拿到的连 class 都不一样
    BQsummer
        8
    BQsummer  
       70 天前
    7L 说的对, 可以试试自己注入自己, instance()返回注入的对象
    facelezz
        9
    facelezz  
       70 天前
    而且感觉属于 "有问题 A 自己想通过 B 解决 来论坛问 B 怎么实现" 建议说明为什么不能通过注入的方式 避免设计问题
    NoKey
        10
    NoKey  
    OP
       70 天前
    @GuuJiang 我试了一下,写了一个测试代码

    @Autowired
    DemoParam demoParam;
    @Test
    public void testest(){
    DemoParam myDemo = DemoParam.instance();
    System.out.println(demoParam);
    System.out.println(myDemo);

    DemoParam myDemo2 = new DemoParam();
    System.out.println(myDemo2);
    }

    就是把注入的,this 返回的,new 的 几个对象打印出来
    得到的结果是

    [email protected]
    [email protected]
    [email protected]

    也就是自动注入的和 this 返回的是同一个对象?
    zmal
        11
    zmal  
       70 天前
    写代码测试了下,7L 说的对。
    NoKey
        12
    NoKey  
    OP
       70 天前
    @facelezz 调用方的类,是一个普通类,不是 bean ,无法自动注入,所以得获取一次这个 bean 的对象
    zmal
        13
    zmal  
       70 天前
    @NoKey 你把你的 DemoParam 写个方法加个事务注解再看看是不是一个对象。
    spring 的大部分功能是通过 AOP 实现的,AOP 可以是编译时,也可以是运行时。
    NoKey
        14
    NoKey  
    OP
       70 天前
    @zmal 你看我 10 楼发的测试方法对不对
    facelezz
        15
    facelezz  
       70 天前
    @NoKey 7L 说的是 你实在想要也只能 context.getBean 因为你的 DemoParam 如果有类似事务或者其他增强功能的注解 你拿到的 this 是源对象,getBean 返回的是代理对象 你的这个测试说明不了什么问题。

    此外你的 instance 代码 本身就是错的 没什么意义
    BQsummer
        16
    BQsummer  
       70 天前
    @NoKey 你在类上加个事务注解就会发现对象不一样了
    NoKey
        17
    NoKey  
    OP
       70 天前
    @facelezz 啊,请问一下,instance 代码 本身就是错的 这个怎么理解,谢谢
    bianjp
        18
    bianjp  
       70 天前
    Spring 并不会对所有 bean 做代理,只有使用到了一些功能时才会,比如 `@Transactional`, `@Validated`, `@Async`。

    可以看下 `org.springframework.beans.factory.config.BeanPostProcessor` 接口,这个接口允许对 bean 实例做处理,然后返回封装 /代理后的对象。
    facelezz
        19
    facelezz  
       70 天前
    @NoKey 执行 demoParam = this 的线程 A 和 执行 instance()的线程 B 没有 happen-before 关系 B 可能永远看见的都是 null
    zmal
        20
    zmal  
       70 天前
    @NoKey 我们平时写的 XXXservice 这是个源类,在 spring 容器里,spring 会对它进行各种增强,增强依赖于动态代理或 CGLIB 技术,不管底层原理是什么,最终都是生成了源类的子类这种方式。
    这就意味着你从 context 中拿到的 bean 和 源对象 this 返回的 bean 不一定是同一个。
    NoKey
        21
    NoKey  
    OP
       70 天前
    @facelezz 单就这个问题进行讨论,我的想法是,把赋值放在 @PostConstruct 中,这个是 servlet 执行时必然调用一次,那么只要是服务可用状态,赋值肯定被执行了,然后,在单例模式下,这个就是对一个对象变量,应该没有线程安全问题
    NoKey
        22
    NoKey  
    OP
       70 天前
    @zmal 感谢,明白这个问题了
    zmal
        23
    zmal  
       70 天前
    @facelezz 这里没有 happen-before 问题,@PostConstruct 注解在 springboot 启动该对象初始化后已经执行了。
    happen-before 也不是这么用的。
    facelezz
        24
    facelezz  
       70 天前
    @zmal 你说的应该是 实例方法下 获取没这个问题 因为注入时的获取 是 spring 在 map 上加锁才有先后顺序(lock-unlock) 楼主直接请求的静态方法静态变量 正常来讲是没有这个保证的
    facelezz
        25
    facelezz  
       70 天前
    @zmal
    @NoKey
    对这个问题有疑惑的话 可以参考 https://stackoverflow.com/questions/23906808/should-i-mark-object-attributes-as-volatile-if-i-init-them-in-postconstruct-in
    里面讲了一般情况下 为什么没有可见性( happen-before )问题
    Bronya
        26
    Bronya  
       70 天前
    所以结论是啥呀,楼主这么写到底有没有问题,是不是在没有增强时(@Transactional 或者其他 aop )楼主这么写也可以的?
    NoKey
        27
    NoKey  
    OP
       70 天前
    @Bronya 我理解的是,保险起见,还是用 context
    siweipancc
        28
    siweipancc  
       70 天前 via iPhone
    会寄,但是可以多测试一下,生命周期暂时稳定(服务器顺序不定)就行,爆了再说。
    siweipancc
        29
    siweipancc  
       70 天前 via iPhone
    上边的提到非唯一问题,只要保证单栗模式+不允许覆盖定义就行。你这样写完全没有问题。

    甚至可以定义成内部静态字段=context.get(),只要第一次方法引用时 app ready 就行
    duteliang
        30
    duteliang  
       70 天前
    不会有问题的,但是要保证这个静态方法不要在项目启动,初始化的时候调用。不然因为 bean 的加载顺序问题有概率空指针。
    liuzhaowei55
        31
    liuzhaowei55  
       70 天前 via iPhone
    直接写个单例,然后自己 new 这个类?现在这样怪怪的,或者参考下许多 BeanUtils 的写法,都可以不用注入 context 静态获取实例,关键字如 cola- domains
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2841 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 50ms · UTC 12:46 · PVG 20:46 · LAX 04:46 · JFK 07:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.