日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

輕松手寫Spring的IOC

作者:Code Icee 更新時間: 2022-07-20 編程語言

我們知道,ioc(控制反轉)和di(依賴注入)是spring里面很重要的東西,那么,我們如何自己手寫出這樣的代碼呢?

什么是IOC

IOC,Inversion of Control,即控制反轉,這個不是一種什么技術,而是一種思想,平時我們寫代碼,都是把對象自己new出來,而現在我們用控制反轉這種思想,把對象的創建交給了程序,那么我們就不需要手動創建對象了,這樣降低了程序的耦合度。
這是spring的核心,貫穿始終。對于spring框架來說,就是由spring來負責控制對象的生命周期和對象間的關系。這是什么意思呢,舉個簡單的例子,我們是如何找女朋友的?常見的情況是,我們到處去看哪里有長得漂亮身材又好的mm,然后打聽她們的興趣愛好、qq號、電話號、wx號,想辦法認識她們,投其所好送其所要,然后嘿嘿……這個過程是復雜深奧的,我們必須自己設計和面對每個環節。傳統的程序開發也是如此,在一個對象中,如果要使用另外的對象,就必須得到它(自己new一個,或者從JNDI中查詢一個),使用完之后還要將對象銷毀(比如Connection等),對象始終會和其他的接口或類藕合起來。
在這里插入圖片描述
在這里插入圖片描述

那么IoC是如何做的呢?有點像通過婚介找女朋友,在我和女朋友之間引入了一個第三者:婚姻介紹所。婚介管理了很多男男女女的資料,我可以向婚介提出一個列表,告訴它我想找個什么樣的女朋友,比如長得像李嘉欣,身材像林熙雷,唱歌像周杰倫,速度像卡洛斯,技術像齊達內之類的,然后婚介就會按照我們的要求,提供一個mm,我們只需要去和她談戀愛、結婚就行了。
在這里插入圖片描述

簡單明了,如果婚介給我們的人選不符合要求,我們就會拋出異常。整個過程不再由我自己控制,而是有婚介這樣一個類似容器的機構來控制。Spring所倡導的開發方式就是如此,所有的類都會在spring容器中登記,告訴spring你是個什么東西,你需要什么東西,然后spring會在系統運行到適當的時候,把你要的東西主動給你,同時也把你交給其他需要你的東西。所有的類的創建、銷毀都由 spring來控制,也就是說控制對象生存周期的不再是引用它的對象,而是spring。對于某個具體的對象而言,以前是它控制其他對象,現在是所有對象都被spring控制,所以這叫控制反轉。

什么是DI

DI—Dependency Injection,即“依賴注入”:組件之間依賴關系由容器在運行期決定,形象的說,即由容器動態的將某個依賴關系注入到組件之中。依賴注入的目的并非為軟件系統帶來更多功能,而是為了提升組件重用的頻率,并為系統搭建一個靈活、可擴展的平臺。通過依賴注入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。

理解DI的關鍵是:“誰依賴誰,為什么需要依賴,誰注入誰,注入了什么”,那我們來深入分析一下:

●誰依賴于誰:當然是應用程序依賴于IoC容器;

●為什么需要依賴:應用程序需要IoC容器來提供對象需要的外部資源;

●誰注入誰:很明顯是IoC容器注入應用程序某個對象,應用程序依賴的對象;

●注入了什么:就是注入某個對象所需要的外部資源(包括對象、資源、常量數據)。

IoC和DI由什么關系呢?其實它們是同一個概念的不同角度描述,由于控制反轉概念比較含糊(可能只是理解為容器控制對象這一個層面,很難讓人想到誰來維護對象關系),所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴注入”,相對IoC 而言,“依賴注入”明確描述了“被注入對象依賴IoC容器配置依賴對象”。

IOC也叫依賴注入(DI)

2004年,Martin Fowler探討了同一個問題,既然IOC是控制反轉,那么到底是“哪些方面的控制被反轉了呢?”,經過詳細地分析和論證后,他得出了答案:“獲得依賴對象的過程被反轉了”。控制被反轉之后,獲得依賴對象的過程由自身管理變為了由IOC容器主動注入。于是,他給“控制反轉”取了一個更合適的名字叫做“依賴注入(Dependency Injection)”。他的這個答案,實際上給出了實現IOC的方法:注入。所謂依賴注入,就是由IOC容器在運行期間,動態地將某種依賴關系注入到對象之中。

實現IOC和DI

首先,我們需要掌握兩項必要的知識

  • 反射
  • 注解

如果對注解和反射沒有怎么了解的,可以看看我之前的寫的博客,java的反射和注解

建立項目

首先,我們先創建基本的項目,并且分好包

  • dao
  • entity
  • service
  • Annoation
  • reflect

在這里插入圖片描述

我們模擬的例子是學生借書,所以,我們先創建好每個包下的java文件

實體類下的User,Book

public class Book {
    private int id;
    private String name;

    public Book(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public Book(){}

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
public class User implements Serializable {
    private int id;
    private String username;
    private String password;
    public User(int id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }
    public User(){}
    public int getId() {
        return id;
    }

    public void setId(int 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;
    }



    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

dao層的UserDao,UserDaoImpl,BookDao,BookDaoImpl

public interface BookDao {
    User findBookById();
    List<User> findAllBook();
    List<User>findBookByBookName(String username);
    void save(Book book);
}

public class BookDaoImpl implements BookDao {
    @Override
    public User findBookById() {
        System.out.println("這里是BookDao-findBookById");
        return null;
    }

    @Override
    public List<User> findAllBook() {
        System.out.println("這里是BookDao-findAllBook");
        return null;
    }

    @Override
    public List<User> findBookByBookName(String username) {
        System.out.println("這里是BookDao-findBookByBookName");
        return null;
    }

    @Override
    public void save(Book book) {
        System.out.println("這里是BookDao-save");

    }
}

public interface UserDao {
    User findUserById();
    List<User>findAllUser();
    List<User>findUserByUserName(String username);
    void save(User user);
}
public class UserDaoImpl implements UserDao {
    @Override
    public User findUserById() {
        System.out.println("這里是UserDao-findUserById");
        return null;
    }

    @Override
    public List<User> findAllUser() {
        System.out.println("這里是UserDao-findAllUser");
        return null;
    }

    @Override
    public List<User> findUserByUserName(String findUserByUserName) {
        System.out.println("這里是UserDao-findUserById");
        return null;
    }

    @Override
    public void save(User user) {
        System.out.println("這里是UserDao-save");
    }
}

service層的UserService,UserserviceImpl,BookService,BookServiceImpl

public interface BookService {
    void  borrow(User user, Book book);
}
public class BookServiceImpl implements BookService {
  
    @Override
    public void borrow(User user,Book book) {
        
    }
}

public interface UserService {
    void  login();
    void regist();
}

public class UserServiceImpl implements UserService {

   private UserDao userDao=new UserDaoImpl();
    @Override
    public void login() {
        userDao.findUserByUserName("劉水龍");
        System.out.println("登錄業務的實現");
    }

    @Override
    public void regist() {
        userDao.save(new User(1,"劉水龍","222"));
        System.out.println("注冊業務的實現");
    }
}

基本思路

通過配置文件把類放入ioc容器

我們知道ioc有一個ioc容器,把全部的對象放入ioc容器之中,這樣我們就不需要自己去手動創建對象了,那么我們是怎么把對象放入ioc容器之中的呢?很簡單,在這里我們使用了Map

我們在refect包下創建ApplicationContext的java文件

public class ApplicationContext<T> {

    private  HashMap<Class,Object> beanFactory=new HashMap<>();
    private static String filePath;
    public T getBean(Class clazz){
        return (T)beanFactory.get(clazz);
    }

}

這里我們創建了一個bean工廠,在ApplicationContext被實例化后,就會去自動讀取配置類文件,把全部的需要放入ioc的容器放入bean工廠,那么如何把配置類文件放入bean工廠呢?我們需要新建議一個類

我們在ApplicationContext下寫一個initContext方法,同時創建一個和com文件同級的config文件夾,寫好配置文件

在這里插入圖片描述

com.znb.service.BookService=com.znb.service.BookServiceImpl
com.znb.dao.BookDao=com.znb.dao.BookDaoImpl

 public void initContext() throws Exception {
        InputStream resource = ApplicationContext.class.getClassLoader().getResourceAsStream("config/bean.config");
        Properties properties=new Properties();
        properties.load(resource);
        Set<Object> keys=properties.keySet();
        System.out.println(properties);
        System.out.println(keys);
        for (Object key : keys) {
//             System.out.println(key.toString());
            beanFactory.put(Class.forName(key.toString()), Class.forName(properties.getProperty(key.toString())).newInstance());
        }
        System.out.println(beanFactory.keySet());
         System.out.println(beanFactory);

    }

在這里我們通過properties類獲取配置文件,然后把properties的文件的key放進set集合中,
然后通過反射,將全部的類放進bean工廠,之后需要的時候,就可以直接從bean工廠獲取

現在,我們測試一下,寫一個BootStrap類

public class BookStrap {
    public static void main(String[] args) throws Exception {
        ApplicationContext applicationContext=new ApplicationContext();
        applicationContext.initContext();

    }
}

在這里插入圖片描述
可以看到,當我們需要的時候,直接從bean工廠里面拿就可以了,這就是通過配置文件把類放入ioc容器

通過注解把類放入ioc容器

我們通過配置文件把類放入ioc容器,但是這樣似乎很麻煩,每次需要放入ioc容器的時候,我們都需要自己去寫一次配置文件,那么我們也沒有什么方法簡化這個步驟呢?

新建注解

我們在Annotation文件下新建一個注解 Bean

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}

在ApplicationContext下寫兩個方法,一個initContextByAnnotation,一個loadOne

 public void initContextByAnnotation() throws Exception {
       //掃描包
        filePath = ApplicationContext.class.getClassLoader().getResource("").getFile();
        loadOne(new File(filePath));

    }
private  void loadOne(File fileParent) {
        if (fileParent.isDirectory()) {
            File[] childrenFiles = fileParent.listFiles();
            if(childrenFiles == null || childrenFiles.length == 0){
                return;
            }
            for (File child : childrenFiles) {
                if (child.isDirectory()) {
                    //如果是個文件夾就繼續調用該方法,使用了遞歸
                    loadOne(child);
                } else {
                    //通過文件路徑轉變成全類名,第一步把絕對路徑部分去掉

                    String pathWithClass = child.getAbsolutePath().substring(filePath.length() - 1);
                    //選中class文件
                    if (pathWithClass.contains(".class")) {
                        //    com.xinzhi.dao.UserDao
                        //去掉.class后綴,并且把 \ 替換成 .
                        String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
                        try {
                            Class<?> aClass = Class.forName(fullName);


                            //把非接口的類實例化放在map中
                            if(!aClass.isInterface()){
                                Bean annotation = aClass.getAnnotation(Bean.class);
                                if(annotation != null){
                                    Object instance = aClass.newInstance();
                                    //判斷一下有沒有接口
                                    if(aClass.getInterfaces().length > 0) {
                                        //如果有接口把接口的class當成key,實例對象當成value
                                        System.out.println("正在加載【"+ aClass.getInterfaces()[0] +"】,實例對象是:" + instance.getClass().getName());
                                        beanFactory.put(aClass.getInterfaces()[0], instance);
                                    }else{
                                        //如果有接口把自己的class當成key,實例對象當成value
                                        System.out.println("正在加載【"+ aClass.getName() +"】,實例對象是:" + instance.getClass().getName());
                                        beanFactory.put(aClass, instance);
                                    }
                                }
                            }
                        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

然后,我們給全部的接口實現類寫上@Bean注解

@Bean
public class BookDaoImpl implements BookDao {
    @Override
    public User findBookById() {
        System.out.println("這里是BookDao-findBookById");
        return null;
    }

    @Override
    public List<User> findAllBook() {
        System.out.println("這里是BookDao-findAllBook");
        return null;
    }

    @Override
    public List<User> findBookByBookName(String username) {
        System.out.println("這里是BookDao-findBookByBookName");
        return null;
    }

    @Override
    public void save(Book book) {
        System.out.println("這里是BookDao-save");

    }
}

都按照這個格式寫,其余的我就省略了,然后我們測試一下

 public static void main(String[] args) throws Exception {
        ApplicationContext applicationContext=new ApplicationContext();
        applicationContext.initContextByAnnotation();
    }

結果:
在這里插入圖片描述
這個時候,寫了@Bean的類都全部進入了IOC容器,當我們需要,只需要自己調用就可以了

依賴注入

我們已經簡單的實現了IOC,那么現在來實現一下依賴注入,我們在ApplicationContext里面新寫一個assembleObject方法,然后在initContextByAnnotation調用

 private void assembleObject() {
        for(Map.Entry<Class,Object> entry : beanFactory.entrySet()){
            //就是咱們放在容器的對象
            Object obj = entry.getValue();
            Class<?> aClass = obj.getClass();
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field field : declaredFields){
                AutoWired annotation = field.getAnnotation(AutoWired.class);
                if( annotation != null ){
                    field.setAccessible(true);
                    try {
                        System.out.println("正在給【"+obj.getClass().getName()+"】屬性【" + field.getName() + "】注入值【"+ beanFactory.get(field.getType()).getClass().getName() +"】");
                        field.set(obj,beanFactory.get(field.getType()));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

 public void initContextByAnnotation() throws Exception {
       //掃描包
        filePath = ApplicationContext.class.getClassLoader().getResource("").getFile();
        loadOne(new File(filePath));
        assembleObject();
    }

然后我們在去接口實現類上寫上自己需要的接口,以及注釋

public class BookServiceImpl implements BookService {
    @AutoWired
    private UserDao userDao;
    @AutoWired
    private BookDao bookDao;
    @Override
    public void borrow(User user,Book book) {
        userDao.save(user);
        System.out.println(user.getUsername()+"借了"+book.getName());
    }
}

測試一下

public class BookStrap {
    public static void main(String[] args) throws Exception {
        ApplicationContext applicationContext=new ApplicationContext();
//        applicationContext.initContext();
//        System.out.println("======================");
//         System.out.println(applicationContext.getBean(BookDao.class));

        applicationContext.initContextByAnnotation();
        BookService bean = (BookService)applicationContext.getBean(BookService.class);
        bean.borrow(new User(1,"劉水龍","00"),new Book(1,"黃書"));
    }
}

結果:
在這里插入圖片描述

總結:

在我們寫ssm項目,或者springboot項目的時候,我們都會要用到ioc和di,因為這樣可以降低系統的耦合性,以后修改迭代起來也很方便,但是由于ioc和di采用了大量的反射,所以性能消耗很大,而且由于ioc容器注入了大量的對象,所以啟動起來會比較的慢。
整體的思想,是通過反射,來將對象注入ioc的容器里面,然后直接把接口的實現類創建,這樣就不需要自己new對象,就可以需要什么就拿什么了。

原文鏈接:https://blog.csdn.net/qq_45191024/article/details/125881393

欄目分類
最近更新