Spring5之IOC操作Bean管理(基于xml和注解)

目录

前言

一、Spring的核心

二、Spring的特点

三、Spring5的主要内容

四、IOC底层

1、IOC底层的技术

2、IOC接口

五、IOC操作Bean管理(基于xml配置文件)

1、bean标签创建对象说明

2、基于xml配置文件进行属性注入的方式

3、基于xml配置文件进行属性注入 

4、FactoryBean 

5、bean的作用域

6、bean生命周期

7、xml自动装配

8、外部属性文件

六、IOC操作Bean管理(基于注解的方式) 


前言

前几天Spring框架曝出RCE 0day漏洞,可导致远程代码执行 (RCE),使用JDK9及以上版本都有可能受到影响,还好博主用的是稳定的JDK8(JDK8真的是yyds)。本篇文章主要介绍一下spring核心之一的IOC/DI以及他们是如何通过XML和注解的方式实现Bean管理。天不生我小码农,码道万古如长夜,码字不易,感谢支持,当然可能有一些理解不到位的地方,欢迎指正。

一、Spring的核心

Spring是轻量级的开源JavaEE框架,Spring主要用到的设计模式有工厂模式和代理模式,一般而言,工厂模式分为三类:简单工厂模式(Simple Factory)、工厂方法模式(Factory Method)、抽象工厂模式(Abstract Factory)。所有的框架主要用于解耦和快速开发,可以理解为框架=注解+反射+设计模式。Spring 有两个核心部分:IOC和AOP(这也是Spring面试常问的两个东西)。

1、IOC:控制反转,把对象创建和对象之间的调用过程交给Spring进行管理(DI和IOC是同一个概念的不同角度描述,控制反转是一种编程思想,依赖注入是实现控制反转的典型方法或者说DI是IOC的具体实现,DI需要在创建对象的基础上实现,给对象的属性进行赋值)。控制:包括对象的创建,属性的赋值以及对象之间关系的管理。反转:把原来由开发人员创建、管理对象的权限交给第三方即代码之外的spring容器进行实现,在JavaSE中,当我们缺少对象时,我们可以用关键字new一个对象,这也可以称为正转。

2、AOP:面向切面,不修改源代码的情况下增强功能(原理:动态代理技术,设计模式就是代理加装饰器)。

二、Spring的特点

1、方便解耦,简化开发

2、AOP编程支持

3、方便程序的测试(可以使用Junit测试单元)

4、方便和其他优秀的框架进行整合(比如博主以前所写的SSM集成框架,就是Spring集成了Mybatis等优秀框架,当然还有过时的SSH框架:Spring + Struts +Hibernate,以前老会开玩笑说学完春天学冬天,现在也用不上冬天了)

5、方便进行事务的操作

6、降低API的开发难度

三、Spring5的主要内容

博主用的是5.2.5版本的,但官网已经更新到了5.3.18版本了 

1、IOC容器

2、AOP

3、jdbcTemplate

4、事务管理

5、Spring 5新特性:Webflux(响应式编程及函数式编程)、函数式注册对象、Nullable注解等。

四、IOC底层

1、IOC底层的技术

IOC思想是基于IOC容器完成的,IOC容器的底层就是Object Factories(对象工厂,工厂顾名思义是用来生产产品的,利用对象工厂,我们创建对象时就可以得到解脱)。IOC底层技术包括xml解析、工厂模式、反射、注解。

2、IOC接口

Spring提供IOC容器的实现有两种方式(两个接口)

BeanFactory:IOC容器的基本实现,是Spring内部的使用接口,不提供开发人员进行使用。加载配置文件时不会创建对象,只有在获取或者使用对象时才去加载对象(懒汉式)。

ApplicationContext:继承了BeanFactory接口,是Beanactory接口的子接口,它提供了更多更强大的功能,一般由开发人员使用,在加载配置文件的时候就会对对象进行创建。(饿汉式)

ApplicationContext接口有实现类,其中有两种是加载配置文件的具体实现类

 对比源码会发现两者在前面的代码基本一致,主要是体现在后面加载配置文件的方式上,FileSystemXmlApplicationContext加载绝对路径的配置文件,ClassPathXmlApplicationContext加载类路径的配置文件。

 protected Resource getResourceByPath(String path) {
        if (path.startsWith("/")) {
            path = path.substring(1);
        }

        return new FileSystemResource(path);
    }
public ClassPathXmlApplicationContext(String path, Class<?> clazz) throws BeansException {
        this(new String[]{path}, clazz);
    }

    public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz) throws BeansException {
        this(paths, clazz, (ApplicationContext)null);
    }

    public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, @Nullable ApplicationContext parent) throws BeansException {
        super(parent);
        Assert.notNull(paths, "Path array must not be null");
        Assert.notNull(clazz, "Class argument must not be null");
        this.configResources = new Resource[paths.length];

        for(int i = 0; i < paths.length; ++i) {
            this.configResources[i] = new ClassPathResource(paths[i], clazz);
        }

        this.refresh();
    }

    @Nullable
    protected Resource[] getConfigResources() {
        return this.configResources;
    }

五、IOC操作Bean管理(基于xml配置文件

Bean管理指的是两个操作,一为Spring创建对象,二为Spring注入属性(有构造器注入和set注入)。Bean管理操作有两种方式,一种是基于xml配置文件,另外一种是基于注解。

1、bean标签创建对象说明

在Spring配置文件中,使用bean标签,标签里可以添加对应的属性,就可以实现对象的创建了。(1)bean标签的属性

id属性:唯一标识,自定义对象名称(可以没有,spring可以提供默认名称),后面获取配置的创建对象时,需要对应上

class属性:类的全限定名称,spring通过反射机制创建对象,不能是接口

spring根据id,class创建对象,并把对象放入到spring的一个map对象 中,map.put(id,对象)

(2)在创建对象的时候,默认执行的是无参构造方法,之所以会使用无参构造方法,是因为反射底层调用的是newInstance(),该方法中没有参数。如何进行验证呢?我们在Books类中写一个带参构造方法,学过java基础的都知道,java在开发者未指定构造方法时,会自动创建一个默认的无参构造方法,当写入一个带参构造方法时,默认的无参构造便会销毁,需要手动创建。

 当不创建无参构造方法时,运行代码,会报 "No default constructor found"的错误,侧面验证了上述观点。

2、基于xml配置文件进行属性注入的方式

属性注入的方式有两种,一为set注入(也叫设值注入),二为构造注入(用的比较少)

(1)set注入

通过set方法对属性进行赋值,可以对简单数据类型进行set注入,也可以对引用数据类型进行set注入。像下面例子,是对引用类型进行注入,UserServiceImpl实现类中注入userDao对象,name属性:类里面的属性名称,ref属性:创建userDao对象bean标签的id值。

 <bean id="userService" class="com.service.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean id="userDao" class="com.dao.UserDaoImpl"/>

普通类型的set注入, 使用property标签完成属性的注入,name表示属性的名字,value表示要注入的值。

<bean id="books" class="com.pojo.Books">
        <property name="bookId" value="12"/>
        <property name="bookName" value="西游记"/>
    </bean>

(2) 构造注入

这里主要指使用有参构造方法进行属性的注入,使用constructor-arg标签,使用该标签可以不写无参构造方法。

<bean id="student" class="com.pojo.Student">
        <constructor-arg name="id" value="10"/>
        <constructor-arg name="name" value="老王"/>
    </bean>
package com.pojo;
/*使用有参构造注入*/
public class Student {
    //两个属性,name和id
    private String name;
    private int id;
    /*public Student(){
    }*/
    //有参构造
    public Student(int id,String name){
        this.id=id;
        this.name=name;
    }
    public void getStudent(){
        System.out.println(id+","+name);
    }
}
@Test
    public void testAllAug() {
        //加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        //获取所配置的对象
        Student student = context.getBean("student", Student.class);
        System.out.println(student);
        student.getStudent();

3、基于xml配置文件进行属性注入 

(1)注入属性--外部bean

创建两个类service类和dao类,并在service里调用dao里面的方法,可以注意到在创建对象的过程中,两个bean标签的位置是并排的,只是将要注入的对象bean标签的id引入被注入的对象中。

package com.dao;

public class UserDaoImpl implements UserDao {

    @Override
    public void update() {
        System.out.println("dao执行更新操作");
    }
}
package com.service;

import com.dao.UserDao;

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("service执行add方法");
        userDao.update();
    }
    //创建UserDao类型属性,生成set方法
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
<?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">
    <!--service和dao对象创建-->
    <bean id="userService" class="com.service.UserServiceImpl">
        <!--注入userDao对象
        name属性:类里面的属性名称
        ref属性:创建userDao对象bean标签的id值
        -->
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean id="userDao" class="com.dao.UserDaoImpl"/>

</beans>

(2)注入属性--内部bean

需要建立一对多的关系来说明,假设现在有员工和部门两个类,一个部门可以有多个员工,而一个员工只属于一个部门。可以看出一个类有多个属性,包括基本类型属性和引用对象引用,对于引用类型的属性使用内部bean进行属性注入,这个过程有点像Mybatis按结果嵌套查询。

package com.bean;

//员工类

public class Employees {

    private String eName;
    private String gender;
    //员工属于某一个部门,使用对象形式表示
    private Department department;
    public void setEName(String eName) {
        this.eName = eName;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }
    //生成department的get方法

    public Department getDepartment() {
        return department;
    }

    public void add(){
        System.out.println(eName+","+gender+","+department);
    }
}
<!--内部bean-->
    <bean id="emp" class="com.bean.Employees">
        <!--先设置两个基本属性-->
        <property name="EName" value="Jack"/>
        <property name="gender" value="男"/>
        <!--设置对象属性-->
        <property name="department">
            <bean class="com.bean.Department">
                <property name="departmentName" value="财务部"/>
            </bean>
        </property>
    </bean>

(3)注入属性--级联赋值

何为级联操作?指的是多个对象之间的映射关系,级联操作在引入外部bean的同时,给外部bean设置了属性,下面用表达式的形式,直接引用外部bean的属性。该方法必须在Employees类中生成Department属性的get方法,上面实体类中已有说明,如没有会报下列错误 ,第一行的refresh方法,读过源码的都知道,这个方法出现的频率很高,是spring的核心启动方法。

<?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">
    <bean id="emp" class="com.bean.Employees">
        <property name="EName" value="老王"/>
        <property name="gender" value="女"/>
        <property name="department" ref="dpt"/>
        <property name="department.departmentName" value="技术部"/>
    </bean>
    <bean id="dpt" class="com.bean.Department">
        <property name="departmentName" value="星星点灯"/>
    </bean>
</beans>

(4)注入属性--集合属性

可以注入数组类型、List集合、Map集合、set类型的属性,在集合里面设置对象类型值

<?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">
    <bean id="stu" class="com.Student">
        <!-- 1、数组类型属性注入-->
        <property name="courses">
            <array>
                <value>Java课程</value>
                <value>Android开发</value>
            </array>
        </property>
        <!-- 2、list类型属性注入-->
        <property name="list">
            <list>
                <value>王富贵</value>
                <value>老王</value>
            </list>
        </property>
        <!-- 3、map类型属性注入-->
        <property name="maps">
            <map>
                <entry key="JAVA" value="java"/>
                <entry key="PHP" value="php"/>
            </map>
        </property>
        <!-- 4、set类型属性注入-->
        <property name="sets">
            <set>
                <value>Mysql</value>
                <value>Redis</value>
            </set>
        </property>
        <!--注入list集合类型,值是对象-->
        <property name="courseList">
            <list>
                <ref bean="course1"/>
                <ref bean="course2"/>
            </list>
        </property>
    </bean>
    <!--创建多个course对象-->
    <bean id="course1" class="com.Course">
        <property name="courseName" value="我爱Java"/>
    </bean>
    <bean id="course2" class="com.Course">
        <property name="courseName" value="我爱C++"/>
    </bean>
</beans>

(5)把集合注入部分提取出来

首先要在spring配置文件中引入名称空间util,再使用util标签完成list集合注入提取,实体类里用set注入

<?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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    <!--1、提取list集合类型属性注入-->
    <util:list id="bookList">
        <value>JavaSE如此简单</value>
        <value>Java修仙路由</value>
        <value>MySQL从入库到跑路</value>
    </util:list>
    <!--2、提取list集合类型属性注入使用-->
    <bean id="book" class="com.Book" scope="prototype">
        <property name="list" ref="bookList"></property>
    </bean>
</beans>
package com;

import java.util.List;

public class Book {
    private List<String> list;

    public void setList(List<String> list) {
        this.list = list;
    }

}

4、FactoryBean 

Spring有两种类型的bean,一种是普通的bean,另一种是工厂bean(FactoryBean),工厂模式的意义就是为了不暴露对象创建的过程。

普通bean:在配置文件中定义bean类型就是返回类型

工厂bean:在配置文件中定义的bean类型可以和返回类型不一样

需要注意的是,上面还提了一个BeanFactory,两者的区别还是要搞清楚的,BeanFactory是IOC容器的基本实现,是Spring内部的使用接口。

创建一个Mybean类,实现FactoryBean接口,重写FactoryBean中的三个方法,可以看出源码中的三个方法:

getObject():返回需要注册的对象 ,如果为单例,该实例会放到Spring容器中单实例缓存池中
getObjectType():返回对象的类型
isSingleton():判断是否是单例 ,非单例时每次创建都会返回一个新的bean

package org.springframework.beans.factory;

import org.springframework.lang.Nullable;

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}
package com.factorybean;

import com.Course;
import org.springframework.beans.factory.FactoryBean;

public class MyBean implements FactoryBean<Course> {

    //定义返回bean
    @Override
    public Course getObject() throws Exception {
        Course course=new Course();
        course.setCourseName("老滑头");
        return course;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }
}

5、bean的作用域

(1)在spring里面,默认的情况下,bean是单实例,当用getBean获取对象两次时,输出的引用对象地址一样

(2) 如何设置单实例还是多实例

在spring配置文件bean标签里面有属性(scope)用于设置单实例还是多实例。

scope属性值:

第一个值为默认值,singleton,表示单例对象,第二个值,prototype,表示多实例对象。

singleton和prototype的区别:

第一 singleton单实例,prototype多实例

第二 设置scope值是singleton的时候,在加载spring配置文件时就会创建单实例对象(饿汉式)。设置scope值是prototype的时候,不是在加载spring配置文件的时候创建对象,而是在调用getBean方法时才会创建多实例对象。可以看出当用getBean获取对象两次时,输出的引用对象地址不一样。

6、bean生命周期

生命周期,即从对象的创建到对象销毁的过程

bean的生命周期(面试常问):

(1)通过构造器创建bean实例(无参构造)

(2)为bean的属性设置值和对其他bean的引用(调用set方法)

(3)把bean实例传递给bean后置处理,执行postProcessBeforeInitialization方法

(4)调用bean的初始化方法(需要进行配置)

(5)把bean实例传递给bean后置处理器,执行postProcessAfterInitialization方法

(6)bean可以使用了(对象获取到了)

(7)当容器关闭的时候,调用bean的销毁方法(需要自己配置销毁的方法)

package com.bean;

public class Orders {
    private String oName;

    //无参构造方法
    public Orders() {
        System.out.println("第一步:执行无参构造创建bean实例");
    }

    public void setoName(String oName) {
        this.oName = oName;
        System.out.println("第二步:调用set方法设置对象的属性值");
    }

    //创建执行的初始化方法
    public void initMethod() {
        System.out.println("第三步:执行初始化的方法");
    }

    //创建执行的销毁方法
    public void destroyMethod() {
        System.out.println("第五步:执行销毁的方法");
    }

}
@Test
    public void test4(){
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("bean4.xml");
        Orders orders = context.getBean("orders", Orders.class);
        System.out.println("第四步:获取创建bean实例对象");
        System.out.println(orders);
        //手动让bean实例销毁
        context.close();
    }
package com.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在初始化之前执行的方法");
        return bean;
    }

    @Override
    public  Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在初始化之后执行的方法");
        return bean;
    }

}
<?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">
    <bean id="orders" class="com.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
        <property name="oName" value="手机"/>
    </bean>
    <!--配置后置处理器-->
    <bean id="myBeanPost" class="com.bean.MyBeanPost"/>
</beans>

7、xml自动装配

根据指定的装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入。bean便签属性autowire,配置自动装配,autowire属性常用的两个值:byName根据属性名称注入,注入值bean的id和类属性名称一样,如果不一样,属性注入失败,得出的结果为null

byType根据属性类型注入(class的类型),如果是多个属性,采取就近原则,选第一个,后面的autowire注解也会谈到。

<?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">
     <bean id="employees" class="com.autowire.Employees" autowire="byType">
     </bean>
    <bean id="department" class="com.autowire.Department">

    </bean>
</beans>

8、外部属性文件

外部属性文件在Mybatis中经常会用到,Spring也可以通过引入外部属性文件配置数据库连接池。把外部properties属性文件引入到配置文件中,这里需要引入context名称空间:

<?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 http://www.springframework.org/schema/context/spring-context.xsd">

六、IOC操作Bean管理(基于注解的方式 

使用注解进行spring配置,注解作用在类上面,方法上面,属性上面,使用注解的目的:简化xml配置,springboot就是简化spring的配置,可以实现完全注解开发(基于注解的方式在实际应用中使用的较多)。

1、Spring针对Bean管理创建对象提供注解

(1)@Component:相当于配置文件中的<bean id="" class=""/>,泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类

(2)@Service:service层

(3)@Controller:web层

(4)@Repository:dao层

*上面四个注解功能是一样的,都可以创建bean实例

2、基于注解方式实现对象创建

第一步 开启组件扫描

<context:component-scan base-package="com"/>

如果扫描多个包,多个包使用","隔开 ,并且扫描包的上层目录。use-default-filters="false" 表示现在不使用默认的filter,自己配置filter。

context:include-filter,设置扫描哪些内容:

<context:component-scan base-package="com.service" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

context:exclude-filter:设置哪些内容不进行扫描

<context:component-scan base-package="com.service">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

3、基于注解方式进行属性的注入 

(1)@AutoWired:根据属性类型进行自动装配

  第一步:创建service和dao对象,在service和dao类上添加创建对象注解,在注解里面value属性值可以不写,默认是类名称,首字母小写,value值和bean的id等价。

package com.dao;
import org.springframework.stereotype.Repository;

@Repository(value ="userDaoImpl")
public class UserDaoImpl implements UserDao{
    @Override
    public void add() {
        System.out.println("dao add..........");
    }
}
package com.service;

import com.dao.UserDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    private UserDao userDao;
    @Value(value = "老王")
    private String name;
    public void add() {
        System.out.println("service add........"+name);
        userDao.add();
    }

}

  第二步:在service里面注入dao对象,在service类里面添加dao类型的属性,在属性上使用注解。

(2)@Qualifier:根据属性名称进行注入。这个@Qualifier注解的使用要和上面的@AutoWired一起使用。

    @Autowired //根据类型进行注入
    @Qualifier(value = "userDaoImpl11")//根据名称进行注入

(3)@Resource:可以根据类型注入,也可以根据名称注入。根据导包来看,import javax.annotation.Resource;是 java自带的注解,不是框架里有的。

@Resource(name = "userDaoImpl11")//根据名称进行注入

(4)@Value:注入普通类型属性

@Value(value = "老王")
    private String name;

 4、完全注解开发

创建一个配置类,替代xml配置文件。@Configuration作为配置类注解,可以替代xml配置文件,

组件扫描也可以用注解@ComponentScan(basePackages = {"com"})实现。

package com.config;


import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration      //作为配置类,替代xml配置文件
@ComponentScan(basePackages = {"com"})
public class SpringConfig {

}
 
 

猜你喜欢

转载自blog.csdn.net/qq_53860947/article/details/123910890