Spring容器

大家好呀!我是小笙,接下来我们开始学习spring框架!

Spring Framework

概述

​ Spring 春天 迎来了程序员的春天!

  • 历史由来:Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版
  • 开发者:该框架最初由 Rod Johnson 以及 Juergen Hoeller 等人开发
  • 概念:Spring 又称 Spring Framework 是java平台上免费开源的全栈应用程序框架和控制反转的容器实现,换句话来说就是Spring Framework 建立在java平台上,并为其使用者提供了应用程序框架(类似于房屋的整体架构)以及一系列底层容器(Spring 是管理框架的框架)
  • 目的:解决企业应用开发的复杂性

  • Spring官网 在线文档

  • maven>>Spring Core » 5.3.8:

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.3.8</version>
    </dependency>
    
  • 特征: 轻量 面向切面(AOP) 控制反转(IOC) 容器 框架 MVC

  • 组成:Spring Framework Runtime

    SpringFrameworkRuntime

    Spring七大模块

    image-20211013171033189

  • 优点:

    1. 低侵入式设计,代码污染极低

    2. 独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once ,Run Auywhere的承诺

    3. Spring的DI机制降低了业务对象替换的复杂性,提高了组件之间的解耦

    4. Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式管理,从而提供了更好的复用

    5. Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问

    6. Spring并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部

Spring容器

快速入门

案例:通过 Spring 的方式[配置文件,获取 JavaBean: Monster 的对象,并给该的对象属性赋值,输出该对象信息

引入jar包

image-20220606164516378

Monster类对象

package com.Al_tair.spring.bean;
public class Monster {
    private int monsterId;
    private String name;
    private String skill;
    // 略get,set方法以及构造器
}

配置bean.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">

    <!-- 配置javabean -->
    <!--
        一个bean标签对应一个javabean对象
        通过class全类名用指定类的全路径(用于反射创建javabean对象)
        通过id可以获取到对象(唯一)
    -->
    <bean class="com.Al_tair.spring.bean.Monster" id="monster01">
        <!-- 给该对象属性赋值 -->
        <property name="monsterId" value="1" />
        <property name="name" value="狐狸精" />
        <property name="skill" value="美人计" />
    </bean>
</beans>
public class BeanTeat {
    @Test
    public void getMonster(){
        // 创建容器 ApplicationContext
        // 关联对象的bean.xml文件
        ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
        Monster monster = ioc.getBean("monster01", Monster.class);
        System.out.println(monster);
        // Monster{monsterId=1, name='狐狸精', skill='美人计'}
    }
}

底层分析

注意debug时候将null进行隐藏方便观察

image-20220607133439260

分析bean.xml中存储的类和对象

debug

image-20220607133607296

image-20220607160805087

image-20220607132626976

image-20220607132918718


反问1:ClassPathXmlApplicationContext是如何创建bean容器?

尝试手写超简易版的 MyApplicationContext

思路分析

  1. 通过dom4j解析技术读取bean.xml文件
  2. 解析得到对象的id,类路径,对象的属性
  3. 使用反射来创建该对象
  4. 放入到beanFactory中(管理bean对象的容器)
  5. 提供一个getBean()方法,通过id来获取对应的bean对象
/**
 * 手写超简易版的Spring容器
 */
public class MyApplicationContext {
    /**
     * 存放id-bean对象的键值对
     */
    private static ConcurrentHashMap<String,Object> SingletonObject = new ConcurrentHashMap<>();

    /**
     * 构造器
     * @param iocBeanXmlPath 配置的bean.xml文件的位置
     */
    public MyApplicationContext(String iocBeanXmlPath) {
        // 资源路径
        String path = MyApplicationContext.class.getResource("/").getPath();

        // 1.通过dom4j解析技术读取bean.xml文件
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(new File(path + iocBeanXmlPath));
            Element rootElement = document.getRootElement();

            // 2.解析得到对象的id,类路径,对象的属性
            // 获取第一个bean对象
            Element bean = (Element)rootElement.elements("bean").get(0);
            // 获取群路径和id
            String beanFullClassPath = bean.attribute("class").getValue();
            String id = bean.attribute("id").getValue();
            // 获取属性值
            List<Element> property = bean.elements("property");
            Integer monsterId = null;
            String name = null;
            String skill = null;
            for (Element element : property) {
                // 我这里简化判断,因为只取第一个bean对象,最好也用容器来存储管理
                if("monsterId".equals(element.attributeValue("name"))){
                    monsterId = Integer.parseInt(element.attributeValue("value"));
                }else if("name".equals(element.attributeValue("name"))){
                    name = element.attributeValue("value");
                }else if("skill".equals(element.attributeValue("name"))){
                    skill = element.attributeValue("value");
                }
            }

            // 3.使用反射创建对象
            // 在容器配置文件(比如 beans.xml)中给属性赋值, 底层是通过 setter 方法完成的
            Class<?> monsterClass = Class.forName(beanFullClassPath);
            Monster monster = (Monster)monsterClass.newInstance();
            monster.setMonsterId(monsterId);
            monster.setName(name);
            monster.setSkill(skill);

            // 4.放入到beanFactory中(管理bean对象的容器)
            SingletonObject.put(id,monster);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 5.提供一个getBean()方法,通过id来获取对应的bean对象
    public static Object getBean(String id){
        if(SingletonObject != null &&SingletonObject.containsKey(id)){
            return SingletonObject.get(id);
        }
        return null;
    }
}

测试类

public class MyApplicationContextTeat {
    @Test
    public void test(){
        MyApplicationContext myApplicationContext = new MyApplicationContext("bean.xml");
        Monster monster01 = (Monster)MyApplicationContext.getBean("monster01");
        System.out.println(monster01); // Monster{monsterId=1, name='狐狸精', skill='美人计'}
    }
}

反问2:如果创建两个bean对象,都不写id会出现什么情况?

系统会默认分配id,规则:全类名#n (n是第一个bean对象,从0开始)

<bean class="com.Al_tair.spring.bean.Monster">
    <!-- 给该对象属性赋值 -->
    <property name="monsterId" value="1" />
    <property name="name" value="狐狸精" />
    <property name="skill" value="美人计" />
</bean>

<bean class="com.Al_tair.spring.bean.Monster">
    <!-- 给该对象属性赋值 -->
    <property name="monsterId" value="2" />
    <property name="name" value="猪八戒" />
    <property name="skill" value="九尺钉耙" />
</bean>

测试类

public class BeanTest {
    @Test
    public void getMonster(){
        // 创建容器 ApplicationContext
        // 关联对象的bean.xml文件
        ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
        String[] beanDefinitionNames = ioc.getBeanDefinitionNames();
        System.out.println(Arrays.toString(beanDefinitionNames));
        // 显示 [com.Al_tair.spring.bean.Monster#0, com.Al_tair.spring.bean.Monster#1]
    }
}

Spring 管理 Bean-IOC

概述

Bean管理包括:创建bean对象、给bean注入属性

Bean配置方式:基于xml文件配置方式、基于注解方式

基于xml文件配置方式
获取bean对象

1.通过id获取bean对象

public class MyApplicationContextTeat {
    @Test
    public void test(){
        MyApplicationContext myApplicationContext = new MyApplicationContext("bean.xml");
        Monster monster01 = (Monster)MyApplicationContext.getBean("monster01");
        System.out.println(monster01); // Monster{monsterId=1, name='狐狸精', skill='美人计'}
    }
}

2.通过对象类型来获取该bean对象

@Test
public void getMonsterByType(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
    Car car = ioc.getBean(Car.class);
    System.out.println(car); // Car{id=1, name='奥迪', price='350000'}
}

注意事项:按类型来获取 bean, 要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常 NoUniqueBeanDefinitionException

应用场景:在一个线程中只需要一个对象实例(单例)的情况


配置bean对象

1.通过构造器配置 bean

解读:通过有参构造器的形式创建对象

<!-- 构造器配置bean对象 -->
<bean class="com.Al_tair.spring.bean.Monster" id="monster03">
    <!-- index是按顺序指定的 -->
    <constructor-arg value="100" index="0" />
    <constructor-arg value="孙悟空" index="1" />
    <constructor-arg value="定海神针" index="2" />
</bean>
<bean class="com.Al_tair.spring.bean.Monster" id="monster03">
    <!-- 根据name来配置构造器 -->
    <constructor-arg value="100" name="monsterId" />
    <constructor-arg value="孙悟空" name="name" />
    <constructor-arg value="定海神针" name="skill" />
</bean>
<bean class="com.Al_tair.spring.bean.Monster" id="monster03">
    <!-- 根据tpye来匹配构造器 -->
    <!-- 注意类定义的时候尽量使用Integer(不要使用基本数据类型),不然无法匹配类型 -->
    <constructor-arg value="100" type="java.lang.Integer" />
    <constructor-arg value="孙悟空" type="java.lang.String" />
    <constructor-arg value="定海神针" type="java.lang.String" />
</bean>

2.通过 p 名称空间配置 bean

xmlns:p="http://www.springframework.org/schema/p"
<!--通过 p 名称空间配置 bean-->
<bean class="com.Al_tair.spring.bean.Monster" id="monster05"
      p:monsterId="4"
      p:name="铁扇公主"
      p:skill="芭蕉扇"
/>
@Test
public void getMonsterByPNameSpace(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
    Monster monster05 = ioc.getBean("monster05", Monster.class);
    System.out.println(monster05); // Monster{monsterId=4, name='铁扇公主', skill='芭蕉扇'}
}

3.通过 reference 来实现 bean 对象的相互引用

解读:属性中引用了其他 bean 对象

依赖注入

<!--通过 reference 来实现 bean 对象的相互引用-->
<bean class="com.Al_tair.spring.dao.MemberDAOImpl" id="memberDAO"/>
<bean class="com.Al_tair.spring.service.MemberServiceImpl" id="memberService">
    <!--ref 表示memberDAO这个属性将引用/指向 id = memberDAOImpl 对象-->
    <property name="memberDAO" ref="memberDAO"></property>
</bean>

测试类

@Test
public void getMonsterByRef() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
    MemberServiceImpl memberService = (MemberServiceImpl) ioc.getBean("memberService");
    memberService.add();
}

4.引用/注入内部 bean 对象

解读:用法类似匿名内部类的方式

<bean class="com.Al_tair.spring.service.MemberServiceImpl" id="memberService">
    <!--直接在内部配置内部bean对象-->
    <property name="memberDAO">
        <bean class="com.Al_tair.spring.dao.MemberDAOImpl"/>
    </property>
</bean>

5.引用/注入集合/数组类型

Master类用来管理monster类

public class Master {
    private String name;
    private List<Monster> monsterList;
    private Map<String,Monster> monsterMap;
    private Set<Monster> monsterSet;
    private String[] monstersName;
    // Properties是Hashtable的子类,是key-value的形式
    private Properties pros;
    // ......等等
}
<!--引用/注入集合/数组类型-->
<bean class="com.Al_tair.spring.bean.Master" id="master">
    <!--name-->
    <property name="name" value="太上老君"/>
    <!--List-->
    <property name="monsterList">
        <list>
            <ref bean="monster01"/>
            <ref bean="monster02"/>
            <ref bean="monster03"/>
        </list>
    </property>
    <!--Map-->
    <property name="monsterMap">
        <map>
            <entry key="monster4" value-ref="monster04" />
            <entry key="monster5" value-ref="monster05"/>
        </map>
    </property>
    <!--Set-->
    <property name="monsterSet">
        <set>
            <ref bean="monster01"/>
            <ref bean="monster02"/>
        </set>
    </property>
    <!--数值-->
    <property name="monstersName">
        <array>
            <value>小妖怪</value>
            <value>大妖怪</value>
        </array>
    </property>
    <!--Properties-->
    <!-- key-value均是字符串 -->
    <property name="pros">
        <props>
            <prop key="key01">value01</prop>
            <prop key="key02">value02</prop>
            <prop key="key03">value03</prop>
        </props>
    </property>
</bean>

6.通过 util 名称空间创建 list集合

目的是为了减少代码的复用性,提取公共的参数

public class BookStore {
    private List<String> bookList;
	// ....等等
}
<!--通过 util 名称空间创建 list集合-->
<util:list id="myBookStore">
    <value>三国演义</value>
    <value>红楼梦</value>
    <value>水浒传</value>
    <value>西游记</value>
</util:list>
<bean class="com.Al_tair.spring.bean.BookStore" id="bookStore">
    <property name="bookList" ref="myBookStore"/>
</bean>

测试类

@Test
public void bookBeanTest(){
    ApplicationContext ios = new ClassPathXmlApplicationContext("bean.xml");
    BookStore bookStore = ios.getBean("bookStore", BookStore.class);
    System.out.println(bookStore); // BookStore{bookList=[三国演义, 红楼梦, 水浒传, 西游记]}
}

7.级联属性赋值

spring 的 ioc 容器, 可以直接给对象属性的属性赋值, 即级联属性赋值

<!--级联属性赋值-->
<bean class="com.Al_tair.spring.bean.Dept" id="dept"/>
<bean class="com.Al_tair.spring.bean.Emp" id="emp">
    <property name="name" value="Al_tair"/>
    <property name="dept" ref="dept"/>
    <!-- 给对象属性的属性赋值 -->
    <property name="dept.name" value="Java开发"/>
</bean>

测试类

@Test
public void test(){
    ApplicationContext ios = new ClassPathXmlApplicationContext("bean.xml");
    Emp emp = ios.getBean("emp", Emp.class);
    System.out.println(emp); // Emp{name='Al_tair', dept=Dept{name='Java开发'}}
}

8.通过静态工厂获取对象

工厂类

public class MyStaticFactory {
    private static Map<String, Monster> monsterMap;
    static {
        monsterMap = new HashMap<String, Monster>();
        monsterMap.put("monster_01", new Monster(1, "一灯大师", "一阳指"));
        monsterMap.put("monster_02", new Monster(2, "如来佛主", "如来神掌"));
    }
    public static Monster getMonster(String key) {
        return monsterMap.get(key);
    }
}
<!--通过静态工厂获取对象-->
<bean class="com.Al_tair.spring.factory.MyStaticFactory" id="myStaticFactory" factory-method="getMonster">
    <!--方法中传入的实参-->
    <constructor-arg value="monster_02"/>
</bean>

测试类

@Test
public void myStaticFactoryTest(){
    ApplicationContext ios = new ClassPathXmlApplicationContext("bean.xml");
    Monster myStaticFactory = ios.getBean("myStaticFactory", Monster.class);
    System.out.println(myStaticFactory); // Monster{monsterId=2, name='如来佛主', skill='如来神掌'}
}

9.通过实例工厂获取对象

public class MyInstanceFactory {
    private  Map<String, Monster> monsterMap;
    {
        monsterMap = new HashMap<String, Monster>();
        monsterMap.put("monster_03", new Monster(3, "牛魔王", "芭蕉扇"));
        monsterMap.put("monster_04", new Monster(4, "孙悟空", "金箍棒"));
    }
    public  Monster getMonster(String key) {
        return monsterMap.get(key);
    }
}
<!--通过实例工厂获取对象-->
<!-- 初始化该实例 -->
<bean class="com.Al_tair.spring.factory.MyInstanceFactory" id="instanceFactory"/>
<!--factory-bean 指定使用哪个实例工厂对象(事先要创建好该实例工厂对象)-->
<!--factory-method 调用该实例工厂对象的哪个方法-->
<!--constructor-arg 该方法调用所需要的参数-->
<bean id="myInstanceFactory" factory-bean="instanceFactory" factory-method="getMonster">
    <constructor-arg value="monster_04"/>
</bean>

测试类

@Test
public void myInstanceFactoryTest(){
    ApplicationContext ios = new ClassPathXmlApplicationContext("bean.xml");
    Monster myInstanceFactory = ios.getBean("myInstanceFactory", Monster.class);
    System.out.println(myInstanceFactory); // Monster{monsterId=4, name='孙悟空', skill='金箍棒'}
}

10.通过 FactoryBean 获取对象

public class FactoryBeanImpl implements FactoryBean<Monster> {
    // 获取指定对象的key值
    private String keyVal;

    private Map<String, Monster> monster_map;

    {
        monster_map = new HashMap<String, Monster>();
        monster_map.put("monster_01", new Monster(100, "黄袍怪", "一阳指"));
        monster_map.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
    }

    public void setKeyVal(String keyVal) {
        this.keyVal = keyVal;
    }

    @Override
    public Monster getObject() throws Exception {
        return this.monster_map.get(keyVal);
    }

    @Override
    public Class<?> getObjectType() {
        return Monster.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}
<!--通过 FactoryBean 获取对象-->
<bean class="com.Al_tair.spring.factory.FactoryBeanImpl" id="factoryBean">
    <property name="keyVal" value="monster_02"/>
</bean>

测试类

@Test
public void FactoryBeanTest(){
    ApplicationContext ios = new ClassPathXmlApplicationContext("bean.xml");
    Monster factoryBean = ios.getBean("factoryBean", Monster.class);
    System.out.println(factoryBean); // Monster{monsterId=200, name='九头金雕', skill='如来神掌'}
}

11.bean 配置信息重用(继承)

<!--通过 FactoryBean 获取对象-->
<bean class="com.Al_tair.spring.factory.FactoryBeanImpl" id="factoryBean">
    <property name="keyVal" value="monster_02"/>
</bean>
<!--继承父类,同时可以修改属性-->
<bean id="factoryBeanSon" parent="factoryBean">
    <property name="keyVal" value="monster_01"/>
</bean>

测试类

@Test
public void FactoryBeanSonTest(){
    ApplicationContext ios = new ClassPathXmlApplicationContext("bean.xml");
    Monster factoryBean = ios.getBean("factoryBeanSon", Monster.class);
    System.out.println(factoryBean); // Monster{monsterId=100, name='黄袍怪', skill='一阳指'}
}

Bean对象创建的顺序

默认按从上到下的顺序进行创建(先创建Bean对象,再建立对象之间的引用)

<bean id="student01" class="com.hspedu.bean.Student" /> 
<bean id="department01" class="com.hspedu.bean.Department" />

可以通过设置属性 depends-on 来控制创建的顺序

<bean id="student01" class="com.hspedu.bean.Student" depends-on="department01"/> 
<bean id="department01" class="com.hspedu.bean.Department" />

Bean 对象的单例和多例

ioc容器, 默认是按照单例创建的,即配置一个 bean对象后,ioc 容器只会创建一个 bean实例

ioc 容器配置的某个 bean 对象,是以多个实例形式创建的则可以通过配置 scope=“prototype” 来指定

<!--Bean 对象的单例-->
<!--scope="singleton"-->
<bean class="com.Al_tair.spring.bean.Car" id="car01">
    <!-- 给该对象属性赋值 -->
    <property name="id" value="1" />
    <property name="name" value="奥迪" />
    <property name="price" value="350000" />
</bean>

<!--Bean 对象的多例-->
<!--scope="prototype"-->
<bean class="com.Al_tair.spring.bean.Car" id="car02" scope="prototype">
    <!-- 给该对象属性赋值 -->
    <property name="id" value="2" />
    <property name="name" value="宝马" />
    <property name="price" value="260000" />
</bean>

测试类

// 单例模式
@Test
public void test(){
    ApplicationContext carIos = new ClassPathXmlApplicationContext("bean.xml");
    Car car01 = carIos.getBean("car01", Car.class);
    Car car02 = carIos.getBean("car01", Car.class);
    System.out.println(car01 == car02); // true
}
// 多例
@Test
public void carTest(){
    ApplicationContext carIos = new ClassPathXmlApplicationContext("bean.xml");
    // 每次返回一个新的对象
    Car car01 = carIos.getBean("car02", Car.class);
    Car car02 = carIos.getBean("car02", Car.class);
    System.out.println(car01 == car02); // false
}

注意细节

  • 默认是单例 singleton,在启动容器时, 默认就会创建,并放入到 singletonObjects 集合;如果希望在 getBean 时才创建 , 可以指定懒加载 lazy-init=“true” (默认是 false)
  • 当 < bean scope=“prototype” > 设置为多实例机制后, 该 bean 是在 getBean()时才创建

Bean 的生命周期

bean 对象创建是由 JVM 来完成的,执行顺序如下

  1. 执行构造器,创建bean对象
  2. 执行 setXxxx 相关的方法
  3. 调用 bean 对象的初始化方法(需要配置)
  4. 通过 getBean() 获取Bean对象并使用
  5. 当容器关闭时候,调用 Bean 对象的销毁方法(需要配置)

House类

public class House {
    private String name;

    public House() {
        System.out.println("House()构造器");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("House setName()...");
        this.name = name;
    }

    // 不是固定init名字,需要配置为初始化方法
    public void init() {
        System.out.println("House init()..");
    }

    // 不是固定destory名字,需要配置为销毁对象
    public void destory() {
        System.out.println("House destory()..");
    }

    @Override
    public String toString() {
        return "House{" +
                "name='" + name + '\'' +
                '}';
    }
}

配置bean对象

<!--Bean 的生命周期-->
<bean class="com.Al_tair.spring.bean.House" id="house" init-method="init" destroy-method="destory">
    <property name="name" value="豪宅"/>
</bean>

测试类

@Test
public void beanHouse(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
    House house = ioc.getBean("house", House.class);
    System.out.println(house);
    // 销毁Bean容器
    ((ConfigurableApplicationContext)ioc).close();
    // 控制台输出结果
    // 1.House()构造器
    // 2.House setName()...
    // 3.House init()..
    // 4.House{name='豪宅'}
    // 5.House destory()..
}

配置 Bean 的后置处理器

该处理器/对象会在 bean 初始化方法调用前和调用后被调用,这个时候 Bean 对象已经创建好了

后置处理器操作需要实现 BeanPostProcessor 接口

/**
 * 后置处理器
 * 初始化前后传入的Bean对象是同一个
 * 可以对 IOC 容器中所有的对象进行统一处理,比如日志处理/权限的校验/安全的验证/事务管理
 */
public class MyBeanPostProcessor implements BeanPostProcessor {
    /**
     * 在Bean对象初始化之前调用
     * 对传入的Bean对象可以进行对应业务的操作 如日志记录
     * @param bean bean 对象
     * @param beanName bean 对象对应的 id
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化前面");
        return null;
    }

    /**
     * 在Bean对象初始化之后调用
     * @param bean bean 对象
     * @param beanName bean 对象对应的 id
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化后面");
        return null;
    }
}

配置 Bean 的后置处理器

<bean class="com.Al_tair.spring.bean.House" id="house" init-method="init" destroy-method="destory">
    <property name="name" value="豪宅"/>
</bean>
<!--配置 Bean 的后置处理器-->
<bean class="com.Al_tair.spring.bean.MyBeanPostProcessor" id="myBeanPostProcessor"/>

测试类

@Test
public void MyBeanPostProcessorByHouse(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("bean2.xml");
    House house = ioc.getBean("house", House.class);
    System.out.println(house);
    ((ConfigurableApplicationContext)ioc).close();
    // 执行顺序
    // House()构造器
    // House setName()...
    // 初始化前面
    // House init()..
    // 初始化后面
    // House{name='豪宅'}
    // House destory()..
}

通过属性文件给 bean 注入值

配置文件

monsterId=10
name=tom
skill=ball
# 如果存在中文 需要进行unicode编码
# 在线转换工具: http://www.esjson.com/unicodeEncode.html

读取配置文件并注入到 Bean 对象

<context:property-placeholder location="classpath:my.properties"/>
<bean class="com.Al_tair.spring.bean.Monster" id="monster">
    <property name="monsterId" value="${monsterId}"/>
    <property name="name" value="${name}"/>
    <property name="skill" value="${skill}"/>
</bean>

测试类

// 通过属性文件给 bean 注入值
@Test
public void getMonsterFromProperties(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("bean2.xml");
    Monster monster = ioc.getBean("monster", Monster.class);
    System.out.println(monster); // Monster{monsterId=10, name='tom', skill='ball'}
}

Bean 的自动装配

autowire=“byType”

条件:在这个Bean容器中不能有两个相同的类对象

<!--Bean 的自动装配-->
<!--OrderDao-->
<bean class="com.Al_tair.spring.dao.OrderDao" id="orderDao"/>
<!--OrderService-->
<bean class="com.Al_tair.spring.service.OrderService" id="orderService" autowire="byType"/>
<!--OrderServlet-->
<bean class="com.Al_tair.spring.controller.OrderServlet" id="OrderServlet" autowire="byType"/>

测试类

@Test
public void ServletTest(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("bean2.xml");
    OrderServlet orderServlet = ioc.getBean("OrderServlet", OrderServlet.class);
    OrderService orderService = orderServlet.getOrderService();
    OrderService orderService2 = ioc.getBean("orderService", OrderService.class);
    System.out.println(orderService == orderService2); // true
}

autowire=“byName”

<!--OrderDao-->
<bean class="com.Al_tair.spring.dao.OrderDao" id="orderDao"/>
<!--OrderService-->
<bean class="com.Al_tair.spring.service.OrderService" id="orderService" autowire="byName"/>
<!--OrderServlet-->
<!--会根据 OrderServlet 中的 setOrderService 方法来查找对应的 id 的 bean 对象进行自动装配-->
<bean class="com.Al_tair.spring.controller.OrderServlet" id="OrderServlet" autowire="byName"/>
基于注解配置 bean
概述

概念:基于注解的方式配置 bean, 主要是项目开发中的组件,比如 Controller、Service、 Dao

  1. @Component 表示当前注解标识的是一个组件
  2. @Controller 表示当前注解标识的是一个控制器,通常用于 Servlet
  3. @Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于 Service 类
  4. @Repository 表示当前注解标识的是一个持久化层的类,通常用于 Dao 类

首先导入 spring-aop-5.3.8.jar

快速入门
@Repository
public class UserDao {}
@Service
public class UserService {}
@Controller
public class UserServlet {}
@Component
public class MyComponent {}

配置扫描包的配置类

<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.Al_tair.springComponent.other" />
<!--
   可以使用通配符*来指定 ,比如com.Al_tair.springComponent.*表示
   component-scan  对指定的包进行扫描,并创建对象放入到容器
   base-package 指定要扫面的包,但是该包中的类必须用注解 @Component/@Controller/@Service/@Repository 标注
   resource-pattern 表示扫描以User开头的类名的类
-->
<context:component-scan base-package="com.Al_tair.springComponent.*" resource-pattern="User*.class"/>

测试类

@Test
public void setBeanByAnnotation(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("bean3.xml");
    UserDao userDao = ioc.getBean(UserDao.class);
    UserService userService = ioc.getBean(UserService.class);
    UserServlet userServlet = ioc.getBean(UserServlet.class);
    MyComponent myComponent = ioc.getBean(MyComponent.class);
    System.out.println(userDao);
    System.out.println(userService);
    System.out.println(userServlet);
    System.out.println(myComponent);
    //com.Al_tair.springComponent.dao.UserDao@1e683a3e
    //com.Al_tair.springComponent.service.UserService@2053d869
    //com.Al_tair.springComponent.controller.UserServlet@7a419da4
    //com.Al_tair.springComponent.other.MyComponent@14555e0a
}

提问:指定扫描某个包,会不会去扫描该包下的子包中的类

会的,会扫描所有该包中的类,也会扫描该子包下的类

注意细节

Spring 的 IOC 容器不能检测一个使用了@Controller 注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件。其它的@Service @Repository 也是一样的道理 [也就是说 spring 的 IOC 容器只要检查到注解就会生成对象, 但是这个注解的含义 spring 不会识别,注解是给程序员编程方便看的,但是在springMVC中有细微的区别]

排除某些注解类

<!--补充:排除哪些类,以annotaion注解为例-->
<!--type:指定排除的类型 annotation表示以注解的方式进进行排除-->
<!--expression="org.springframework.stereotype.Service" Service的全路径,用来指定排除Service注解的类-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>

指定自动扫描哪些注解类

<!-- 
	1. use-default-filters="false": 表示不使用默认的过滤机制 
	2. context:include-filter: 表示只是扫描指定的注解的类
 	3. expression="org.springframework.stereotype.Controller": 注解的全类名 
-->
<context:component-scan base-package="com.Al_tair.springComponent.*" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>

默认情况:标记注解后,类名首字母小写作为 id 的值; 也可以使用注解的 value 属性指定 id 值(value 可以省略)

@Repository
public class UserDao {}
// value 可以省略,可以写成 @Service("serviceValue")
@Service(value = "serviceValue") 
public class UserService {}

测试类

@Test
public void setBeanById(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("bean3.xml");
    // 类名首字母小写作为id的值
    UserDao userDao = ioc.getBean("userDao",UserDao.class);
    // 但是如果指定了value值的话,id就不能将类名首字母小写作为id的值
    // UserService userService = ioc.getBean("userService",UserService.class); error
    // 使用注解的 value 属性指定 id 
    UserService userService = ioc.getBean("serviceValue",UserService.class);
    System.out.println(userDao);
    System.out.println(userService);
    //com.Al_tair.springComponent.dao.UserDao@33990a0c
    //com.Al_tair.springComponent.service.UserService@4de4b452
}

手动开发简单的 Spring 基于注解配置的程序

思路分析

对比原生开发和手动开发的区别

image-20220612150203837

自定义注解类

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

作用类似于bean.xml:指定需要扫描的包

@ComponentScan(value = "com.Al_tair.springComponent")
public class SpringConfig {
}

自定义ioc容器

/**
 * 作用类似于Spring的原生ioc容器
 */
public class MyApplicationContext {
    private Class springConfig;
    private final ConcurrentHashMap<String,Object> ioc = new ConcurrentHashMap<>();

    public MyApplicationContext(Class springConfig) {
        this.springConfig = springConfig;
        // 1.获取扫描包的全路径
        ComponentScan componentScan = (ComponentScan)this.springConfig.getDeclaredAnnotation(ComponentScan.class);
        String ScanPackage = componentScan.value().replace('.','/');

        // 2.得到类加载器
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(ScanPackage);
        File file = new File(resource.getFile());
        List<String> fullPath = new ArrayList<>();
        
        // 3.扫描包并保存class文件的全类名
        if(file.isDirectory()){
            File[] files = file.listFiles();
            for (File fileItem : files) {
                File[] f = fileItem.listFiles();
                for (File fileClass : f) {
                    String absoluterPath = fileClass.getAbsolutePath();
                    if(absoluterPath.endsWith(".class")){
                        String substring = absoluterPath.substring(absoluterPath.lastIndexOf("\\com") + 1, absoluterPath.indexOf(".class"));
                        String str = substring.replace("\\",".");
                        fullPath.add(str);
                    }
                }
            }
        }

        // 4.遍历所有class文件,进行反射实例化
        for (String s : fullPath) {
            try {
                Class<?> aClass = classLoader.loadClass(s);
                if(aClass.isAnnotationPresent(Component.class) || aClass.isAnnotationPresent(Service.class) ||
                   aClass.isAnnotationPresent(Repository.class) || aClass.isAnnotationPresent(Controller.class)){
                    Class<?> clazz = Class.forName(s);
                    Object o = clazz.newInstance();
                    // 5.放入ioc容器,id为类名首字母小写完成
                    ioc.put(s.substring(s.lastIndexOf(".") + 1,s.lastIndexOf(".") + 2).toLowerCase()
                            + s.substring(s.lastIndexOf(".") + 2),o);
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 6.通过getBean方法给外界来获取该bean对象
    public Object getBean(String id){
        if(ioc.containsKey(id)){
            return ioc.get(id);
        }else{
            return null;
        }
    }
}

测试类

@Test
public void configTest(){
    MyApplicationContext ioc = new MyApplicationContext(SpringConfig.class);
    Object userDao = ioc.getBean("userDao");
    System.out.println(userDao); // com.Al_tair.springComponent.dao.UserDao@7dc36524
}

自动装配注解类
@AutoWried

规则:

在Ioc容器中查找待装配的组件的类型,如果有唯一的bean的类型,则使用该bean进行装配

@Autowired
private UserService userservice;

注意:

  • 在注入的过程中,扫描到公共方法中要注入的bean,并未找到,强行注入就会注入失败。我们又不能单独的去除改方法,所以我们采取的思想就是有bean就注入,没有就不注入。解决办法就是@Autowired(required=false)

  • @Autowired注释的注入点的类型相同。否则,注入可能会因运行时“未找到类型匹配”错误而失败,如果遇到多个同类型的将无法实现注入,我们就需要用到**@Qualifier**来进行指定名字的注入,例子如下

    @Autowired
    @Qualifier(value = "userservice2")
    private UserService userservice;
    
@Resource

需要导入注解包

image-20220620225708893

  1. @Resource(name = “serviceValue”) 表示装配 id = serviceValue 的 bean对象
  2. @Resource(type= UserService.class) 表示按照 UserService 类型进行装配,这要求容器中的该 bean对象必须是单例对象
  3. @Resource 如果没有指定 name 或者 type,默认先使用 id = bean对象首字母小写来进行装配,如果不成功则使用类型进行装配,如果都不成功则报异常
public @interface Resource {
    String name() default "";
    Class<?> type() default Object.class;
    // 部分源码...
}

注意:建议选择@Resource进行装配

自动注解 Vs Xml配置
  • xml更加强大,适用于任何的范围,维护简单!
  • 自动注解适用范围有限,且维护麻烦,分布在各个类

个人觉得最优操作(限于Spring):

  • xml用来创建bean

  • 自动注解用来进行属性的注入

    但是要记住需要开启注解的支持


泛型依赖注入
  1. 为了更好的管理有继承和相互依赖的 bean 的自动装配,spring 还提供基于泛型依赖的 注入机制
  2. 在继承关系复杂情况下,泛型依赖注入就会有很大的优越性

image-20220621224429494

实体类

public class Phone {}
public class Book {}

Dao层

@Repository
public abstract class BaseDao<T> {
    public abstract void save();
}
@Repository
public class PhoneDao extends BaseDao<Phone>{
    @Override
    public void save() {
        System.out.println("PhoneDao");
    }
}
@Repository
public class BookDao extends BaseDao<Book>{
    @Override
    public void save() {
        System.out.println("BookDao");
    }
}

Service层

@Service
public abstract class BaseService<T> {
    @Autowired
    private BaseDao<T> baseDao;
    public void save(){
        baseDao.save();
    }
}
@Service
public class BookService extends BaseService<Book>{}
@Service
public class PhoneService extends BaseService<Phone>{}

测试

@Test
public void setProByAutowried(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("bean4.xml");
    PhoneService bean = ioc.getBean(PhoneService.class);
    bean.save(); // PhoneDao
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Al_tair

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值