static final、static、@PostConstruct、构造方法、@AutoWired执行的顺序

static final、static、@PostConstruct、构造方法、@AutoWired执行的顺序

场景

在执行业务的时候 一般的流程是 调用 service来执行

即某个类的上面加入注解 @Service 交由spring容器管理

那么在这个service类中 如果存在 static final / static / @PostConstruct 修饰的变量 或者是方法 执行顺序如何?

	private static final Integer AGE=12;

    static {
        System.out.println("static---static initializer"+AGE);
    }

    @PostConstruct //类初始化完成后,就执行这个方法
    private void init(){
        System.out.println("@PostConstruct--init"+AGE);
    }

如上图所示
那么在项目启动的时候 执行的顺序是 (这里注意 我的静态变量是写在静态代码块上面的 其实哪个先 写 哪个先执行)

  1. 先加载 static final
  2. 然后是执行 static 方法
  3. 最后是执行 @PostConstruct

如果有继承关系,那么就是

1、父类静态变量和静态代码块(先声明的先执行);

2、子类静态变量和静态代码块(先声明的先执行);

3、父类的变量和代码块(先声明的先执行);

4、父类的构造函数;

5、子类的变量和代码块(先声明的先执行);

6、子类的构造函数。

如果加入构造方法和 自动注入 那么顺序是

  1. 静态变量和静态代码块
  2. 构造方法
  3. @AutoWired
  4. @PostConstruct

有继承,静态代码块、构造代码块、构造方法、@Autowired、@PostConstruct 执行顺序

执行顺序:

1、父类静态变量和静态代码块(先声明的先执行);
2、子类静态变量和静态代码块(先声明的先执行);
3、父类的变量和代码块(先声明的先执行);
4、父类的构造方法;
5、子类的变量和代码块(先声明的先执行);
6、子类的构造方法。
7、@Autowired
8、@PostConstruct

静态代码块:仅在类加载时执行一次。

构造代码块:创建一次对象,执行一次

静态方法里不能调用类的实例方法,但可以调用构造方法(因为不需要实例)。

class HelloA {
    static int staticParam = 1;
    int param = 0;

    static {
        System.out.println("static A");
        System.out.println("staticParam=" + staticParam);
    }

    {
        System.out.println("I'm A class");
        System.out.println("param=" + param);
    }

    public HelloA() {
        System.out.println("HelloA");
    }
}

public class HelloB extends HelloA {
    static {
        System.out.println("static B");
    }

    {
        System.out.println("I'm B class");
    }

    public HelloB() {
        System.out.println("HelloB");
    }
}
@Test
public void testStatic() {
	new HelloB();
}

在这里插入图片描述

@PostConstruct注解详解

简介
JavaEE5引入了@PostConstruct和@PreDestroy两个作用于Servlet生命周期的注解,实现Bean初始化之前和销毁之前的自定义操作

使用场景
在项目中主要是在Servlet初始化之前加载一些缓存数据等

API使用说明
PostConstruct 注释用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。此方法必须在将类放入服务之前调用。支持依赖关系注入的所有类都必须支持此注释。即使类没有请求注入任何资源,用 PostConstruct 注释的方法也必须被调用。只有一个方法可以用此注释进行注释。应用 PostConstruct 注释的方法必须遵守以下所有标准:该方法不得有任何参数,除非是在 EJB 拦截器 (interceptor) 的情况下,根据 EJB 规范的定义,在这种情况下它将带有一个 InvocationContext 对象 ;该方法的返回类型必须为 void;该方法不得抛出已检查异常;应用 PostConstruct 的方法可以是 public、protected、package private 或 private;除了应用程序客户端之外,该方法不能是 static;该方法可以是 final;如果该方法抛出未检查异常,那么不得将类放入服务中,除非是能够处理异常并可从中恢复的 EJB。

特点:
1、只有一个非静态方法能使用此注解

2、被注解的方法不得有任何参数

3、被注解的方法返回值必须为void

4、被注解方法不得抛出已检查异常

5、此方法只会被执行一次

servlet执行流程
img

注意事项
使用此注解时会影响服务启动时间。服务启动时会扫描WEB-INF/classes的所有文件和WEB-INF/lib下的所有jar包。

@PostConstruct注解的用法
@PostConstruct是java5的时候引入的注解,指的是在项目启动的时候执行这个方法,也可以理解为在spring容器启动的时候执行,可作为一些数据的常规化加载,比如数据字典之类的。

被@PostConstruct修饰的方法会在服务器加载Servle的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行

也就是加载顺序

服务器加载Servlet -> servlet 构造函数的加载 -> postConstruct ->init(init是在service 中的初始化方法. 创建service 时发生的事件.) ->Service->destory->predestory->服务器卸载serlvet

那么问题:spring中Constructor、@Autowired、@PostConstruct的顺序
Constructor >> @Autowired >> @PostConstruct

依赖注入的字面意思就可以知道,要将对象p注入到对象a,那么首先就必须得生成对象p与对象a,才能执行注入。所以,如果一个类A中有个成员变量p被@Autowired注解,那么@Autowired注入是发生在A的构造方法执行完之后的。

@PostConstruct应用场景:
如果想在生成对象时候完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么就无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。

@PostConstruct
    public void start() {
 
        try {
            int connect = producer.connect();
            if (connect == 0) {
                log.info("producer start success! groupName:{},namesrvAddr:{}", rocketMqProperties.getProducer().getGroupName(), rocketMqProperties.getNamesrvAddr());
 
            }
        } catch (MQException e) {
            e.printStackTrace();
        }
}
 
    @PreDestroy
    public void stop()  {
        try {
            if (producer != null) {
                producer.close();
                log.info("producer closed");
            }
        }catch (MQException a)
        {
            a.printStackTrace();
        }
}

在最近的工作中,get到一个很实用的注解,分享给诸位。
痛点
做过微信或支付宝支付的童鞋,可能遇到过这种问题,就是填写支付结果回调,就是在支付成功之后,支付宝要根据我们给的地址给我们进行通知,通知我们用户是否支付成功,如果成功我们就要去处理下面相应的业务逻辑,如果在测试服务,那么这个回调地址我们就需要填写测试服务的,如果发布到线上那么我们就需要改成线上的地址。

针对上面的场景,我们一般都会通过如下的方式,进行一个动态配置,不需要每次去改,防止出现问题。

public class PayTest {
 
    @Value("${spring.profiles.active}")
    private String environment;
 
    public Object notify(HttpServletRequest request) {
 
        if ("prod".equals(environment)) {
            // 正式环境
        } else if ("test".equals(environment)) {
 
            // 测试环境
        }
        return "SUCCESS";
    }
}

上面的代码看起来没有一点问题,但是身为搬砖的我们咋可能这样搬,姿势不对呀!

问题:
扩展性太差,如果这个参数我们还需要在别的地方用到,那么我们是不是还要使用@Value的注解获取一遍,假如有天我们的leader突然说吗,test这个单词看着太low了,换个高端一点的,换成dev,那么我们是不是要把项目中所有的test都要改过来,如果少还好,要是很多,那我们怕不是凉了。

所以我们能不能将这些配置参数搞成一个全局的静态变量,这样的话我们直接饮用就好了,哪怕到时候真的要改,那我也只需要改动一处就好了。

注意大坑
有的朋友可能就比较自信了,那我直接加个static修饰下不就好了,如果你真是打算这样做,那你就准备卷好铺盖走人吧。直接加static获取到的值其实是一个null,至于原因,大家复习下类以及静态变量变量的加载顺序。

@PostConstruct注解
那么既然说出了问题,肯定就有解决方法,不然你以为我跟你玩呢。

首先这个注解是由Java提供的,它用来修饰一个非静态的void方法。它会在服务器加载Servlet的时候运行,并且只运行一次。

改造:

@Component
public class SystemConstant {
 
    public static String surroundings;
 
    @Value("${spring.profiles.active}")
    public String environment;
 
    @PostConstruct
    public void initialize() {
        System.out.println("初始化环境...");
        surroundings = this.environment;
    }
}

结果:
我们可以看到在项目启动的时候进行了初始化img

到这里我们已经可以拿到当前运行的环境是测试还是正式,这样就可以做到动态配置

img

最后想说
其实这个注解远不止这点用处,像我之前写的Redis工具类,我使用的是RedisTemplate操作Redis,导致写出来的方法没办法用static修饰,每次使用Redis工具类只能先注入到容器然后再调用,使用了这个注解就可以完美的解决这种尴尬的问题。代码如下。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
 
import javax.annotation.PostConstruct;
 
/**
 * @ClassName RedisUtil
 * @Description TODO
 * @Version 1.0
 */
@Component
public class RedisUtil {
 
    private static RedisTemplate<Object, Object> redisTemplates;
 
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
 
    @PostConstruct
    public void initialize() {
        redisTemplates = this.redisTemplate;
    }
 
    /**
     * 添加元素
     *
     * @param key
     * @param value
     */
    public static void set(Object key, Object value) {
 
        if (key == null || value == null) {
            return;
        }
        redisTemplates.opsForValue().set(key, value);
    }
}


一、@PostConstruct介绍

Java提供的注解,被用来修饰方法,被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。

(1)结论:

调用的顺序为: 构造函数 > @Autowired > @PostConstruct

(2)作用:

@PostConstruct注解的方法在项目启动的时候执行这个方法,也可以理解为在spring容器启动的时候执行,可作为一些数据的常规化加载,比如读取数据字典之类、目录树缓存

二、Spring框架中在bean初始化和销毁时候执行实现方式

Spring框架中在bean初始化和销毁时候执行某个方法的三种实现方式。
(1)Spring框架中通过注解@PostConastruct 和 @PreDestroy来实现Bean初始化执行和销毁时候执行方法;

(2)Spring框架中通过实现接口InitializingBean ,DisposableBean来实现Bean初始化执行和销毁时候执行方法;

(3)Spring框架中通过xml配置文件中bean的init-method=“” destroy-method=""来实现Bean初始化执行和销毁时候执行方法;

Spring Bean执行顺序
在这里插入图片描述

三、项目验证

1.MyServiceImpl

package com.huahua.myIdea.service.serviceImpl;

import com.huahua.myIdea.service.MyService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;

@Service
public class MyServiceImpl implements MyService, InitializingBean {

    @Override
    public int addTotal(int x, int y) {
        return 0;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("开始执行 afterPropertiesSet 方法: MyServiceImpl");
    }

    @PostConstruct
    public void postConstructMethod() {
        System.out.println("开始执行 PostConstruct 方法: MyServiceImpl");
    }

    @Autowired
    private void testAutowired(){
        System.out.println("开始执行 testAutowired 方法: MyServiceImpl");
    }

    MyServiceImpl(){
        System.out.println("开始执行 构造函数MyServiceImpl : MyServiceImpl");
    }
}

2.测试结果

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Archie_java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值