Spring IOC 學習

原由

clipboard.png

欲實現路由驗證,寫了一個Hibernate攔截器,在對數據庫進行操做以前對菜單進行校驗,若是存在,則拋出異常,終止保存操做的執行;若是不存在,則繼續執行。攔截器代碼以下:html

package com.mengyunzhi.measurement.interceptor;

import com.mengyunzhi.measurement.Service.WebAppMenuService;
import com.mengyunzhi.measurement.entity.WebAppMenu;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;

import java.io.Serializable;

/**
 * 繼承自Hibernate提供的EmptyInterceptor攔截器
 * 在進行數據庫操做以前進行處理,判斷該路由是否惟一
 * 抽象菜單:則不能相同
 * 其子菜單:若是不屬於同一父菜單,能夠相同
 */
public class WebAppMenuInterceptor extends EmptyInterceptor {

    @Autowired
    private WebAppMenuService webAppMenuService;

    /**
     * 重寫OnSave方法
     */
    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        // 若是是菜單實體
        if (entity instanceof WebAppMenu) {
            // 則執行路由驗證
            this.validateWebAppMenuRoute((WebAppMenu) entity);
        }
        // 調用父類的onSave方法,不太明白這個方法設計返回boolean值是爲什麼?
        return super.onSave(entity, id, state, propertyNames, types);
    }

    /**
     * 路由驗證
     */
    private void validateWebAppMenuRoute(WebAppMenu webAppMenu) {
        // 初始化變量
        Boolean isAbstract = webAppMenu.isAbstract();
        String routeName = webAppMenu.getRouteName();
        Boolean isExist;
        // 若是是抽象菜單
        if (isAbstract){
            // 根據路由名和抽象爲true,判斷是否存在
            isExist = webAppMenuService.existsByRouteNameAndIsAbstractIsTrue(routeName);
        } else {
            // 非抽象,判斷其同一父菜單下是否有相同菜單
            isExist = webAppMenuService.existsByRouteNameAndParentWebAppMenu(routeName, (WebAppMenu) webAppMenu.getParentWebAppMenu());
        }
        if (isExist) {
            throw new DataIntegrityViolationException("該路由非法");
        }
    }
}

可是測試一直通不過,報錯:沒法加載上下文。java

clipboard.png

打印了一下webAppMenuService的值,發現全都是null;果真,這個東西沒注入進來。web

clipboard.png

看來@Autowired並非萬能的,纔有了下文對Spring IOC的學習。spring

系統的學習一下,不要像之前同樣去StackOverflow查答案,而後照搬別人的解決方案,感受如今對Spring的瞭解仍是太少了。數據庫

IOC

IOC:控制反轉,你們都熟知的定義這裏就再也不贅述了。segmentfault

其實,Spring負責幫咱們管理對象,就像我在關於接口的代碼複用中實現的手動注入對象的代碼同樣。架構

/**
 * 鳥
 */
public class Bird extends Animal implements Volitant {

    private Volitant volitant;

    public void setVolitant(Volitant volitant) {
        this.volitant = volitant;
    }

    @Override
    public void fly() {
        this.volitant.fly();
    }
}

/**
 * 蝙蝠
 */
public class Bat extends Animal implements Volitant {

    private Volitant volitant;

    public void setVolitant(Volitant volitant) {
        this.volitant = volitant;
    }

    @Override
    public void fly() {
        this.volitant.fly();
    }
}

public class Main {

    public static void main(String[] args) {
        Volitant volitant = new VolitantImpl();
        Bird bird = new Bird();
        bird.setVolitant(volitant);
        bird.fly();
        Bat bat = new Bat();
        bat.setVolitant(volitant);
        bat.fly();
    }
}

上述代碼是手動對一個類中須要設置的對象進行注入,其實Spring IOC就是一個幫助咱們管理和注入對象的角色。maven

容器

說到對象的管理,不得不提一個「容器」的概念,這個容器裝的是對象。ide

固然,這裏的容器是咱們的想象的一個概念,它的術語名稱你們可能會很熟悉——「上下文」,只是上下文是一種更高級的容器罷了。學習

創建一個maven項目,依賴引入spring-context,咱們以一個實際的Spring使用來學習IOC

clipboard.png

clipboard.png

這是Spring的七個模塊,在IBM開發者學習文檔中找到的。大體看了一下介紹,coreSpring的核心庫,有各類功能,context調用核心庫中的方法,對外提供容器以及其餘功能。

BeanFactory

BeanFactory是一個最基礎的容器接口,提供了從容器中獲取對象,判斷容器中是否有某對象的方法。只有最基礎的容器功能,如今已經不建議使用。

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";

    Object getBean(String var1) throws BeansException;

    <T> T getBean(String var1, @Nullable Class<T> var2) throws BeansException;

    Object getBean(String var1, Object... var2) throws BeansException;

    <T> T getBean(Class<T> var1) throws BeansException;

    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

    boolean containsBean(String var1);

    boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;

    boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, @Nullable Class<?> var2) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String var1) throws NoSuchBeanDefinitionException;

    String[] getAliases(String var1);
}

既然咱們要從容器中拿對象?那咱們就須要告訴IOC容器,你的容器中應該有什麼對象,而且各個對象之間的依賴關係,總不能我寫的每一個類都實例化到容器中吧?因此誕生了xml配置文件。相似下面這樣。

clipboard.png

一個beanid是什麼,對應的是哪一個類,而且這個對象依賴於其餘的什麼對象。

clipboard.png

看了一下,有個刪除線,不建議使用,也就沒有過深的研究,咱們投入上下文的懷抱。

ApplicationContext

ApplicationContext固然也支持xml配置,其實現類爲ClassPathXmlApplicationContext

由於本人更喜歡註解,這裏深刻學習一下註解的配置方式。

模擬項目中的真實架構,Service接口,依賴,爲其注入實現。

TestService接口:

package com.mengyunzhi.spring.service;

public interface TestService {
}

TestService實現:

package com.mengyunzhi.spring.service;

import org.springframework.stereotype.Component;

@Component
public class TestServiceImpl implements TestService {

    @Override
    public String toString() {
        return "我是TestServiceImpl, TestService的實現";
    }
}

你問這裏爲何加的是@Component註解而不是@Service,其實這幾個註解,不管是@Service@Repository@Controller實際上都是聲明一個組件,讓其被Spring所管理,這麼設計,不過是讓人們看代碼上的註解時,就能清晰地瞭解這個類的職責。

clipboard.png

clipboard.png

clipboard.png

注意,這裏加的@Component告訴Spring,你爲我管理這個類。而後還有一個註解,@ComponentScan就是表示我要掃描哪一個包下的有@Component註解的類,並對其進行管理。

主方法:

package com.mengyunzhi.spring;

import com.mengyunzhi.spring.service.TestService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan("com.mengyunzhi.spring")
public class Application {

    public static void main(String []args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        TestService testService = context.getBean(TestService.class);
        System.out.println(testService);
    }
}

新建一個基於註解的上下文,而後獲取TestService這個類的實例,同時打印。注意,這裏的@ComponentScan必定要加,不加會報錯。(我感受這種方式比xml配置簡單多了,@幾下就完成了配置)

運行結果:

clipboard.png

Bean

突發奇想,咱們修改一下toString方法的代碼,相信你看到代碼就知道我想幹什麼了。

package com.mengyunzhi.spring.service;

import org.springframework.stereotype.Component;

@Component
public class TestServiceImpl implements TestService {

    @Override
    public String toString() {
        return "我是TestServiceImpl, TestService的實現\n"
                + "個人內存地址是:"
                + super.toString();
    }
}

修改主方法代碼,如今咱們從上下文中獲取兩個TestService的實例。

package com.mengyunzhi.spring;

import com.mengyunzhi.spring.service.TestService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan("com.mengyunzhi.spring")
public class Application {

    public static void main(String []args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        TestService testService1 = context.getBean(TestService.class);
        System.out.println(testService1);
        TestService testService2 = context.getBean(TestService.class);
        System.out.println(testService2);
    }
}

clipboard.png

咱們發現這兩個對象的內存地址是相同的,說明這兩個對象引用的是同一塊內存。

Scope

這就涉及到Bean對象的Scope屬性,默認的Scope屬性是Singleton,單例,全容器共享這個實例。

若是想使用多例,就將其Scope屬性設置爲Prototype

@Component
@Scope("prototype")
public class TestServiceImpl implements TestService {

    @Override
    public String toString() {
        return "我是TestServiceImpl, TestService的實現\n"
                + "個人內存地址是:"
                + super.toString();
    }
}

TestServiceImpl上加上@Scope註解,注意這裏的prototype必定要是小寫的。

再次運行:

clipboard.png

兩個內存地址不一樣,這是兩個獨立的對象。

爲容器添加對象

咱們本身寫的類,咱們想將其加入到容器中,咱們能夠將其加上@Component註解將其添加到容器中,可是若是咱們想把某個第三方庫中的對象添加到容器中怎麼辦呢?咱們可不能去改動依賴開源庫的代碼。

package com.mengyunzhi.spring;

public class OtherService {

    public void show() {
        System.out.println("我是一個第三方的庫");
    }
}
package com.mengyunzhi.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.mengyunzhi.spring")
public class Application {

    public static void main(String []args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        OtherService otherService = context.getBean(OtherService.class);
        otherService.show();
    }

    @Bean
    OtherService getOtherService() {
        return new OtherService();
    }
}

@Configuration:表示這是一個Spring的配置類,而後就能夠在其中配置相關Bean,經過@Bean註解,會將其標註的方法的返回值對象加入到容器中。

運行結果:

clipboard.png

總結

Spring IOC其實就是一個容器,咱們常說的啓動一個Spring項目其實就是所謂的加載容器(上下文)。

路漫漫其修遠兮,讓咱們一塊兒探索 Spring的本質!
相關文章
相關標籤/搜索