Spring初始化Bean生命周期


获取class信息 -> 根据class信息调用构造方法创建对象 -> 判断成员变量中是否有依赖注入注解并进行注入操作 -> 初始化前(@PostConstruct) -> 初始化(实现InitializingBean接口) -> 初始化后(AOP) -> 生成代理对象 -> Bean

  1. 判断类中成员变量是否有指定注解
    可以利用反射获取类信息, 如获取类中所有的方法信息,然后根据isAnnotationPresent方法来获取方法上是否标识指定注解
    如以下所示
    首先定义了一个class类 在类中有一个方法标识PostConstruct注解
@Component
public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;

    @PostConstruct
    public void helloWorld() {
        System.out.println("Hello world!");
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
···
之后利用获取user对象的class类中的所有方法,挨个遍历判断是否有`PostConstruct`注解
```java
        for (Method method : user.getClass().getMethods()) {
            if (method.isAnnotationPresent(PostConstruct.class)) {
                System.out.println(method);
            }
        }

最终输出为

  1. 推断构造方法,调用构造方法进行创建对象
    有以下几种情况:
    首先是只有一个无参构造方法,那么直接调用无参构造方法进行创建对象即可
    其次是多个构造方法,其中有一个无参构造方法,那么直接和上面一样,直接调用无参构造方法进行创建对象即可
    在然后是只有单一构造方法且有参数,那么spring会自动从容器中查找与构造方法参数类型匹配的对象进行注入,如果spring容器中只有一个类实例对象,那么直接将该实例进行注入,如果有多个实例的话,根据构造方法的参数对象的名称进行匹配,若匹配成功则注入成功,否则创建bean失败。
    如果是多个构造构造方法且都有参数可以使用@Autowired注解来指定使用哪个构造方法进行注入。
    查找对象的顺序如下: 1. 根据类型查找实例 -> 根据变量名称进行匹配

PostConstruct注解

PostConstruct注解是在初始化前执行的 判断方法是否有该注解可以根据以上方法进行判断,之后利用反射调用方法即可

实现InitializingBean接口

通过实现该接口来调用初始化后的方法,判断对象是否实现了该接口可以使用instanceof来判断,如果实现了该接口直接将对象强转为InitializingBean,然后调用接口方法即可。

AOP

如果被代理对象不是实现于接口,那么AOP会采用cglib动态代理
cglib动态代理是通过继承原有的类来实现的比如下面代码 user类是被代理对象,当生产代理类是以继承的方式进行生产的,而生成后的代理类中的方法为切入点方法根据不同的通知来完成的不同业务代码, 通过获取之前通过类信息、构造方法以及依赖注入后的对象设置到代理对象中如下代码的target对象,来执行具体方法,这就是为什么被代理对象中的user为空,依旧可以访问原有对象中的数据。这就是为什么代理对象中的属性为空依旧调用的原因。

public class UserProxy extends User {
	private User target;
	
	// 切入点业务 例如通知是before则会先执行切面的业务代码,再执行 被代理对象的业务代码
	public void test() {
	 System.out.println("test");
	 target.test();
	}
}

如下所示
这是由Spring生成的代理对象 对象中的属性为空

在这个代理对象中有个target属性 该属性有值,通过调用该对象中的方法来获取原因对象的属性即可

怎样判断对象是否被切

从Spring容器中找到切面Bean
然后遍历切面Bean中的所有方法,查看方法中的切入点表达式是与关于当前对象匹配,如果匹配就表示该对象需要执行AOP,并将改方法缓存至map中,执行切面方法时直接从map中获取方法执行即可

事务

事务生成逻辑

  1. 判断类方法上是否有事务注解。
  2. 创建数据库连接,数据库连接是由事务管理器创建的。
  3. 关闭MySQL自动提交 autocommit,当autocommit为true是,每执行一条SQL语句,MySQL将会自动为该SQL语句进行提交。
  4. 执行业务代码。
  5. 如果业务代码抛异常则回滚,如果未抛异常则提交。

事务传播机制

有以下代码,@Transactional(propagation = Propagation.NEVER)表示当有事务存在时则抛出异常,以下代码却为抛出异常,原因与Spring AOP原理有关,Spring事务的底层原理就是AOP,AOP在生成代理对象时,有个target属性作为被代理对象,当执行事务方法nerver的业务逻辑时是调用的target中的never方法进行执行,没有判断类方法上是否有事务注解,也就是执行时不是以事务的方式进行执行的,而是直接以调用方法的方式进行执行的,原target对象并不知道该方法是事务方法。

    @Transactional
    public void insertUser() {
        jdbcTemplate.execute("insert into study.user (id, username, password, age)\n" +
                "values (5, 'lyra', '365373011@qq.com', 11);");

        never();

    }

    @Transactional(propagation = Propagation.NEVER)
    public void never() {

    }

解决方法:
可以新建一个类用于存储代理方法,这样的话使用AOP注入时就可以将代理注解所标识的代理方法一并扫描到了,如下代码所示

@Component
public class BaseDao {
    @Transactional(propagation = Propagation.NEVER)
    public void never() {

    }
}

@Service
public class UserDAO {
    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Autowired
    private BaseDao baseDao;

    @Transactional
    public void insertUser() {
        jdbcTemplate.execute("insert into study.user (id, username, password, age)\n" +
                "values (12, 'lyra', '365373011@qq.com', 11);");

        baseDao.never();

    }
}

事务失效的情况

一定要保证事务管理器和DAO接口所使用的数据源必须一致。