Spring注解之@Autowired:装配构造函数和属性
在User类中创建一个构造函数,传入参数student:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* @author Wiener
*/
@Component
public class User implements Serializable {
private static final long serialVersionUID = 6089103683553156328L;
private Long id;
private Student student;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Autowired // 构造函数注入参数的方式
User(Student student) {
this.student = student;
System.out.println("------ 为构造器装配Bean成功 ---------");
}
public void isStu() {
student.studentStudy();
System.out.println("------ 装配Bean成功 ---------");
}
}
其中,Student类如下:
import lombok.Getter; import lombok.Setter; import org.springframework.stereotype.Component; import java.io.Serializable; import java.util.Date; /** * @author Wiener */ @Getter @Setter @Component public class Student implements Serializable { private static final long serialVersionUID = -5246589941647210011L; //姓名 private String name; public Student() { System.out.println("A default student constructor." ); } public void studentStudy() { System.out.println("A student is studying." ); } }
改造Spring Boot项目启动类:
import com.east7.bean.User; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; /** * @author Wiener */ @SpringBootApplication public class East7Application { public static void main(String[] args) { ApplicationContext act = SpringApplication.run(East7Application.class, args); User user = (User) act.getBean("user"); user.isStu(); } }
执行测试函数后,控制台打印信息如下:
A default student constructor.
------ 为构造器装配Bean成功 ---------
A student is studying.
------ 装配Bean成功 ---------
说明成员变量已经注入。此处需要注意一点,如果有两个自定义构造方法,而且都没加@Autowired注解,则会报错,因为Spring不知道你要用哪个构造方法初始化;如果只有一个构造方法加了@Autowired注解,那么就会用这个构造方法初始化;同理,如果有多个构造方法都加了@Autowired注解,那么还是会报错提示Only one constructor can have @Autowired annotation。
可以在属性中使用@Autowired 注解来省略 setter 方法。当使用@Autowired为自动连接属性传递的时候,Spring 会将这些传递过来的值或者引用自动分配给那些属性。像下面这样写在属性上并直接引用会报空指针异常:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Wiener
*/
@Component
public class User {
@Autowired //变量注入方式
private Student student;
private Long id = student.getId();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public void isStu() {
id = student.getId();
student.studentStudy();
System.out.println("------ 装配Bean成功 ---------");
}
}
异常信息如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'user' defined in file [xxxxxx\User.class]:
Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException:
Failed to instantiate [com.east7.bean.User]:
Constructor threw exception; nested exception is java.lang.NullPointerException
报错信息提示创建Bean时出错,因为实例化bean时构造方法抛出了空指针异常。如果仅仅使用函数isStu()初始化变量id, 并且把【private Long id = student.getId();】改为 【private Long id;】,则属于方法调用的时候初始化,此时,bean已经注入,不会抛异常。
其实这两种方式都可以使用,但报错的原因是加载顺序的问题,@autowired写在变量上的注入要等到类完全加载后,才会将相应的bean注入,而变量是在加载类的时候按照相应顺序加载的,所以变量的加载要早于被@autowired注解的变量,那么给变量prefix 赋值的时候所使用的a,其实还没有被注入,所以报空指针,而使用构造器则会在加载类的时候将a加载,这样在内部使用a给prefix 赋值就完全没有问题。
如果不使用构造器,那么也可以不给prefix 提前赋值,而是在系统启动后,变量prefix被使用的地方,通过a.getExcelPrefix()进行赋值,此时对a的使用是在类完全加载之后,即a被注入后,所以也是可以的。
总之,@Autowired一定要等本类构造完成后,才能从外部引用设置属性,所以@Autowired的注入时间一定会晚于构造函数的执行时间。但在初始化变量的时候就使用了还没注入的bean,所以导致了NPE。若在初始化其它变量时不使用这个要注入的bean,而是在后期调用方法的时候去初始化,是可以使用这个bean的,因为那时构造函数已经执行完毕,即已注入bean了。一言以蔽之,Java变量的初始化顺序为:静态变量或静态语句块–>实例变量或初始化语句块–>构造函数–>@Autowired 注入Bean。