spring下 -spring整體架構,JdbcTemplate筆記

银河小船儿發表於2024-10-29

2,搭建Java Maven專案

我的idea是2024.1.1版本,建立普通Maven專案如下圖:

用的jdk8,專案名可以自己改,Archetype選圖中的第一個就行,之後點 create。

建立後空的Maven專案的程式碼結構就是下圖

再修改 pom.xml檔案

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>hsp-spring</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>Archetype - hsp-spring</name>
  <url>http://maven.apache.org</url>

  <dependencies>
    <!--加入spring開發的基本包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.8</version>
    </dependency>
    <!--加入spring開發切面程式設計需要的包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.3.8</version>
    </dependency>
  </dependencies>
</project>

重新整理後,點選最右邊的 藍色m 圖示,再點 Dependencies,就能看到出現的對應 5.3.8 版本的jar包。

測試依賴注入的程式碼結構:

UserAction.java

package com.hspedu.spring.component;

import org.springframework.stereotype.Component;

/**
 * 就是一個Controller
 */
//也可以使用@Controller
//在預設情況下 我們配置@Component @Controller @Service @Repository 是單例
//@Scope(value = "prototype") 表示以多例項形式,返回UserAction bean
@Component
public class UserAction {
}

UserDao.java

package com.hspedu.spring.component;

import org.springframework.stereotype.Component;

//可以使用@Repository
@Component
public class UserDao {
    public void hi() {
        System.out.println("UserDao-hi()---");
    }
}

UserService.java

package com.hspedu.spring.component;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

//也可以使用@Service
@Component
public class UserService {

    @Autowired
    //也可以使用@Resource
    private UserDao userDao;

    public void m1() {
        userDao.hi();
    }
}

beans.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--配置自動掃描的包, 同時引入對應的名稱空間-->
    <!--老師說明:
    1. 如果我們是普通的java專案, beans.xml 放在src下
    2. 如果我們是java maven 專案, beans.xml 放在 src/main/resources
    -->
    <context:component-scan base-package="com.hspedu.spring.component"/>

    <!--啟用基於註解方式的AOP功能-->
    <aop:aspectj-autoproxy/>
</beans>

AppMain.java

package com.hspedu.spring;

import com.hspedu.spring.component.UserAction;
import com.hspedu.spring.component.UserDao;
import com.hspedu.spring.component.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AppMain {
    public static void main(String[] args) {
        //測試看看是否可以得到spring容器中的bean , 同時看看依賴注入是否OK
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");
        UserAction userAction = (UserAction) ioc.getBean("userAction");
        UserAction userAction2 = (UserAction) ioc.getBean("userAction");

        System.out.println("userAction=" + userAction);
        System.out.println("userAction2=" + userAction2);

        UserDao userDao = (UserDao) ioc.getBean("userDao");
        System.out.println("userDao=" + userDao);

        UserService userService = (UserService) ioc.getBean("userService");
        System.out.println("userService=" + userService);

        //測試一下當前的依賴注入
        userService.m1();
    }
}

執行結果:

12,編寫自己的Spring容器,掃描包得到bean的class物件

spring整體架構

本節分析示意圖

類載入器

建立模組

注意路徑是同級目錄

再改 ProjectStructure 和 Settings, 如下圖所示

本節程式碼結構:

annotation包

ComponentScan.java
package com.hspedu.spring.annotation;

//自己要寫的註解,之前寫過,所以直接拿來用

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 1. @Target(ElementType.TYPE)指定我們的ComponentScan註解可以修飾 Type程式元素
 * 2. @Retention(RetentionPolicy.RUNTIME) 指定ComponentScan註解 保留範圍
 * 3. String value() default ""; 表示ComponentScan 可以傳入 value
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
    //透過value可以指定要掃描的包
    String value() default "";
}

Component.java
package com.hspedu.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//再定義一個註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    //透過value可以給注入的bean/物件指定名字
    String value() default "";
}

Component包

MonsterService.java
package com.hspedu.spring.component;

import com.hspedu.spring.annotation.Component;

@Component("monsterDao")
public class MonsterDao {
}

MonsterDao.java
package com.hspedu.spring.component;

//引入自己定義的註解
import com.hspedu.spring.annotation.Component;

/**
 * 說明MonsterService 是一個Service
 * 1. 如果指定了value,那麼在注入spring容器時,以你指定為準
 * 2. 如果沒有指定value ,則使用類名首字母小寫名字
 */
@Component//(value = "monsterService") //把MonsterService注入我們自己的spring容器中
//@Scope(value = "prototype")
public class MonsterService {
}

Car.java
package com.hspedu.spring.component;

public class Car {
}

ioc包

HspSpringConfig.java
package com.hspedu.spring.ioc;

//之前寫過 基於註解的spring容器

import com.hspedu.spring.annotation.ComponentScan;

/**
 * 這是一個配置類, 作用類似我們原生spring的 beans.xml 容器配置檔案,value指定了要掃描的包
 */
@ComponentScan(value = "com.hspedu.spring.component")
public class HspSpringConfig {
}

HspSpringApplicationContext.java
package com.hspedu.spring.ioc;



import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.annotation.ComponentScan;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * HspSpringApplicationContext 類的作用類似Spring原生ioc容器
 */

public class HspSpringApplicationContext {
    private Class configClass;

    //構造器
    public HspSpringApplicationContext(Class configClass) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        this.configClass = configClass;
        //獲取要掃描的包
        //1. 先得到HspSpringConfig配置的@ComponentScan(value = "com.hspedu.spring.component")
        ComponentScan componentScan =
                (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2. 透過componentScan的value=> 即要掃描的包
        String path = componentScan.value();
        System.out.println("要掃描的包= " + path);

        //得到要掃描的包下的所有資源(類 .class)
        //1.得到類的載入器->APP類載入器
        ClassLoader classLoader =
                HspSpringApplicationContext.class.getClassLoader();
        //2. 透過類的載入器獲取到要掃描的包的資源 url=》類似一個路徑
        //一定要把. 替換成 /
        path = path.replace(".","/");
        URL resource =
                classLoader.getResource("com/hspedu/spring/component");
        System.out.println("resource=" + resource);

        //3. 將要載入的資源(.class) 路徑下的檔案進行遍歷=>io
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                String fileAbsolutePath = f.getAbsolutePath();
                //這裡我們只處理.class檔案
                if (fileAbsolutePath.endsWith(".class")) {
                    //1. 獲取到類名
                    String className =
                            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    //2. 獲取類的完整的路徑(全類名)
                    //老師解讀 path.replace("/",".") => com.hspedu.spring.component.
                    String classFullName = path.replace("/", ".") + "." + className;

                    //3. 判斷該類是不是需要注入容器, 就看該類是不是有註解 @Component @Service..
                    Class<?> clazz = classLoader.loadClass(classFullName);
                    if (clazz.isAnnotationPresent(Component.class)) {
                        //如果該類使用了@Component,說明是Spring bean
                        System.out.println("是一個Spring bean =" + clazz + " 類名=" + className);
                    } else {
                    //如果該類沒有使用了@Component,說明不是Spring bean
                        System.out.println("不是一個Spring bean =" + clazz + " 類名=" + className);
                    }
                }
            }
            System.out.println("------------------------------------");
        }
    }

    //編寫方法返回對容器中物件
    public Object getBean(String name) {
        return null;
    }
}

AppMain.java

package com.hspedu.spring;

import com.hspedu.spring.ioc.HspSpringApplicationContext;
import com.hspedu.spring.ioc.HspSpringConfig;

public class AppMain {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //把 配置檔案class物件傳進去後,然後再去容器中掃描包,容器中有注入的userService,userDao的bean物件
        HspSpringApplicationContext hspSpringApplicationContext =
                new HspSpringApplicationContext(HspSpringConfig.class);
    }
}

執行結果:

15,掃描bean資訊封裝BeanDefinition,放入Map

分析示意圖

BeanDefinition物件裡有scope和class屬性,再把key=beanName,value=BeanDefinition物件,放進BeanDefinitionMap裡去。

在 pom.xml配置要用到的jar包

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>hsp-myspring</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>Archetype - hsp-myspring</name>
  <url>http://maven.apache.org</url>

  <dependencies>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.6</version>
    </dependency>
  </dependencies>
</project>

有如下圖所示的包就行

程式碼結構:

annotation包

這個包只新增了Scope.java,其他java檔案不變

Scope.java
package com.hspedu.spring.annotation;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Scope 可以指定Bean的作用範圍[singleton, prototype]
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
    //透過value可以指定singleton,prototype
    String value() default "";
}

component包

這個包 MonsterService.java 改變了,其他java檔案不變

MonsterService.java
package com.hspedu.spring.component;

//引入自己定義的註解
import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.annotation.Scope;

/**
 * 說明MonsterService 是一個Service
 * 1. 如果指定了value,那麼在注入spring容器時,以你指定為準
 * 2. 如果沒有指定value ,則使用類名首字母小寫名字
 */
@Component//(value = "monsterService") //把MonsterService注入我們自己的spring容器中
@Scope(value = "prototype")
public class MonsterService {
}

ioc包

這個包 只有HspSpringConfig.java不變

BeanDefinition.java
package com.hspedu.spring.ioc;

/**
 * BeanDefinition 用於封裝/記錄Bean的資訊[1. scope 2 Bean對應的Class物件, 反射可以生對應的物件]
 */
public class BeanDefinition {
    private String scope;
    private Class clazz;
    //可以根據需求,進行擴充套件

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    @Override
    public String toString() {
        return "BeanDefinition{" +
                "scope='" + scope + '\'' +
                ", clazz=" + clazz +
                '}';
    }
}

HspSpringApplicationContext.java
package com.hspedu.spring.ioc;



import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.annotation.ComponentScan;
import com.hspedu.spring.annotation.Scope;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * HspSpringApplicationContext 類的作用類似Spring原生ioc容器
 */

public class HspSpringApplicationContext {
    private Class configClass;

    //定義屬性BeanDefinitionMap -> 存放BeanDefinition物件
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =
            new ConcurrentHashMap<>();
    //定義屬性SingletonObjects -> 存放單例物件,key是String,value是不確定的型別,所以用Object
    private ConcurrentHashMap<String, Object> singletonObjects =
            new ConcurrentHashMap<>();

    //構造器
    public HspSpringApplicationContext(Class configClass) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //完成掃描指定包
        beanDefinitionByScan(configClass);
        System.out.println("beanDefinitionMap=" + beanDefinitionMap);
    }

    //該方法完成對指定包的掃描,並將Bean資訊封裝到BeanDefinition物件,在放入到Map
    public void beanDefinitionByScan(Class configClass) throws ClassNotFoundException {
        this.configClass = configClass;
        //獲取要掃描的包
        //1. 先得到HspSpringConfig配置的@ComponentScan(value = "com.hspedu.spring.component")
        ComponentScan componentScan =
                (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2. 透過componentScan的value=> 即要掃描的包
        String path = componentScan.value();
        System.out.println("要掃描的包= " + path);

        //得到要掃描的包下的所有資源(類 .class)
        //1.得到類的載入器->APP類載入器
        ClassLoader classLoader =
                HspSpringApplicationContext.class.getClassLoader();
        //2. 透過類的載入器獲取到要掃描的包的資源 url=》類似一個路徑
        //一定要把. 替換成 /
        path = path.replace(".","/");
        URL resource =
                classLoader.getResource("com/hspedu/spring/component");
        System.out.println("resource=" + resource);

        //3. 將要載入的資源(.class) 路徑下的檔案進行遍歷=>io
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                String fileAbsolutePath = f.getAbsolutePath();
                //這裡我們只處理.class檔案
                if (fileAbsolutePath.endsWith(".class")) {
                    //1. 獲取到類名
                    String className =
                            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    //2. 獲取類的完整的路徑(全類名)
                    //老師解讀 path.replace("/",".") => com.hspedu.spring.component.
                    String classFullName = path.replace("/", ".") + "." + className;

                    //3. 判斷該類是不是需要注入容器, 就看該類是不是有註解 @Component @Service..
                    Class<?> clazz = classLoader.loadClass(classFullName);
                    if (clazz.isAnnotationPresent(Component.class)) {
                        //如果該類使用了@Component,說明是Spring bean
                        System.out.println("是一個Spring bean =" + clazz + " 類名=" + className);
                        System.out.println("------------------------------------");
                        //先得到beanName
                        //1. 得到Component註解
                        Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                        //2. 得到配置value值, 疑問 如果程式設計師沒有配置value[後面處理..]
                        String beanName = componentAnnotation.value();
                        if ("".equals(beanName)) {//如果沒有寫value

                            //將該類的類名首字母小寫作為beanName
                            beanName = StringUtils.uncapitalize(className);
                        }

                        //3.將Bean的資訊封裝到BeanDefinition物件->放入到BeanDefinitionMap
                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setClazz(clazz);

                        //4. 獲取Scope值
                        if (clazz.isAnnotationPresent(Scope.class)) {
                            //如果配置了Scope, 獲取他配置的值
                            Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
                            beanDefinition.setScope(scopeAnnotation.value());
                        } else {
                            //如果沒有配置Scope, 就預設的值singleton
                            beanDefinition.setScope("singleton");
                        }

                        //將beanDefinition 物件放入到Map
                        beanDefinitionMap.put(beanName, beanDefinition);

                    } else {
                        //如果該類沒有使用了@Component,說明不是Spring bean
                        System.out.println("不是一個Spring bean =" + clazz + " 類名=" + className);
                        System.out.println("------------------------------------");
                    }
                }
            }

        }
    }

    //編寫方法返回對容器中物件
    public Object getBean(String name) {
        return null;
    }
}

AppMain.java

package com.hspedu.spring;

import com.hspedu.spring.ioc.HspSpringApplicationContext;
import com.hspedu.spring.ioc.HspSpringConfig;

public class AppMain {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //把 配置檔案class物件傳進去後,然後再去容器中掃描包,容器中有注入的userService,userDao的bean物件
        HspSpringApplicationContext hspSpringApplicationContext =
                new HspSpringApplicationContext(HspSpringConfig.class);
        System.out.println("ok");
    }
}

執行結果:

有一部分太長了

Debug結果:

20,初始化Bean單例池,並完成getBean,createBean方法

分析示意圖

程式碼結構不變,ioc包的HspSpringApplicationContext.java和 AppMain.java變了

ioc包

HspSpringApplicationContext.java
package com.hspedu.spring.ioc;



import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.annotation.ComponentScan;
import com.hspedu.spring.annotation.Scope;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Enumeration;
import java.util.concurrent.ConcurrentHashMap;

/**
 * HspSpringApplicationContext 類的作用類似Spring原生ioc容器
 */

public class HspSpringApplicationContext {
    private Class configClass;

    //定義屬性BeanDefinitionMap -> 存放BeanDefinition物件
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =
            new ConcurrentHashMap<>();
    //定義屬性SingletonObjects -> 存放單例物件,key是String,value是不確定的型別,所以用Object
    private ConcurrentHashMap<String, Object> singletonObjects =
            new ConcurrentHashMap<>();

    //構造器
    public HspSpringApplicationContext(Class configClass) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //完成掃描指定包
        beanDefinitionByScan(configClass);
        System.out.println("beanDefinitionMap=" + beanDefinitionMap);

        //透過beanDefinitionMap , 初始化singletonObjects 單例池
        //封裝成方法
        //遍歷所有的beanDefinition物件
        //這裡是java基礎->集合和列舉
        Enumeration<String> keys = beanDefinitionMap.keys();//把key=beanName拿到了
        while (keys.hasMoreElements()){
            //得到beanName
            String beanName = keys.nextElement();
            //透過beanName 得到對應的beanDefinition物件
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            //判斷該bean是singleton還是prototype
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //將該bean例項放入到singletonObjects 集合
                Object bean = createBean(beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
        System.out.println("singletonObjects 單例池=" + singletonObjects);
        System.out.println("beanDefinitionMap=" + beanDefinitionMap);
    }

    //該方法完成對指定包的掃描,並將Bean資訊封裝到BeanDefinition物件,在放入到Map
    public void beanDefinitionByScan(Class configClass) throws ClassNotFoundException {
        this.configClass = configClass;
        //獲取要掃描的包
        //1. 先得到HspSpringConfig配置的@ComponentScan(value = "com.hspedu.spring.component")
        ComponentScan componentScan =
                (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2. 透過componentScan的value=> 即要掃描的包
        String path = componentScan.value();
        System.out.println("要掃描的包= " + path);

        //得到要掃描的包下的所有資源(類 .class)
        //1.得到類的載入器->APP類載入器
        ClassLoader classLoader =
                HspSpringApplicationContext.class.getClassLoader();
        //2. 透過類的載入器獲取到要掃描的包的資源 url=》類似一個路徑
        //一定要把. 替換成 /
        path = path.replace(".","/");
        URL resource =
                classLoader.getResource("com/hspedu/spring/component");
        System.out.println("resource=" + resource);

        //3. 將要載入的資源(.class) 路徑下的檔案進行遍歷=>io
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                String fileAbsolutePath = f.getAbsolutePath();
                //這裡我們只處理.class檔案
                if (fileAbsolutePath.endsWith(".class")) {
                    //1. 獲取到類名
                    String className =
                            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    //2. 獲取類的完整的路徑(全類名)
                    //老師解讀 path.replace("/",".") => com.hspedu.spring.component.
                    String classFullName = path.replace("/", ".") + "." + className;

                    //3. 判斷該類是不是需要注入容器, 就看該類是不是有註解 @Component @Service..
                    Class<?> clazz = classLoader.loadClass(classFullName);
                    if (clazz.isAnnotationPresent(Component.class)) {
                        //如果該類使用了@Component,說明是Spring bean
                        System.out.println("是一個Spring bean =" + clazz + " 類名=" + className);
                        System.out.println("------------------------------------");
                        //先得到beanName
                        //1. 得到Component註解
                        Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                        //2. 得到配置value值, 疑問 如果程式設計師沒有配置value[後面處理..]
                        String beanName = componentAnnotation.value();
                        if ("".equals(beanName)) {//如果沒有寫value

                            //將該類的類名首字母小寫作為beanName
                            beanName = StringUtils.uncapitalize(className);
                        }

                        //3.將Bean的資訊封裝到BeanDefinition物件->放入到BeanDefinitionMap
                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setClazz(clazz);

                        //4. 獲取Scope值
                        if (clazz.isAnnotationPresent(Scope.class)) {
                            //如果配置了Scope, 獲取他配置的值
                            Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
                            beanDefinition.setScope(scopeAnnotation.value());
                        } else {
                            //如果沒有配置Scope, 就預設的值singleton
                            beanDefinition.setScope("singleton");
                        }

                        //將beanDefinition 物件放入到Map
                        beanDefinitionMap.put(beanName, beanDefinition);

                    } else {
                        //如果該類沒有使用了@Component,說明不是Spring bean
                        System.out.println("不是一個Spring bean =" + clazz + " 類名=" + className);
                        System.out.println("------------------------------------");
                    }
                }
            }

        }
    }

    //完成createBean(BeanDefinition beanDefinition) 方法
    //老師說明,目前,我們先簡單實現
    private Object createBean(BeanDefinition beanDefinition){
        //得到Bean的clazz物件
        Class clazz = beanDefinition.getClazz();

        try {
            //使用反射得到例項
            Object instance = clazz.getDeclaredConstructor().newInstance();
            return instance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        //如果反射建立物件失敗
        return null;
    }

    //編寫方法返回對容器中物件
    public Object getBean(String name) {
        //老師加一個判斷,傳入的beanName是否在beanDefinitionMap中存在..
        if (beanDefinitionMap.containsKey(name)) {//如果存在

            BeanDefinition beanDefinition = beanDefinitionMap.get(name);
            //得到beanDefinition的scope, 分別進行處理
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //說明是單例配置, 就直接從單例池獲取
                return singletonObjects.get(name);
            } else {//如果不是單例的,我就呼叫createBean, 反射一個物件
                return createBean(beanDefinition);
            }
        } else {//如果不存在
            //丟擲一個空指標異常-小夥伴也可以自定義-Java基礎異常
            throw new NullPointerException("沒有該bean");
        }
    }
}

AppMain.java

package com.hspedu.spring;

import com.hspedu.spring.component.MonsterDao;
import com.hspedu.spring.component.MonsterService;
import com.hspedu.spring.ioc.HspSpringApplicationContext;
import com.hspedu.spring.ioc.HspSpringConfig;

public class AppMain {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //把 配置檔案class物件傳進去後,然後再去容器中掃描包,容器中有注入的userService,userDao的bean物件
        HspSpringApplicationContext hspSpringApplicationContext =
                new HspSpringApplicationContext(HspSpringConfig.class);
        MonsterService monsterService = (MonsterService) hspSpringApplicationContext.getBean("monsterService");
        MonsterService monsterService2 = (MonsterService) hspSpringApplicationContext.getBean("monsterService");
        System.out.println("monsterService=" + monsterService);
        System.out.println("monsterService2=" + monsterService2);

        MonsterDao monsterDao = (MonsterDao) hspSpringApplicationContext.getBean("monsterDao");
        MonsterDao monsterDao2 = (MonsterDao) hspSpringApplicationContext.getBean("monsterDao");

        System.out.println("monsterDao=" + monsterDao);
        System.out.println("monsterDao2=" + monsterDao2);

        System.out.println("ok");
    }
}

執行結果:

23,實現依賴注入

分析示意圖:

1,增加註解 @Autowired

2,在MonsterService類裡增加屬性MonsterDao

3,在createBean方法裡增加依賴注入的業務

程式碼結構:

以下程式碼改變了,其餘不變

annotation包

介面Autowired.java
package com.hspedu.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

component包

MonsterDao.java
package com.hspedu.spring.component;

import com.hspedu.spring.annotation.Component;

@Component("monsterDao")
public class MonsterDao {
    public void hi() {
        System.out.println("MonsterDao-hi()");
    }
}

MonsterService.java
package com.hspedu.spring.component;

//引入自己定義的註解
import com.hspedu.spring.annotation.Autowired;
import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.annotation.Scope;

/**
 * 說明MonsterService 是一個Service
 * 1. 如果指定了value,那麼在注入spring容器時,以你指定為準
 * 2. 如果沒有指定value ,則使用類名首字母小寫名字
 */
@Component//(value = "monsterService") //把MonsterService注入我們自己的spring容器中
@Scope(value = "prototype")
public class MonsterService {
    //這裡我們使用自己的@Autowired來修飾屬性
    //表示該屬性,是透過容器完成依賴注入
    //說明: 我們實現按照名字來進行組裝即可
    @Autowired
    private MonsterDao monsterDao;

    public void m1() {
        monsterDao.hi();
    }
}

ioc包

HspSpringApplicationContext.java
package com.hspedu.spring.ioc;



import com.hspedu.spring.annotation.Autowired;
import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.annotation.ComponentScan;
import com.hspedu.spring.annotation.Scope;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Enumeration;
import java.util.concurrent.ConcurrentHashMap;

/**
 * HspSpringApplicationContext 類的作用類似Spring原生ioc容器
 */

public class HspSpringApplicationContext {
    private Class configClass;

    //定義屬性BeanDefinitionMap -> 存放BeanDefinition物件
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =
            new ConcurrentHashMap<>();
    //定義屬性SingletonObjects -> 存放單例物件,key是String,value是不確定的型別,所以用Object
    private ConcurrentHashMap<String, Object> singletonObjects =
            new ConcurrentHashMap<>();

    //構造器
    public HspSpringApplicationContext(Class configClass) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //完成掃描指定包
        beanDefinitionByScan(configClass);
        System.out.println("beanDefinitionMap=" + beanDefinitionMap);

        //透過beanDefinitionMap , 初始化singletonObjects 單例池
        //封裝成方法
        //遍歷所有的beanDefinition物件
        //這裡是java基礎->集合和列舉
        Enumeration<String> keys = beanDefinitionMap.keys();//把key=beanName拿到了
        while (keys.hasMoreElements()){
            //得到beanName
            String beanName = keys.nextElement();
            //透過beanName 得到對應的beanDefinition物件
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            //判斷該bean是singleton還是prototype
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //將該bean例項放入到singletonObjects 集合
                Object bean = createBean(beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
        System.out.println("singletonObjects 單例池=" + singletonObjects);
        System.out.println("beanDefinitionMap=" + beanDefinitionMap);
    }

    //該方法完成對指定包的掃描,並將Bean資訊封裝到BeanDefinition物件,在放入到Map
    public void beanDefinitionByScan(Class configClass) throws ClassNotFoundException {
        this.configClass = configClass;
        //獲取要掃描的包
        //1. 先得到HspSpringConfig配置的@ComponentScan(value = "com.hspedu.spring.component")
        ComponentScan componentScan =
                (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2. 透過componentScan的value=> 即要掃描的包
        String path = componentScan.value();
        System.out.println("要掃描的包= " + path);

        //得到要掃描的包下的所有資源(類 .class)
        //1.得到類的載入器->APP類載入器
        ClassLoader classLoader =
                HspSpringApplicationContext.class.getClassLoader();
        //2. 透過類的載入器獲取到要掃描的包的資源 url=》類似一個路徑
        //一定要把. 替換成 /
        path = path.replace(".","/");
        URL resource =
                classLoader.getResource("com/hspedu/spring/component");
        System.out.println("resource=" + resource);

        //3. 將要載入的資源(.class) 路徑下的檔案進行遍歷=>io
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                String fileAbsolutePath = f.getAbsolutePath();
                //這裡我們只處理.class檔案
                if (fileAbsolutePath.endsWith(".class")) {
                    //1. 獲取到類名
                    String className =
                            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    //2. 獲取類的完整的路徑(全類名)
                    //老師解讀 path.replace("/",".") => com.hspedu.spring.component.
                    String classFullName = path.replace("/", ".") + "." + className;

                    //3. 判斷該類是不是需要注入容器, 就看該類是不是有註解 @Component @Service..
                    Class<?> clazz = classLoader.loadClass(classFullName);
                    if (clazz.isAnnotationPresent(Component.class)) {
                        //如果該類使用了@Component,說明是Spring bean
                        System.out.println("是一個Spring bean =" + clazz + " 類名=" + className);
                        System.out.println("------------------------------------");
                        //先得到beanName
                        //1. 得到Component註解
                        Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                        //2. 得到配置value值, 疑問 如果程式設計師沒有配置value[後面處理..]
                        String beanName = componentAnnotation.value();
                        if ("".equals(beanName)) {//如果沒有寫value

                            //將該類的類名首字母小寫作為beanName
                            beanName = StringUtils.uncapitalize(className);
                        }

                        //3.將Bean的資訊封裝到BeanDefinition物件->放入到BeanDefinitionMap
                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setClazz(clazz);

                        //4. 獲取Scope值
                        if (clazz.isAnnotationPresent(Scope.class)) {
                            //如果配置了Scope, 獲取他配置的值
                            Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
                            beanDefinition.setScope(scopeAnnotation.value());
                        } else {
                            //如果沒有配置Scope, 就預設的值singleton
                            beanDefinition.setScope("singleton");
                        }

                        //將beanDefinition 物件放入到Map
                        beanDefinitionMap.put(beanName, beanDefinition);

                    } else {
                        //如果該類沒有使用了@Component,說明不是Spring bean
                        System.out.println("不是一個Spring bean =" + clazz + " 類名=" + className);
                        System.out.println("------------------------------------");
                    }
                }
            }

        }
    }

    //完成createBean(BeanDefinition beanDefinition) 方法
    //老師說明,目前,我們先簡單實現
    private Object createBean(BeanDefinition beanDefinition){
        //得到Bean的clazz物件
        Class clazz = beanDefinition.getClazz();

        try {
            //使用反射得到例項
            Object instance = clazz.getDeclaredConstructor().newInstance();

            //老師分析: 這裡老韓會加入依賴注入的業務邏輯!!!
            //1. 遍歷當前要建立的物件的所有欄位,看看哪個欄位有@Autowired
            for (Field declaredField : clazz.getDeclaredFields()) {
                //2. 判斷這個欄位是否有@Autowired
                if (declaredField.isAnnotationPresent(Autowired.class)) {
                    //3. 得到這個欄位名字
                    String name = declaredField.getName();
                    //4. 透過getBean方法來獲取要組裝物件
                    Object bean = getBean(name);
                    //5. 進行組裝
                    declaredField.setAccessible(true);//因為屬性是pirvate, 需要暴破
                    declaredField.set(instance, bean);
                }
            }

            return instance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        //如果反射建立物件失敗
        return null;
    }

    //編寫方法返回對容器中物件
    public Object getBean(String name) {
        //老師加一個判斷,傳入的beanName是否在beanDefinitionMap中存在..
        if (beanDefinitionMap.containsKey(name)) {//如果存在

            BeanDefinition beanDefinition = beanDefinitionMap.get(name);
            //得到beanDefinition的scope, 分別進行處理
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //說明是單例配置, 就直接從單例池獲取
                return singletonObjects.get(name);
            } else {//如果不是單例的,我就呼叫createBean, 反射一個物件
                return createBean(beanDefinition);
            }
        } else {//如果不存在
            //丟擲一個空指標異常-小夥伴也可以自定義-Java基礎異常
            throw new NullPointerException("沒有該bean");
        }
    }
}

AppMain.java

package com.hspedu.spring;

import com.hspedu.spring.component.MonsterDao;
import com.hspedu.spring.component.MonsterService;
import com.hspedu.spring.ioc.HspSpringApplicationContext;
import com.hspedu.spring.ioc.HspSpringConfig;

public class AppMain {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //把 配置檔案class物件傳進去後,然後再去容器中掃描包,容器中有注入的userService,userDao的bean物件
        HspSpringApplicationContext hspSpringApplicationContext =
                new HspSpringApplicationContext(HspSpringConfig.class);

        //測試一下依賴注入的功能
        MonsterService monsterService =
                (MonsterService)hspSpringApplicationContext.getBean("monsterService");

        monsterService.m1();
    }
}

執行結果:

26,實現BeanPostProcessor機制

程式碼結構:

annotation包

介面Autowired.java
package com.hspedu.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

Component包

Car.java
package com.hspedu.spring.component;

import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.processor.InitializingBean;

@Component
public class Car implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Car的初始化方法...");
    }
}

MonsterDao.java
package com.hspedu.spring.component;

import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.processor.InitializingBean;

@Component("monsterDao")
public class MonsterDao implements InitializingBean {
    public void hi() {
        System.out.println("MonsterDao-hi()");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("MonsterDao 初始化方法被呼叫...");
    }
}

MonsterService.java
package com.hspedu.spring.component;

//引入自己定義的註解
import com.hspedu.spring.annotation.Autowired;
import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.annotation.Scope;
import com.hspedu.spring.processor.InitializingBean;

/**
 * 說明MonsterService 是一個Service
 * 1. 如果指定了value,那麼在注入spring容器時,以你指定為準
 * 2. 如果沒有指定value ,則使用類名首字母小寫名字
 */
@Component//(value = "monsterService") //把MonsterService注入我們自己的spring容器中
@Scope(value = "prototype")
public class MonsterService implements InitializingBean {
    //這裡我們使用自己的@Autowired來修飾屬性
    //表示該屬性,是透過容器完成依賴注入
    //說明: 我們實現按照名字來進行組裝即可
    @Autowired
    private MonsterDao monsterDao;

    public void m1() {
        monsterDao.hi();
    }

    /**
     * 老師解讀
     * 1. afterPropertiesSet就是在bean的setter方法執行完畢後被spring容器呼叫
     * 2 即就是初始化方法
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("MonsterService 初始化方法被呼叫 程式設計師在這裡加入初始化的業務..");
    }
}

HspBeanPostProcessor.java
package com.hspedu.spring.component;

import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.processor.BeanPostProcessor;

/**
 * 說明
 * 1. 這是我們自己的一個後置處理器
 * 2. 實現了BeanPostProcessor
 * 3. 我們可以重寫before和after方法
 * 4. 在Spring容器中,仍然把HspBeanPostProcessor當做一個Bean物件, 要在注入到容器
 * 5. @Component 標識
 * 6. 我們要讓HspBeanPostProcessor成為真正的後置處理器, 需要在容器中加入業務程式碼
 * 7. 還要考慮多個後置處理器物件注入到容器問題
 */
@Component
public class HspBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        //這裡請小夥伴一定要體會到,後置處理器是會容器的建立的bean生效
        //,相當於是可以對多個物件程式設計, 切面程式設計
        //日誌,許可權,身份, 事務.......
        if (bean instanceof Car) {
            System.out.println("這是一個Car物件, 我可以處理");
            //((Car)bean)
        }
        System.out.println("後置處理器HspBeanPostProcessor Before呼叫 bean型別="
                + bean.getClass() + " bean的名字=" + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("後置處理器HspBeanPostProcessor After呼叫 bean型別="
                + bean.getClass() + " bean的名字=" + beanName);
        return bean;
    }
}

ioc包

HspSpringApplicationContext.java
package com.hspedu.spring.ioc;



import com.hspedu.spring.annotation.Autowired;
import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.annotation.ComponentScan;
import com.hspedu.spring.annotation.Scope;
import com.hspedu.spring.processor.BeanPostProcessor;
import com.hspedu.spring.processor.InitializingBean;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * HspSpringApplicationContext 類的作用類似Spring原生ioc容器
 */

public class HspSpringApplicationContext {
    private Class configClass;

    //定義屬性BeanDefinitionMap -> 存放BeanDefinition物件
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =
            new ConcurrentHashMap<>();
    //定義屬性SingletonObjects -> 存放單例物件,key是String,value是不確定的型別,所以用Object
    private ConcurrentHashMap<String, Object> singletonObjects =
            new ConcurrentHashMap<>();

    //定義一個屬性beanPostProcessorList, => 存放後置處理器
    private List<BeanPostProcessor> beanPostProcessorList =
            new ArrayList<>();

    //構造器
    public HspSpringApplicationContext(Class configClass) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //完成掃描指定包
        beanDefinitionByScan(configClass);
        System.out.println("beanDefinitionMap=" + beanDefinitionMap);

        //透過beanDefinitionMap , 初始化singletonObjects 單例池
        //封裝成方法
        //遍歷所有的beanDefinition物件
        //這裡是java基礎->集合和列舉
        Enumeration<String> keys = beanDefinitionMap.keys();//把key=beanName拿到了
        while (keys.hasMoreElements()){
            //得到beanName
            String beanName = keys.nextElement();
            //透過beanName 得到對應的beanDefinition物件
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            //判斷該bean是singleton還是prototype
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //將該bean例項放入到singletonObjects 集合
                Object bean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
//        System.out.println("singletonObjects 單例池=" + singletonObjects);
//        System.out.println("beanDefinitionMap=" + beanDefinitionMap);
    }

    //該方法完成對指定包的掃描,並將Bean資訊封裝到BeanDefinition物件,在放入到Map
    public void beanDefinitionByScan(Class configClass) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        this.configClass = configClass;
        //獲取要掃描的包
        //1. 先得到HspSpringConfig配置的@ComponentScan(value = "com.hspedu.spring.component")
        ComponentScan componentScan =
                (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2. 透過componentScan的value=> 即要掃描的包
        String path = componentScan.value();
        System.out.println("要掃描的包= " + path);

        //得到要掃描的包下的所有資源(類 .class)
        //1.得到類的載入器->APP類載入器
        ClassLoader classLoader =
                HspSpringApplicationContext.class.getClassLoader();
        //2. 透過類的載入器獲取到要掃描的包的資源 url=》類似一個路徑
        //一定要把. 替換成 /
        path = path.replace(".","/");
        URL resource =
                classLoader.getResource("com/hspedu/spring/component");
        System.out.println("resource=" + resource);

        //3. 將要載入的資源(.class) 路徑下的檔案進行遍歷=>io
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                String fileAbsolutePath = f.getAbsolutePath();
                //這裡我們只處理.class檔案
                if (fileAbsolutePath.endsWith(".class")) {
                    //1. 獲取到類名
                    String className =
                            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    //2. 獲取類的完整的路徑(全類名)
                    //老師解讀 path.replace("/",".") => com.hspedu.spring.component.
                    String classFullName = path.replace("/", ".") + "." + className;

                    //3. 判斷該類是不是需要注入容器, 就看該類是不是有註解 @Component @Service..
                    Class<?> clazz = classLoader.loadClass(classFullName);
                    if (clazz.isAnnotationPresent(Component.class)) {
                        //如果該類使用了@Component,說明是Spring bean
                        System.out.println("是一個Spring bean =" + clazz + " 類名=" + className);

                        //老師說明
                        //1. 為了方便,老韓這裡將後置處理器放入到一個ArrayList
                        //2. 如果發現是一個後置處理器, 放入到 beanPostProcessorList
                        //3. 在原生的Spring容器中, 對後置處理器還是走的getBean, createBean
                        //   , 但是需要我們在singletonObjects 加入相應的業務邏輯
                        //4. 因為這裡我們是為了講解後置處理去的機制,我就簡化
                        //5. 如果小夥伴們,仍然走以前的邏輯,也可以,就是要麻煩一點


                        //判斷當前的這個clazz有沒有實現BeanPostProcessor
                        //說明, 這裡我們不能使用 instanceof 來判斷clazz是否實現了BeanPostProcessor
                        //原因: clazz不是一個例項物件,而是一個類物件/clazz, 使用isAssignableFrom
                        //小夥伴將其當做一個語法理解
                        if (BeanPostProcessor.class.isAssignableFrom(clazz)) {

                            BeanPostProcessor beanPostProcessor =
                                    (BeanPostProcessor) clazz.newInstance();
                            //放入到beanPostProcessorList
                            beanPostProcessorList.add(beanPostProcessor);
                            continue;
                        }
                        System.out.println("-------------------------------------------------[pyuyu");
                        //先得到beanName
                        //1. 得到Component註解
                        Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                        //2. 得到配置value值, 疑問 如果程式設計師沒有配置value[後面處理..]
                        String beanName = componentAnnotation.value();
                        if ("".equals(beanName)) {//如果沒有寫value

                            //將該類的類名首字母小寫作為beanName
                            beanName = StringUtils.uncapitalize(className);
                        }

                        //3.將Bean的資訊封裝到BeanDefinition物件->放入到BeanDefinitionMap
                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setClazz(clazz);

                        //4. 獲取Scope值
                        if (clazz.isAnnotationPresent(Scope.class)) {
                            //如果配置了Scope, 獲取他配置的值
                            Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
                            beanDefinition.setScope(scopeAnnotation.value());
                        } else {
                            //如果沒有配置Scope, 就預設的值singleton
                            beanDefinition.setScope("singleton");
                        }

                        //將beanDefinition 物件放入到Map
                        beanDefinitionMap.put(beanName, beanDefinition);

                    } else {
                        //如果該類沒有使用了@Component,說明不是Spring bean
                        System.out.println("不是一個Spring bean =" + clazz + " 類名=" + className);
                        System.out.println("------------------------------------");
                    }
                }
            }

        }
    }

    //完成createBean(BeanDefinition beanDefinition) 方法
    //老師說明,目前,我們先簡單實現
    private Object createBean(String beanName, BeanDefinition beanDefinition){
        //得到Bean的clazz物件
        Class clazz = beanDefinition.getClazz();

        try {
            //使用反射得到例項
            Object instance = clazz.getDeclaredConstructor().newInstance();

            //老師分析: 這裡老韓會加入依賴注入的業務邏輯!!!
            //1. 遍歷當前要建立的物件的所有欄位,看看哪個欄位有@Autowired
            for (Field declaredField : clazz.getDeclaredFields()) {
                //2. 判斷這個欄位是否有@Autowired
                if (declaredField.isAnnotationPresent(Autowired.class)) {
                    //3. 得到這個欄位名字
                    String name = declaredField.getName();
                    //4. 透過getBean方法來獲取要組裝物件
                    Object bean = getBean(name);
                    //5. 進行組裝
                    declaredField.setAccessible(true);//因為屬性是pirvate, 需要暴破
                    declaredField.set(instance, bean);
                }
            }

            System.out.println("=====建立好例項====" + instance);

            //我們在Bean的初始化方法前,呼叫後置處理器的before方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                //在後置處理器的before方法,可以對容器的bean例項進行處理
                //然後返回處理後新的bean例項, 相當於做一個前置處理
                Object current =
                        beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
                //不是空,就還是原先的instance
                if (current != null) {
                    instance = current;
                }
            }

            //這裡判斷是否要執行Bean初始化方法
            //1. 判斷當前建立的Bean物件是否實現了InitializingBean
            //2. instanceof java基礎中講 表判斷某個物件的執行型別是不是某個型別或者
            //   某個型別的子型別
            //3. 這裡就使用到介面程式設計
            if (instance instanceof InitializingBean) {
                //3.將instance轉成InitializingBean型別
                try {
                    ((InitializingBean) instance).afterPropertiesSet();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            //我們在Bean的初始化方法後,呼叫後置處理器的after方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                //在後置處理器的after方法,可以對容器的bean例項進行處理
                //然後返回處理後的bean例項, 相當於做一個後置處理
                //原生Spring容器,比我們這個還要複雜
                Object current =
                        beanPostProcessor.postProcessAfterInitialization(instance, beanName);
                if(current != null) {
                    instance = current;
                }
            }

            System.out.println("----------------------------------------------");
            return instance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        //如果反射建立物件失敗
        return null;
    }

    //編寫方法返回對容器中物件
    public Object getBean(String name) {
        //老師加一個判斷,傳入的beanName是否在beanDefinitionMap中存在..
        if (beanDefinitionMap.containsKey(name)) {//如果存在

            BeanDefinition beanDefinition = beanDefinitionMap.get(name);
            //得到beanDefinition的scope, 分別進行處理
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //說明是單例配置, 就直接從單例池獲取
                return singletonObjects.get(name);
            } else {//如果不是單例的,我就呼叫createBean, 反射一個物件
                return createBean(name, beanDefinition);
            }
        } else {//如果不存在
            //丟擲一個空指標異常-小夥伴也可以自定義-Java基礎異常
            throw new NullPointerException("沒有該bean");
        }
    }
}

processor包

InitializingBean.java
package com.hspedu.spring.processor;

/**
 * 老師解讀
 * 1. 我們根據原生Spring 定義了一個InitializingBean
 * 2. 該InitializingBean介面有一個方法void afterPropertiesSet() throws Exception;
 * 3. afterPropertiesSet() 在Bean的 setter後執行,即就是我們原來的初始化方法
 * 4. 當一個Bean實現這個介面後,就實現afterPropertiesSet() , 這個方法就是初始化方法
 */
public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

BeanPostProcessor.java
package com.hspedu.spring.processor;

/**
 * 老師解讀
 * 1. 參考原生Spring容器定義一個介面BeanPostProcessor
 * 2. 該介面有兩個方法postProcessBeforeInitialization 和 postProcessAfterInitialization
 * 3. 這兩個方法,會對Spring容器的所有Bean生效, 已經是切面程式設計的概念.
 */
public interface BeanPostProcessor {
    /**
     * 老師說明
     * 1. postProcessBeforeInitialization在Bean的初始化方法前呼叫
     * @param bean
     * @param beanName
     * @return
     */
    default Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    /**
     * 1. postProcessAfterInitialization在Bean的初始化方法後呼叫
     * @param bean
     * @param beanName
     * @return
     */
    default Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }
}

AppMain.java
package com.hspedu.spring;

import com.hspedu.spring.component.MonsterDao;
import com.hspedu.spring.component.MonsterService;
import com.hspedu.spring.ioc.HspSpringApplicationContext;
import com.hspedu.spring.ioc.HspSpringConfig;

public class AppMain {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //把 配置檔案class物件傳進去後,然後再去容器中掃描包,容器中有注入的userService,userDao的bean物件
        HspSpringApplicationContext hspSpringApplicationContext =
                new HspSpringApplicationContext(HspSpringConfig.class);

        //測試一下依賴注入的功能
        MonsterService monsterService =
                (MonsterService)hspSpringApplicationContext.getBean("monsterService");

        monsterService.m1();
    }
}

執行結果:

35,實現AOP機制

AOP機制需要 Bean後置處理機制和動態代理機制

程式碼結構:

annotation包

註解Aspect.java
package com.hspedu.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Aspect {
    String value() default "";
}

component包

SmartAnimalable.java
package com.hspedu.spring.component;

public interface SmartAnimalable {
    float getSum(float i, float j);
    float getSub(float i, float j);
}

SmartDog.java
package com.hspedu.spring.component;

import com.hspedu.spring.annotation.Component;

@Component(value = "smartDog")
public class SmartDog implements SmartAnimalable{
    @Override
    public float getSum(float i, float j) {
        float res = i + j;
        System.out.println("SmartDog-getSum-res=" + res);
        return res;
    }

    @Override
    public float getSub(float i, float j) {
        float res = i - j;
        System.out.println("SmartDog-getSub-res=" + res);
        return res;
    }
}

SmartAnimalAspect.java
package com.hspedu.spring.component;


import com.hspedu.spring.annotation.Aspect;
import com.hspedu.spring.annotation.Component;

/**
 * 老師說明:SmartAnimalAspect當做一個切面類來使用
 * ,後面老師再分析如何做的更加靈活
 */
@Aspect //我們的註解
@Component //這是實現了
public class SmartAnimalAspect {

    public static void showBeginLog() {

        System.out.println("前置通知..");
    }

    public static void showSuccessLog() {

        System.out.println("返回通知..");
    }
}

processor包

BeanPostProcessor.java
package com.hspedu.spring.component;

import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.processor.BeanPostProcessor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 說明
 * 1. 這是我們自己的一個後置處理器
 * 2. 實現了BeanPostProcessor
 * 3. 我們可以重寫before和after方法
 * 4. 在Spring容器中,仍然把HspBeanPostProcessor當做一個Bean物件, 要在注入到容器
 * 5. @Component 標識
 * 6. 我們要讓HspBeanPostProcessor成為真正的後置處理器, 需要在容器中加入業務程式碼
 * 7. 還要考慮多個後置處理器物件注入到容器問題
 */
@Component
public class HspBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        //這裡請小夥伴一定要體會到,後置處理器是會容器的建立的bean生效
        //,相當於是可以對多個物件程式設計, 切面程式設計
        //日誌,許可權,身份, 事務.......
        if (bean instanceof Car) {
            System.out.println("這是一個Car物件, 我可以處理");
            //((Car)bean)
        }
        System.out.println("後置處理器HspBeanPostProcessor Before呼叫 bean型別="
                + bean.getClass() + " bean的名字=" + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("後置處理器HspBeanPostProcessor After呼叫 bean型別="
                + bean.getClass() + " bean的名字=" + beanName);

        //實現AOP, 返回代理物件, 即對Bean進行包裝
        if ("smartDog".equals(beanName)) {
            //使用Jdk的動態代理,返回返回bean的代理物件
            //如果沒有印象的小夥伴,回去看老韓講過的動態代理
            Object proxyInstance = Proxy.newProxyInstance(HspBeanPostProcessor.class.getClassLoader(),
                    bean.getClass().getInterfaces(), new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args)
                                throws Throwable {
                            System.out.println("method=" + method.getName());
                            Object result = null;
                            //假如我們進行前置通知+返回通知 處理的方法是getSum
                            //後面可以透過註解來做的更加靈活
                            if ("getSum".equals(method.getName())) {
                                SmartAnimalAspect.showBeginLog();
                                result = method.invoke(bean, args);//執行目標方法
                                //進行返回通知的處理
                                SmartAnimalAspect.showSuccessLog();
                            } else {
                                result = method.invoke(bean, args);//執行目標方法
                            }
                            return result;
                        }
                    });
            //如果bean是需要返回代理物件的, 這裡就直接return proxyInstance
            return proxyInstance;
        }
        //如果不需要AOP, 返回 bean
        return bean;
    }
}

AppMain.java
package com.hspedu.spring;

import com.hspedu.spring.component.MonsterDao;
import com.hspedu.spring.component.MonsterService;
import com.hspedu.spring.component.SmartAnimalable;
import com.hspedu.spring.ioc.HspSpringApplicationContext;
import com.hspedu.spring.ioc.HspSpringConfig;

public class AppMain {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //把 配置檔案class物件傳進去後,然後再去容器中掃描包,容器中有注入的userService,userDao的bean物件
        HspSpringApplicationContext hspSpringApplicationContext =
                new HspSpringApplicationContext(HspSpringConfig.class);

        //這裡我們測試一下AOP機制是否生效了

        SmartAnimalable smartDog = (SmartAnimalable)hspSpringApplicationContext.getBean("smartDog");
        //System.out.println("smartDog=" + smartDog.getClass());

        smartDog.getSum(10, 2);

        smartDog.getSub(10,2);

        System.out.println("ok");
    }
}

執行結果:

截了一部分

44,JdbcTemplate使用

1. 引入使用 JdbcTemplate 需要的 jar

2. 建立資料庫 spring 和表 monster

注意:資料庫 mysql 版本是5.7.19。

在 SQLyog 軟體裡 建立資料庫和表,一句一句執行。

-- 建立資料庫
CREATE DATABASE spring
USE spring
-- 建立表 monster
CREATE TABLE monster(
id INT PRIMARY KEY,
`name` VARCHAR(64) NOT NULL DEFAULT '',
skill VARCHAR(64) NOT NULL DEFAULT ''
)CHARSET=utf8
INSERT INTO monster VALUES(100, '青牛怪', '吐火');
INSERT INTO monster VALUES(200, '黃袍怪', '吐煙');
INSERT INTO monster VALUES(300, '蜘蛛怪', '吐絲');

建立結果:

3. 建立配置檔案 src/jdbc.properties

jdbc.properties

改成自己的密碼

jdbc.user=root
jdbc.pwd=123
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8

4. 建立配置檔案 src/JdbcTemplate_ioc.xml

JdbcTemplate_ioc.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 引入外部屬性檔案 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 配置資料來源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.userName}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClass" value="${jdbc.driverClass}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
    </bean>
</beans>

5,測試是否可以正確得到資料來源

JdbcTemplateTest.java

package com.hspedu.spring.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class JdbcTemplateTest {

    @Test
    public void testDatasourceByJdbcTemplate() throws SQLException {
        //獲取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

        DataSource dataSource = ioc.getBean(DataSource.class);

        Connection connection = dataSource.getConnection();
        System.out.println("獲取到connection= " + connection);
        connection.close();
        System.out.println("ok");
    }
}

執行結果:

46,JdbcTemplate-新增資料

1,配置 JdbcTemplate_ioc.xml,將資料來源分配給 JdbcTemplate bean

JdbcTemplate_ioc.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--引入外部的jdbc.properties檔案-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置資料來源物件-DataSoruce-->
    <bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
        <!--給資料來源物件配置屬性值-->
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.pwd}"/>
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
    </bean>

    <!--配置JdbcTemplate物件-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <!--給JdbcTemplate物件配置屬性dataSource, 注意:後面是引用的上面的資料來源物件dataSource-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

2. 修改 JdbcTemplateTest.java,新增一個新的 monster

JdbcTemplateTest.java

package com.hspedu.spring.test;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class JdbcTemplateTest {

    //測試透過JdbcTemplate物件完成新增資料
    @Test
    public void addDataByJdbcTemplate() {
        //獲取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

        //獲取JdbcTemplate物件
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        //1. 新增方式1
        //String sql = "INSERT INTO monster VALUES(400, '紅孩兒', '槍法')";
        //jdbcTemplate.execute(sql);

        //2. 新增方式2,?用來佔位
        String sql = "INSERT INTO monster VALUES(?, ?, ?)";
        //affected表示 執行後表受影響的記錄數
        int affected = jdbcTemplate.update(sql, 500, "紅孩兒2", "槍法2");
        System.out.println("add ok affected=" + affected);
    }
}

執行結果:

sqlyog資料庫結果:

47,JdbcTemplate-修改資料

1,修改 JdbcTemplateTest.java,更新一個 monster skill

JdbcTemplateTest.java

package com.hspedu.spring.test;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class JdbcTemplateTest {

    //測試透過JdbcTemplate物件完成修改資料
    @Test
    public void updateDataByJdbcTemplate() {

        //獲取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

        //獲取JdbcTemplate物件
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

        //組織SQL
        String sql = "UPDATE monster SET skill=? WHERE id=?";
        int affected = jdbcTemplate.update(sql, "美女計", 500);
        System.out.println("update ok affected= " + affected);

    }
}

執行結果:

sqlyog資料庫結果:

48,JdbcTemplate-批次處理

1,修改 JdbcTemplateTest.java,批次新增二個 monster 白蛇精和青蛇精

JdbcTemplateTest.java

package com.hspedu.spring.test;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class JdbcTemplateTest {

    //批次新增二個monster 白蛇精和青蛇精
    //這裡有一個使用API的技巧
    /**
     * 老師說明
     * 1. 對於某個類, 有很多API, 使用的步驟
     * 2. 老韓的使用技巧(1) 先確定API名字 (2) 根據API提供相應的引數 [組織引數]
     *    (3) 把自己的呼叫思路清晰 (4) 根據API, 可以推測類似的用法和功能
     */

    /**
     * batch add data
     * 批次新增二個monster 白蛇精和青蛇精-update(sql,List<Object[]>)
     */
    @Test
    public void addBatchDataByJdbcTemplate() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到JdbcTemplate bean
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);//新增..

        //1. 先確定,猜測API名稱 batchUpdate[如果出現問題,才重新玩]
        //public int[] batchUpdate(String sql, List<Object[]> batchArgs){}
        //2. 準備引數
        String sql = "INSERT INTO monster VALUES(?, ?, ?)";
        List<Object[]> batchArgs = new ArrayList<>();
        batchArgs.add(new Object[]{600, "老鼠精", "偷吃糧食"});
        batchArgs.add(new Object[]{700, "老貓精", "抓老鼠"});
        //3. 呼叫
        //說明:返回結果是一個陣列,每個元素對應上面的sql語句對錶的影響記錄數
        int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
        //輸出
        for (int anInt : ints) {
            System.out.println("anInt=" + anInt);
        }
        System.out.println("batch add ok..");
    }
}

執行結果:

sqlyog資料庫結果:

49,JdbcTemplate-查詢後封裝成物件

1. 查詢 id=100 monster 並封裝到 Monster 實體物件

JdbcTemplateTest.java

package com.hspedu.spring.test;

import com.hspedu.spring.bean.Monster;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class JdbcTemplateTest {

    //查詢id=100的monster並封裝到Monster實體物件[在實際開發中,非常有用]
    @Test
    public void selectDataByJdbcTemplate() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到JdbcTemplate bean
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        //組織SQL
        //透過BeanPropertyRowMapper獲取rowmapper 是一個介面,可以將查詢的結果,封裝到你指定的Monster物件中.

        //1. 確定API : queryForObject()
        //public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
        //2.準備引數
        String sql = "SELECT id AS monsterId, NAME, skill FROM monster WHERE id = 100";
        //使用RowMapper 介面來對返回的資料,進行一個封裝-》底層使用的反射->setter
        //這裡有一個細節: 你查詢的記錄的表的欄位需要和 Monster的物件欄位名保持一致
        RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
        //jdbcTemplate
        Monster monster = jdbcTemplate.queryForObject(sql, rowMapper);
        System.out.println("monster= " + monster);
        System.out.println("查詢ok");

    }
}

執行結果:

50,JdbcTemplate-查詢後封裝成物件集合

1. 查詢 id>=100 monster 並封裝到 Monster 實體物件

JdbcTemplateTest.java

package com.hspedu.spring.test;

import com.hspedu.spring.bean.Monster;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class JdbcTemplateTest {

    //查詢id>=200的monster並封裝到Monster實體物件
    /**
     * 查詢多條記錄
     */
    @Test
    public void selectMulDataByJdbcTemplate() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到JdbcTemplate bean
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        //組織SQL
        //透過BeanPropertyRowMapper獲取rowmapper 是一個介面,可以將查詢的結果,封裝到你指定的Monster物件中.

        //1.    確定API
        //public <T> T query(String sql, RowMapper<T> rowMapper, Object... args){}
        //2. 組織引數
        String sql = "SELECT id AS monsterId, NAME, skill FROM monster WHERE id >= ?";
        RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
        //3. 呼叫
        List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper, 100);
        for (Monster monster : monsterList) {
            System.out.println("monster= " + monster);
        }
    }
}

執行結果:

51,JdbcTemplate-返回單行單列

1. 查詢返回結果只有一行一列的值,比如查詢 id=100 的怪物名

JdbcTemplateTest.java

package com.hspedu.spring.test;

import com.hspedu.spring.bean.Monster;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class JdbcTemplateTest {

    //查詢返回結果只有一行一列的值,比如查詢id=100的怪物名
    /**
     * 查詢返回結果只有一行一列的值
     */
    @Test
    public void selectScalarByJdbcTemplate() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到JdbcTemplate bean
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

        //1. 確定API
        //public <T> T queryForObject(String sql, Class<T> requiredType)
        //2. 提供引數
        String sql = "SELECT NAME FROM monster WHERE id = 100";
        //Class<T> requiredType 表示你返回的單行單列的資料型別

        String name =
                jdbcTemplate.queryForObject(sql, String.class);
        System.out.println("返回name= " + name);

    }
}

執行結果:

52,JdbcTemplate-具名引數

1. 使用 Map 傳入具名引數完成操作,比如新增 螞蟻精.:name 就是具名引數形式需要使NamedParameterJdbcTemplate

JdbcTemplateTest.java

package com.hspedu.spring.test;

import com.hspedu.spring.bean.Monster;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JdbcTemplateTest {

    //使用Map傳入具名引數完成操作,比如新增 螞蟻精.:name 就是具名引數形式需要使用NamedParameterJdbcTemplate 類,
    // 語句形式: String sql = "INSERT INTO monster VALUES(:my_id, :name, :skill)";
    /**
     * 使用Map傳入具名引數完成操作,比如新增
     */
    @Test
    public void testDataByNamedParameterJdbcTemplate() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到NamedParameterJdbcTemplate bean
        NamedParameterJdbcTemplate namedParameterJdbcTemplate =
                ioc.getBean(NamedParameterJdbcTemplate.class);

        //1. 確定使用API
        //public int update(String sql, Map<String, ?> paramMap)
        //2. 準備引數 [:my_id, :name, :skill] 要求按照規定的名字來設定引數
        String sql = "INSERT INTO monster VALUES(:id, :name, :skill)";
        Map<String, Object> paramMap = new HashMap<>();
        //給paramMap填寫資料
        paramMap.put("id", 800);
        paramMap.put("name", "螞蟻精");
        paramMap.put("skill", "喜歡打洞");
        //3. 呼叫
        int affected = namedParameterJdbcTemplate.update(sql, paramMap);
        System.out.println("add ok affected=" + affected);


    }
}

執行結果:

sqlyog資料庫結果:

53,JdbcTemplate-sqlparametersoruce

1. 使用 sqlparametersoruce 來封裝具名引數,還是新增一個 Monster 狐狸精

JdbcTemplateTest.java

package com.hspedu.spring.test;

import com.hspedu.spring.bean.Monster;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JdbcTemplateTest {

    //使用sqlparametersoruce 來封裝具名引數,還是新增一個Monster 狐狸精

    @Test
    public void operDataBySqlparametersoruce() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到NamedParameterJdbcTemplate bean
        NamedParameterJdbcTemplate namedParameterJdbcTemplate =
                ioc.getBean(NamedParameterJdbcTemplate.class);

        //確定API
        //public int update(String sql, SqlParameterSource paramSource)
        //public BeanPropertySqlParameterSource(Object object)
        //準備引數
        String sql = "INSERT INTO monster VALUES(:monsterID, :name, :skill)";
        Monster monster = new Monster(900, "大象精", "搬運木頭");
        SqlParameterSource sqlParameterSource =
                new BeanPropertySqlParameterSource(monster);
        //呼叫
        int affected =
                namedParameterJdbcTemplate.update(sql, sqlParameterSource);

        System.out.println("add ok affected= " + affected);
    }
}

執行結果:

sqlyog資料庫結果:

54,DAO使用JdbcTemplate完成對資料庫的操作

程式碼結構:

MonsterDao.java

package com.hspedu.spring.jdbctemplate.dao;

import com.hspedu.spring.bean.Monster;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

@Repository //將MonsterDao 注入到spring容器
public class MonsterDao {
    //注入一個屬性
    @Resource
    private JdbcTemplate jdbcTemplate;

    //完成儲存任務
    public void save(Monster monster) {
        //組織sql
        String sql = "INSERT INTO monster VALUES(?,?,?)";
        int affected = jdbcTemplate.update
                (sql, monster.getMonsterID(), monster.getName(), monster.getSkill());
        System.out.println("affected= " + affected);
    }
}

JdbcTemplate_ioc.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置要掃描包-->
    <context:component-scan
            base-package="com.hspedu.spring.jdbctemplate.dao"/>

    <!--引入外部的jdbc.properties檔案-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置資料來源物件-DataSoruce-->
    <bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
        <!--給資料來源物件配置屬性值-->
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.pwd}"/>
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
    </bean>

    <!--配置JdbcTemplate物件-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <!--給JdbcTemplate物件配置屬性dataSource, 注意:後面是引用的上面的資料來源物件dataSource-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置NamedParameterJdbcTemplate物件-->
    <bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"
          id="namedParameterJdbcTemplate">
        <!--透過構造器,設定資料來源-->
        <constructor-arg name="dataSource" ref="dataSource"/>
    </bean>
</beans>

JdbcTemplateTest.java

package com.hspedu.spring.test;

import com.hspedu.spring.bean.Monster;
import com.hspedu.spring.jdbctemplate.dao.MonsterDao;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JdbcTemplateTest {

    //測試MonsterDAO
    @Test
    public void monsterDaoSave() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

        MonsterDao monsterDao = ioc.getBean(MonsterDao.class);
        Monster monster = new Monster(1000, "小鴨精", "吃魚");
        monsterDao.save(monster);
        System.out.println("MonsterDAO儲存 ok ..");
    }
}

執行結果:

sqlyog資料庫結果:

相關文章