SpringのIOCコンテナは最初から手書きのソースコードを持っている

記事ディレクトリ

スプリングコア(コアコンテナ)

ここに画像の説明を挿入
Spring コアは、IOC、DI、および Bean 設定ロード作成のコア実装を提供します。 コア
概念: Bean、BeanFactory、BeanDefinitions、ApplicationContext

  • spring-core: IOC と DI の基本的な実装
  • spring-beans: BeanFactory および Bean アセンブリ管理 (BeanFactory)
  • spring-context: Spring コンテキスト コンテキスト、つまり IOC コンテナ (AppliactionContext)
  • spring-expression: 春の式言語

1.IOCコンテナ

1. 制御の反転 (ioc)

  • 制御の反転とは、思想
  • 制御の反転は、プログラムの結合を減らし、プログラムのスケーラビリティを向上させることを目的としています。
  • 制御の反転、反転とは何ですか?
    • 对象的创建第三者コンテナに権利を譲渡し、責任を譲渡する
    • 对象和对象之间关系メンテナンス権限をサードパーティのコンテナに引き渡す
  • 制御の反転のアイデアをどのように実現するか?
    • DI (Dependency Injection): 依存性注入

2. 依存性の注入

DI(Dependency Injection):依存性注入、依存性注入は制御の逆転の考え方を実現する

  • 依赖属性Spring でオブジェクトを作成し、構成を通じてオブジェクトを構成するプロセスを指します。注入
  • 依存関係注入には 2 つの一般的な実装があります。
    • セットインジェクション
    • コンストラクターインジェクション
  • Bean 管理とは、Bean オブジェクト创建、および Bean オブジェクト属性的赋值(または Bean オブジェクト間の関係の維持)を指します。

結論としては、IOC は制御の逆転のアイデアであり、DI は IoC を具体的に実現したものです。

3. SpringでのIoCコンテナの実装

  • Spring の IoC コンテナは、IoC のアイデアを製品実装したものです。
  • IoCコンテナで管理されるコンポーネントはBeanとも呼ばれます
  • Bean を作成する前に、まず IoC コンテナを作成する必要があります
  • Spring は、IoC コンテナの 2 つの実装を提供します。
    • ビーンファクトリー
      • これは IoC コンテナの基本实现
      • Spring が内部的に使用するインターフェイスです
      • 開発者が使用するためではなく、Spring 自体用
    • アプリケーションコンテキスト
      • BeanFactory 的子接口,提供了更多高级特性
      • Springユーザー向け
      • ほとんどの場合、基になる BeanFactory の代わりに ApplicationContext が使用されます。

ApplicationContextの主な実装クラス

ここに画像の説明を挿入

型名 序章
ClassPathXmlApplicationContext クラスパスの下にある XML 形式の構成ファイルを読み取って、IOC コンテナ オブジェクトを作成します。
FileSystemXmlApplicationContext ファイル システム パスを介して XML 形式の構成ファイルを読み取ることにより、IOC コンテナ オブジェクトを作成します。
構成可能なアプリケーションコンテキスト ApplicationContext のサブインターフェイスには、いくつかの拡張メソッド Refresh() および close() が含まれており、これにより、ApplicationContext がコンテキストを開始、閉じ、更新できるようになります。
Webアプリケーションコンテキスト Web アプリケーション用に特別に準備され、Web 環境に基づいて IOC コンテナ オブジェクトを作成し、そのオブジェクトを ServletContext ドメインにインポートして保存します。

2、XML 管理 Bean に基づく

プロジェクトを構築する

  • 依存関係を追加する
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.3</version>
</dependency>
  • Javaクラスをインポートする
public class User {
    
    
}
  • 豆.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1 获取bean演示,user对象创建-->
    <bean id="user" class="com.xc.spring6.iocxml.bean.User"></bean>
</beans>

1.豆を入手する

方法1.idによる取得

public class TestUser {
    
    
    public static void main(String[] args) {
    
    
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        // 根据id获取bean
        User user = (User) context.getBean("user");
    }
}

方法2. 種類ごとに取得する

public class TestUser {
    
    
    public static void main(String[] args) {
    
    
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        User user = context.getBean(User.class);
    }
}

方法 3. ID とタイプに従って Bean を取得する

public class TestUser {
    
    
    public static void main(String[] args) {
    
    
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        User user = context.getBean("user",User.class);
    }
}

知らせ

  • タイプ別に Bean を取得する場合、指定类型IOC コンテナ内の Bean には 1 つだけ必要があります。
  • IOCコンテナに合計2つ設定した場合
<bean id="user" class="com.xc.spring6.iocxml.bean.User"></bean>
<bean id="user1" class="com.xc.spring6.iocxml.bean.User"></bean>

型に応じて取得した場合は例外がスローされます:NoUniqueBeanDefinitionException(独自Bean定義例外なし)

拡大

  • コンポーネントクラスがインターフェースを実装していれば根据接口类型Beanを取得できるのでしょうか?( 可以、Bean が一意である場合)
  • インターフェースがある場合多个实现类、これらの実装クラスはBeanで構成されますが、インターフェースの種類に応じてBeanを取得できますか? ( 不行、Bean は一意ではないため)

2. 依存性注入のセッター注入

  • 学生クラスを作成する
@Data
public class Student {
    
    
    private Integer id;
    private String name;
    private Integer age;
    private String sex;
}
  • Bean を構成するときにプロパティに値を割り当てる
<bean id="studentOne" class="com.xc.spring6.bean.Student">
    <!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
    <!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) -->
    <!-- value属性:指定属性值 -->
    <property name="id" value="1001"></property>
    <property name="name" value="张三"></property>
    <property name="age" value="23"></property>
    <property name="sex" value=""></property>
</bean>
  • テスト
@Test
public void testDIBySet(){
    
    
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml");
    Student studentOne = ac.getBean("studentOne", Student.class);
    System.out.println(studentOne);
}

3. 依存性注入のコンストラクター注入

  • Student クラスにパラメータ化された構造を追加する
public Student(Integer id, String name, Integer age, String sex) {
    
    
    this.id = id;
    this.name = name;
    this.age = age;
    this.sex = sex;
}
  • 構成 Bean
<bean id="studentTwo" class="com.xc.spring6.bean.Student">
    <constructor-arg value="1002"></constructor-arg>
    <constructor-arg value="李四"></constructor-arg>
    <constructor-arg value="33"></constructor-arg>
    <constructor-arg value="女"></constructor-arg>
</bean>

注:
constructor-arg タグには、コンストラクターのパラメーターを詳しく説明する 2 つの属性もあります。

  • Index 属性: パラメーターが配置されているインデックスを指定します (0 から始まります)。
  • name 属性: パラメータ名を指定します
  • テスト
@Test
public void testDIByConstructor(){
    
    
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml");
    Student studentOne = ac.getBean("studentTwo", Student.class);
    System.out.println(studentOne);
}

4. 特殊な値の処理

リテラル代入

<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="name" value="张三"/>

NULL値

<property name="name">
    <null />
</property>

XMLエンティティ

<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a &lt; b"/>

CDATA セクション

<property name="expression">
    <!-- 解决方案二:使用CDATA节 -->
    <!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
    <!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
    <!-- 所以CDATA节中写什么符号都随意 -->
    <value><![CDATA[a < b]]></value>
</property>

5. オブジェクトタイプのプロパティに値を割り当てる

  • 従業員と部門
// 部门累
@Data
public class Dept {
    
    
    private String dname;
}
// 员工类
@Data
public class Emp {
    
    
    //对象类型属性:员工属于某个部门
    private Dept dept;
    //员工名称
    private String ename;
    //员工年龄
    private Integer age;
}

方法 1: 外部 Bean を参照する

<bean id="dept" class="com.xc.spring6.iocxml.ditest.Dept">
    <property name="dname" value="安保部"></property>
</bean>
  • ref属性Emp クラスの部門属性割り当てオブジェクトを使用する
<bean id="emp" class="com.xc.spring6.iocxml.ditest.Emp">
    <!--注入对象类型属性
        private Dept dept;
    -->
    <property name="dept" ref="dept"></property>
    <!--普通属性注入-->
    <property name="ename" value="lucy"></property>
    <property name="age" value="50"></property>
</bean>

方法 2: 内部 Bean

<bean id="emp2" class="com.xc.spring6.iocxml.ditest.Emp">
    <!--普通属性注入-->
    <property name="ename" value="mary"></property>
    <property name="age" value="20"></property>
    <!--内部bean-->
    <property name="dept">
        <bean id="dept2" class="com.xc.spring6.iocxml.ditest.Dept">
            <property name="dname" value="财务部"></property>
        </bean>
    </property>
</bean>

方法 3: カスケード属性の割り当て

  • 名前属性 = オブジェクト.属性名
<bean id="dept3" class="com.xc.spring6.iocxml.ditest.Dept">
    <property name="dname" value="技术研发部"></property>
</bean>

<bean id="emp3" class="com.xc.spring6.iocxml.ditest.Emp">
    <property name="ename" value="tom"></property>
    <property name="age" value="30"></property>
    
    <property name="dept" ref="dept3"></property>
    <property name="dept.dname" value="测试部"></property>
</bean>

6. 配列型プロパティに値を代入する

  • 趣味属性の配列を Emp 従業員クラスに追加します
//爱好
private String[] loves;
<bean id="emp" class="com.xc.spring6.iocxml.ditest.Emp">
    <!--普通属性-->
    <property name="ename" value="lucy"></property>
    <property name="age" value="20"></property>
    <!--对象类型属性-->
    <property name="dept" ref="dept"></property>
    <!--数组类型属性-->
    <property name="loves">
        <array>
            <value>吃饭</value>
            <value>睡觉</value>
            <value>敲代码</value>
        </array>
    </property>
</bean>

7. コレクション型プロパティに値を割り当てる

  • 部門クラスが従業員コレクションを追加します
//一个部门有很多员工
private List<Emp> empList;
<bean id="empone" class="com.xc.spring6.iocxml.ditest.Emp">
    <property name="ename" value="lucy"></property>
    <property name="age" value="20"></property>
</bean>
<bean id="emptwo" class="com.xc.spring6.iocxml.ditest.Emp">
    <property name="ename" value="mary"></property>
    <property name="age" value="30"></property>
</bean>

<bean id="dept" class="com.xc.spring6.iocxml.ditest.Dept">
  <property name="dname" value="技术部"></property>
  <property name="empList">
        <list>
            <ref bean="empone"></ref>
            <ref bean="emptwo"></ref>
        </list>
  </property>
</bean>

Set コレクション タイプ属性に値を割り当てる場合は、その中のリスト タグを次set标签のように変更するだけで済みます。

8. XMLに基づいた自動アセンブリ

  • 指定されたポリシーに従って IOC コンテナ内の Bean を照合します。
  • 指定された Bean が依存するクラス タイプまたはインターフェイス タイプのプロパティに値を自動的に割り当てます。
  • XML自動アセンブリに基づいて、最下層はセットインジェクションを使用します

Bean クラス

public class UserServiceImpl  implements UserService{
    
    

    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
    
    
        this.userDao = userDao;
    }

    @Override
    public void addUserService() {
    
    
        userDao.addUserDao();
    }
}

bean.xml を設定する

  • 自動組み立て方法:byType
  • 类型匹配byType: IOC コンテナ内の互換性のあるタイプの Beanに従って、プロパティに値が自動的に割り当てられます。
    • IOC に、プロパティに値を割り当てることができる互換性のあるタイプの Bean がない場合、プロパティはアセンブルされません。つまり、デフォルト値は null です。
    • IOC に、プロパティに値を割り当てることができる互換性のあるタイプの Bean が複数ある場合、例外 NoUniqueBeanDefinitionException がスローされます。
<bean id="userService" class="com.xc.spring6.autowire.service.impl.UserServiceImpl" 
autowire="byType"></bean>

<bean id="userDao" class="com.xc.spring6.autowire.dao.impl.UserDaoImpl"></bean>
  • 自動アセンブリ方法: byName
  • 属性名byName: IOC コンテナ内の対応する Bean と一致するように、自動アセンブリ属性を Bean ID として割り当てます。

3. アノテーションに基づいて Bean を管理する ( )

1. 構成クラスは bean.xml を置き換えます。

@Configuration
//@ComponentScan({"com.xc.spring6.controller", "com.xc.spring6.service","com.xc.spring6.dao"})
@ComponentScan("com.xc.spring6")
public class Spring6Config {
    
    
}

2. @Autowired インジェクション

  • @Autowired アノテーションを単独で使用します。默认根据类型装配
  • 注釈のソースコードを表示する
@Target({
    
    ElementType.CONSTRUCTOR, ElementType.METHOD, 
ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    
    
    boolean required() default true;
}
  • 注釈で位置をマークできます
    • 施工方法について
    • 方法
    • 形参上
    • 属性
    • 注意事項
  • このアノテーションには必須の属性があります
    • デフォルト値は true で、注入された Bean が注入中に存在する必要があり、存在しない場合はエラーが報告されることを示します。
    • required 属性が false に設定されている場合、注入された Bean が存在するかどうかは関係ありません。

方法 1: 属性の挿入 ( 最常用)

  • アノテーションベースの自動アセンブリ、最下層はリフレクションインジェクションを使用するため、setメソッドは必要ありません
@Service
public class UserServiceImpl implements UserService {
    
    

    @Autowired
    private UserDao userDao;
}

方法2、セットインジェクション

@Service
public class UserServiceImpl implements UserService {
    
    

    private UserDao userDao;

    @Autowired
    public void setUserDao(UserDao userDao) {
    
    
        this.userDao = userDao;
    }
}

メソッド 3、コンストラクター インジェクション

@Service
public class UserServiceImpl implements UserService {
    
    

    private UserDao userDao;

    @Autowired
    public UserServiceImpl(UserDao userDao) {
    
    
        this.userDao = userDao;
    }
}

方法 4. 仮パラメータの注入

@Service
public class UserServiceImpl implements UserService {
    
    

    private UserDao userDao;

    public UserServiceImpl(@Autowired UserDao userDao) {
    
    
        this.userDao = userDao;
    }
}

方法 5. コンストラクターは 1 つだけであり、コンストラクターを介した注入にはアノテーションは必要ありません

  • パラメーターを持つコンストラクターが 1 つだけの場合、 @Autowired アノテーションは省略できます。
@Service
public class UserServiceImpl implements UserService {
    
    

    private UserDao userDao;

    public UserServiceImpl(UserDao userDao) {
    
    
        this.userDao = userDao;
    }
}

方法 6、@Autowired アノテーションと @Qualifier アノテーションの組み合わせ

  • UserDao インターフェースに两个实现类
  • @Autowired 経由で注入すると例外がスローされます (単一の一致する Bean が予期されましたが、2 つ見つかりました: xx1、xx2)
  • この時点で、@Qualifier アノテーションを使用して特定の Bean の名前を指定できます。
@Service
public class UserServiceImpl implements UserService {
    
    

    @Autowired
    @Qualifier("userDaoImpl") // 指定bean的名字
    private UserDao userDao;

}

3. @リソース注入

@Resource アノテーションによってプロパティの注入を完了することもできます。では、これと @Autowired アノテーションの違いは何でしょうか?

  • @Resource アノテーションは JDK 拡張パッケージに含まれており、これは JDK の一部であることを意味します。
    • したがって、このアノテーションは標準的なアノテーションであり、より多用途です。
    • JSR-250 標準で指定されたアノテーション タイプ。JSR は Java 仕様提案です
  • @Autowired アノテーションは Spring フレームワーク独自のものです
  • @Resource アノテーションは、デフォルトでは byName によって名前によってアセンブルされます。名前によって見つからない場合は、自動的にタイプ byType によってアセンブルが開始されます。
  • @Autowired アノテーションはデフォルトでタイプごとにアセンブルされますが、名前でアセンブルしたい場合は @Qualifier アノテーションと併用する必要があります。
  • @Resource アノテーションはプロパティとセッター メソッドで使用されます
  • @Autowired アノテーションは、プロパティ、セッター メソッド、構築メソッド、および構築メソッドのパラメータで使用されます。

@Resource アノテーションは JDK 拡張パッケージに属するため、JDK には含まれておらず、以下の依存関係を追加で導入する必要がありますが、
JDK8 の場合は追加の依存関係を導入する必要はありません。JDK11 以降または JDK8 未満では、次の依存関係を導入する必要があります。

<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
</dependency>

方法 1. 名前に従って注入する

@Service
public class UserServiceImpl implements UserService {
    
    

    @Resource(name = "userDao")
    private UserDao myUserDao;

}

方法 2: 不明な名前の挿入

  • 名前を指定せずに @Resource アノテーションが使用された場合でも、属性名である名前に従って検索されます。
@Service
public class UserServiceImpl implements UserService {
    
    

    @Resource
    private UserDao userDao;
}

方法3、名前が見つからない場合

  • 明らかに、名前が見つからない場合は、注入のために byType で自然に開始されます。
@Service
public class UserServiceImpl implements UserService {
    
    

    @Resource
    private UserDao userDaoNotFound ;

}

4. 原則 - 手書き IoC

マークされた Bean の @Bean アノテーションと依存関係注入の @Di アノテーションを定義します。

  • @Bean は @Component と同等です
  • @Di は @Autowired と同等です
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
    
    
}

@Target({
    
    ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
    
    
}

Beanコンテナインターフェースを定義する

public interface ApplicationContext {
    
    
    Object getBean(Class<?> clazz);
}

Bean コンテナ インターフェイスと依存関係注入の実装に注釈を付ける

public class AnnotationApplicationContext implements ApplicationContext {
    
    
    //创建map集合,放bean对象
    private final Map<Class<?>, Object> beanFactory = new HashMap<>();
    private static String rootPath;

    //返回对象
    @Override
    public Object getBean(Class<?> clazz) {
    
    
        return beanFactory.get(clazz);
    }

    //创建有参数构造,传递包路径,设置包扫描规则
    //当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化
    public AnnotationApplicationContext(String basePackage) {
    
    
        // com.xc
        try {
    
    
            //1 把.替换成\
            String packagePath = basePackage.replaceAll("\\.", "/");
            //2 获取包绝对路径
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            while (urls.hasMoreElements()) {
    
    
                URL url = urls.nextElement();
                String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
                //获取包前面路径部分,字符串截取
                rootPath = filePath.substring(0, filePath.length() - packagePath.length());
                //包扫描
                loadBean(new File(filePath));
            }
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
        //属性注入
        loadDi();
    }

    //包扫描过程,实例化
    private void loadBean(File file) throws Exception {
    
    
        //1 判断当前是否文件夹
        if (file.isDirectory()) {
    
    
            //2 获取文件夹里面所有内容
            File[] childrenFiles = file.listFiles();
            //3 判断文件夹里面为空,直接返回
            if (childrenFiles == null || childrenFiles.length == 0) {
    
    
                return;
            }
            //4 如果文件夹里面不为空,遍历文件夹所有内容
            for (File child : childrenFiles) {
    
    
                //4.1 遍历得到每个File对象,继续判断,如果还是文件夹,递归
                if (child.isDirectory()) {
    
    
                    //递归
                    loadBean(child);
                } else {
    
    
                    //4.2 遍历得到File对象不是文件夹,是文件,
                    //4.3 得到包路径+类名称部分-字符串截取
                    String pathWithClass = child.getAbsolutePath().substring(rootPath.length());
                    //4.4 判断当前文件类型是否.class
                    if (pathWithClass.contains(".class")) {
    
    
                        //4.5 如果是.class类型,把路径\替换成.  把.class去掉
                        // com.xc.service.UserServiceImpl
                        String allName = pathWithClass.replaceAll("/", ".").replace(".class", "");
                        //4.6 判断类上面是否有注解 @Bean,如果有实例化过程
                        //4.6.1 获取类的Class
                        Class<?> clazz = Class.forName(allName);
                        //4.6.2 判断不是接口
                        if (!clazz.isInterface()) {
    
    
                            //4.6.3 判断类上面是否有注解 @Bean
                            if (clazz.isAnnotationPresent(Bean.class)) {
    
    
                                //4.6.4 实例化
                                Object instance = clazz.getConstructor().newInstance();
                                //4.7 把对象实例化之后,放到map集合beanFactory
                                //4.7.1 判断当前类如果有接口,让接口class作为map的key
                                if (clazz.getInterfaces().length > 0) {
    
    
                                    beanFactory.put(clazz.getInterfaces()[0], instance);
                                } else {
    
    
                                    beanFactory.put(clazz, instance);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    //属性注入
    private void loadDi() {
    
    
        //实例化对象在beanFactory的map集合里面
        //1 遍历beanFactory的map集合
        Set<Map.Entry<Class<?>, Object>> entries = beanFactory.entrySet();
        for (Map.Entry<Class<?>, Object> entry : entries) {
    
    
            //2 获取map集合每个对象(value),每个对象属性获取到
            Object obj = entry.getValue();
            //获取对象Class
            Class<?> clazz = obj.getClass();
            //获取每个对象属性获取到
            Field[] declaredFields = clazz.getDeclaredFields();
            //3 遍历得到每个对象属性数组,得到每个属性
            for (Field field : declaredFields) {
    
    
                //4 判断属性上面是否有@Di注解
                if (field.isAnnotationPresent(Di.class)) {
    
    
                    //如果私有属性,设置可以设置值
                    field.setAccessible(true);
                    //5 如果有@Di注解,把对象进行设置(注入)
                    try {
    
    
                        field.set(obj, beanFactory.get(field.getType()));
                    } catch (IllegalAccessException e) {
    
    
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}

テストクラス

@Bean
public class UserDaoImpl  implements UserDao {
    
    
    @Override
    public void add() {
    
    
        System.out.println("dao.......");
    }
}
@Bean
public class UserServiceImpl implements UserService {
    
    

    @Di
    private UserDao userDao;

    @Override
    public void out() {
    
    
        userDao.print();
        System.out.println("Service层执行结束");
    }
}
public class SpringIocTest {
    
    
    @Test
    public void testIoc() {
    
    
        ApplicationContext applicationContext = new AnnotationApplicationContext("com.xc");
        UserService userService = (UserService)applicationContext.getBean(UserService.class);
        userService.add();
        System.out.println("run success");
    }
}

出力結果

service.......
dao.......
run success

おすすめ

転載: blog.csdn.net/qq_35512802/article/details/131272763