Spring学习笔记1

    技术2022-07-12  72

    一、spring生命周期回调

            在spring的官网中1.6的部分讲解了关于spring生命周期的回调;分为两种回调,一种是bean的回调,还有一种是容器的回调。

    1、bean的回调

            spring中的bean的回调方式,官网中有进行讲解,有三种的回调方式:

    1、实现InitializingBean和DisposableBean分别可以实现创建bean后使用的方法以及销毁bean钱调用的方法;

    2、使用init()还有destory()方法,在xml配置中使用init-method属性来指定一个回调方法;

    3、使用@PostConstruct和@PreDestroy注释;

    @Component public class Service1 implements InitializingBean { @Autowired Service2 service2; public Service1(){ System.out.println("这里是构造方法"); } public void afterPropertiesSet() throws Exception { System.out.println("用来实现init接口的回调方法"); } } @Component public class Service2 { public Service2(){ System.out.println("这里是service2的构造方法"); } public void name(){ } } public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class); }

            由于我们使用的是默认的单例singleton方式,所以在初始化容器的时候立马会去初始化bean,后面会将单例与prototype区别。根据打印的结果,可以看出先加载类,然后再去初始化bean,然后会直接实现回调方法。

            下面使用注解的方式来进行实现,并同时比较这两种的执行顺序,以及是否能够同时实现多种回调方式。

    @Component public class Service1 implements InitializingBean { @Autowired Service2 service2; public Service1(){ System.out.println("这里是构造方法"); } @PostConstruct public void huiDiao(){ System.out.println("使用注解实现的回调方法"); } public void afterPropertiesSet() throws Exception { System.out.println("用来实现init接口的回调方法"); } } @Component public class Service2 { public Service2(){ System.out.println("这里是service2的构造方法"); } public void name(){ } } public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class); }

            根据打印结果可以得出几个结论:1、多个回调方法可以同时使用;2、会先调用注解的才去调用实现接口的。并且相关知识点在官网里也有说到。

    2、容器的回调

            官网中关于容器的回调说明就在bean的回调说明之后。Spring的声明周期回调除了bean的回调还有容器的回调,就是在初始化容器完成后需要进行某操作,或者在销毁容器之前需要进行的回调。有两种方式,一种是使用一个类实现Lifecycle接口,并实现他的三个方法,在官网中也提供了这种方式,但是这种方式需要手动在初始化之后进行start启动;还有种方式可以实现自动启动,实现SmartLifecycle接口,实现另外三个方法,将其中的isAutoStartup方法的false改为true实现自动调用,其中的stop方法优先于Lifecycle中的stop方法执行,SmartLifecycle接口是继承Lifecycle接口的。

            首先实现Lifecycle接口来看下是否能够实现回调。

    @Component public class InitBack implements Lifecycle { public void start() { System.out.println("start"); } public void stop() { System.out.println("stop"); } public boolean isRunning() { return false; } } public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class); context.start(); }

            然后来实现SmartLifecycle接口来实现回调。

    @Component public class InitBack implements SmartLifecycle { public void start() { System.out.println("start"); } public void stop() { System.out.println("stop"); } public boolean isRunning() { return false; } public boolean isAutoStartup() { return true; } public void stop(Runnable callback) { } public int getPhase() { return 0; } } public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class); } }

            上面两种方式都能实现回调,使用SmartLifecycle不需要手动进行调用,但是需要将isAutoStartup方法返回值改为true。

    二、单例与原型的区别

            在进行回调的实现过程中突然想到可以借用回调来进行对单例与原型初始化时机的一个测试。我们同时使用bean的回调以及容器的回调。因为bean的回调是在加载bean之后的,容器的回调是在容器初始化之后的,如果我们将一个bean设置为原型的话,那么在getBean之前应该只能打印出容器的回调,而打印不出bean的回调。下来就来进行测试。代码还是使用测试回调的代码,就是将service1这个类设置为原型。

    @Component @Scope("prototype") public class Service1 implements InitializingBean { @Autowired Service2 service2; public Service1(){ System.out.println("这里是构造方法"); } public void userService(){ System.out.println(service2); service2.name(); } public void setService2(Service2 service2) { this.service2 = service2; } @PostConstruct public void huiDiao(){ System.out.println("使用注解实现的回调方法"); } public void afterPropertiesSet() throws Exception { //该方法用于创建该bean之后进行回调实现的内容 System.out.println("用来实现init接口的回调方法"); } }

            这是优点改变的service1的代码。

    public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class); }

            我们先只进行容器的初始化,来看下打印结果。

            根据结果我们可以看到由于service2是单例的所以已经进行了bean的初始化,但是我们的bean回调都是在service1中,并没有进行相关任何打印,所以service1此时都没有被加载到,更别说初始化为bean了。并且此时容器的回调已经实现了。接下来使用getBean进行调用。

    public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class); context.getBean(Service1.class).userService(); }

            根绝结果看到在使用getBean时,首先会去加载service1,然后再进行bean的初始化,所以此时bean的两种回调方式都实现了。

            Spring具有singleton以及properts两种;区别其实是singleton是在spring容器初始化的时候就已经进行了实例化,每次调用的都是同一个bean,在不考虑懒加载的情况下,单例在初始化的时候就将所有单例的bean放入一个map容器中,所以每次调用都是去这个容器中拿,拿到的都是同一个对象。而原型是在调用getbean的时候才会去进行实例化,所以每次getbean都是不同的对象。但是当一个原型被一个单例依赖的时候,那么这个原型也会在spring进行初始化的时候进行实例化bean。

    三、当单例依赖原型或者原型依赖原型时

            当一个单例对象依赖一个原型对象时,spring在进行IOC的时候对一个bean只会进行一次,单例在初始化容器的时候就进行实例化,此时因为依赖一个原型对象,所以也会去加载这个原型对象,那么导致这个原型对象会变为一个单例对象。那么怎么去解决呢?

            Spring的官网上给出了两种解决办法:1、在单例对象中引入ApplicationContext对象,然后需要用到原型的时候,再去getBean获取到这个类;但是这样是的耦合性太高。2、使用@Lookup注解,要使用该注解有一定条件,类以及要覆盖的方法不能为final等,这块需要再了解。

    Processed: 0.017, SQL: 9