05set注入和构造注入的原理

set注入复杂类型ref属性

测试准备

准备一个UserDao类和UserService类,其中UserService类关联UserDao类型的属性

public class UserDao {
    public void insert(){
        System.out.println("正在保存用户数据");
    }
}

public class UserService {
    private UserDao userDao;
    // 使用set方式注入必须提供set方法,反射机制要调用这个方法给属性赋值的
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void save(){
        userDao.insert();
    }
}

注入外部Bean(常用)

注入外部Bean的方式: 在外部注册一个bean, 然后通过外部bean的id配合property标签的ref引用属性进行注入

  • 对于property标签来说还可以使用子标签ref的bean属性方式引入外部bean(实际开发中一般直接使用ref属性)
<?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">
    <!--配置dao-->
    <bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
    <!--配置service-->
    <!--name属性的属性值怎么写:属性set方法的方法名去掉set,然后把剩下的单词首字母变小写-->
    <bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
        <property name="userDao" ref="userDaoBean"/>
    </bean>
    <!--对于property标签来说,ref属性也可以采用标签的方式,但使用ref属性是多数的-->
    <bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
        <property name="userDao">
            <ref bean="userDaoBean"/>
        </property>
    </bean>
</beans>

注入的bean相当于是从容器中拿的,我们修改从容器中获取的bean或者通过访问被注入对象的属性修改bean都会引起容器中的bean变化

public void testFirst(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    // 从容器中获取要注入的UserDao然后修改属性值,此时注入到UserService的UserDao属性值也发生了变化
    UserDao userDaoBean = applicationContext.getBean("userDaoBean", UserDao.class);
    userDaoBean.setId(9);
    UserService userServiceBean =  (UserService)applicationContext.getBean("userServiceBean");
    System.out.println(userServiceBean.getUserDao().getId());
    
    // 通过UserService修改UserDao的属性值,此时从容器中获取的UserDao的属性值也发生改变
    userServiceBean.getUserDao().setId(6);
    System.out.println(userDaoBean.getId());
}

注入内部Bean(了解)

注入内部Bean的方式: 在bean标签中嵌套bean标签直接注入,注意通过内部bean的id并不能通过容器获取到bean对象,所以不用写id

<?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="userServiceBean" class="com.powernode.spring6.service.UserService">
        <property name="userDao">
            <bean class="com.powernode.spring6.dao.UserDao"/>
        </property>
    </bean>
</beans>

set注入简单类型value属性

简单类型的注入和分类

Spring的源码来分析有哪些简单类型: BeanUtils类(按两次shift键搜索)

  • String类或其他的CharSequence子类,Number子类,Date子类,Enum子类,URI,URL,Temporal子类,Locale,Class,包括以上简单值类型对应的数组类型
public class BeanUtils{
    //........
    //ctrl + F12搜索isSimpleValueType方法
    public static boolean isSimpleValueType(Class<?> type) {
        return (Void.class != type && void.class != type &&
                (ClassUtils.isPrimitiveOrWrapper(type) ||Enum.class.isAssignableFrom(type) ||
                 //String实现了CharSequence
                 CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) ||
                 Date.class.isAssignableFrom(type) ||
                 //java8提供的时间和时区类型
                 Temporal.class.isAssignableFrom(type) ||
                 URI.class == type || URL.class == type ||
                 //语言类型
                 Locale.class == type || Class.class == type));
    }
    //........
}

注入部分简单类型: 实际开发中我们一般不会把Date当做简单类型而是采用ref属性给Date类型的属性赋值

  • 如果把Date当做简单类型的话,日期字符串格式必须符合Date的toString()方法格式Fri Sep 30 15:26:38 CST 2022,否则无法赋值给Date类型的属性
// 实体类
public class A {
    private String str;
    private Date date;
    // 枚举类型的属性
    private Season season;
    // spring6之后要求注入的URL必须存在有效,如果不存在则报错
    private URI uri;
    private URL url;
    private LocalDate localDate;
    private Locale locale;
    private Class clazz; 
    // 生成setter方法
    // 生成toString方法
}
// 枚举类
enum Season {
    SPRING, SUMMER, AUTUMN, WINTER
}
<?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="a" class="com.powernode.spring6.beans.A">
        <property name="str" value="zhangsan"/>
        <!--value后面的日期字符串格式不能随便写,必须是Date对象toString()方法执行的结果,否则报错提示无法转换成Date类型-->
        <!--如果想使用其他格式的日期字符串就需要进行特殊处理了,具体怎么处理后面再说-->
        <property name="date" value="Fri Sep 30 15:26:38 CST 2022"/>
        <property name="season" value="WINTER"/>
        <property name="uri" value="/save.do"/>
        <!--spring6之后会自动检查url是否有效,如果无效会报错-->
        <property name="url" value="http://www.baidu.com"/>
        <property name="localDate" value="EPOCH"/>
        <!--java.util.Locale主要在软件的本地化时使用,它本身没有什么功能,更多的是作为一个参数辅助其他方法完成输出的本地化-->
        <property name="locale" value="CHINESE"/>
        <property name="clazz" value="java.lang.String"/>
    </bean>
</beans>

手写数据源并自动注入属性值

我们都知道所有的数据源都要实现javax.sql.DataSource接口,并且数据源中应该有连接数据库的信息例如driver、url、username、password

// 手写一个数据源并交给Spring取管理
public class MyDataSource implements DataSource {
    private String driver;
    private String url;
    private String username;
    private String password;
    //提供属性的set和toString方法....
    @Override
    public Connection getConnection() throws SQLException {
        return null;
    }
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }
    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }
    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }
    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }
    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }
    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}

使用Spring提供的依赖注入完成数据源对象的创建和driver,url,username,password属性的赋值(要求属性提供了setter方法)

<?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">
    <!--让Spring管理我们的数据源-->
    <bean id="dataSource" class="com.powernode.spring6.beans.MyDataSource">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
</beans>

构造注入

测试准备

准备一个OrderDao类和OrderService类 , 其中OrderService类关联OrderDao类型的属性

public class OrderDao {
    public void deleteById(){
        System.out.println("正在删除订单");
    }
}
public class UserDao {
    public void insert(){
        System.out.println("正在添加用户");
    }
}
public class OrderService {
    private OrderDao orderDao;
    private UserDao userDao;
    // 通过反射机制调用构造方法给属性赋值(构造方法有两个参数)
    public OrderService(OrderDao orderDao, UserDao userDao) {
        this.orderDao = orderDao;
        this.userDao = userDao;
    }
    // 构造方法重载
    public OrderService(Integer a, UserDao userDao) {
        this.userDao = userDao;
    }
    public void delete(){
        orderDao.deleteById();
        userDao.insert();
    }
}

构造注入的核心原理

通过调用构造方法来给属性赋值,每个constructor-arg标签对应构造器的一个形参,Spring先通过constructor-arg标签的个数确定调用哪个构造方法

如果构造方法有多个参数,通过index和name属性指定为构造方法的哪个形参赋值, 对于没有赋值的形参遵循属性的默认赋值规范

<?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="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>
    <bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
    <!--这里利用构造方法上参数的下标为属性赋值-->
    <bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
        <!--指定为构造方法的第一个参数赋值,第一个参数下标是0-->
        <constructor-arg index="0" ref="orderDaoBean"/>
        <!--指定为构造方法的第二个参数赋值,第二个参数下标是1-->
        <constructor-arg index="1" ref="userDaoBean"/>
    </bean>
    <!--这里使用了构造方法上参数的名字-->
    <bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
        <constructor-arg name="orderDao" ref="orderDaoBean"/>
        <constructor-arg name="userDao" ref="userDaoBean"/>
    </bean>
</beans>

如果既不指定下标也不指定参数名字,Spring也会根据实参类型自动推断出要给哪个形参赋值,使用type属性指定形参的类型让Spring可以快速精确到具体形参

<!--这里没有指定下标也没有指定参数名字,默认按照constructor-arg标签从上到下的顺序给构造方法中对应的形参赋值-->
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
    <constructor-arg ref="orderDaoBean"/>
    <constructor-arg ref="userDaoBean"/>
</bean>

<!--这里标签的顺序已经和构造方法的参数顺序不同了,Spring也可以根据实参类型自动推断给哪个形参赋值-->
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
    <constructor-arg ref="userDaoBean"/>
    <constructor-arg ref="orderDaoBean"/>
</bean>

<!--这里没有指定下标也没有指定参数名字,利用type属性指定参数的类型-->
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
    <constructor-arg ref="userDaoBean" type="com.powernode.spring6.dao.UserDao"/>
    <constructor-arg ref="orderDaoBean" type="com.powernode.spring6.dao.OrderDao"/>
</bean>

set和构造注入的核心实现原理

set注入是对象创建好后注入的,构造注入是对象实例化过程当中注入

set注入

set注入简单类型和复杂类型的常见方式

  • 利用property标签的value属性给对象的属性注入简单类型,ref属性给对象的属性注入复杂类型

set注入是基于setter方法实现的,底层会通过反射机制调用属性对应的setter方法然后给属性赋值

  • set注入要求实体类的属性必须对外提供setter方法, 并且想让Spring调用对应的setter方法还需要设置property标签
  • Spring先拿到property标签中的name属性值 ,然后首字母大写前面加上set , 最后通过反射机制调用实体类中对应的setXxx方法为该属性赋值
  • set注入只会调用实体类属性对应的setter方法为属性赋值与属性名无关,如果所有getter/setter都自动生成,那么property标签的name属性的值就可以写属性名
  • 对于实体类中没有赋值的属性,遵循属性默认赋值规范

构造注入

构造注入简单类型和复杂类型的常见方式

  • 利用constructor-arg标签的value属性给形参注入简单类型,ref属性注入复杂类型

构造注入是基于构造方法实现的,底层会通过反射机制调用对应的构造方法给属性赋值

  • 通过constructor-arg标签个数确定调用哪个构造方法,即使形参的个数相同但类型一定不都相同,Spring会根据实参类型自动推断出要给哪个构造方法的形参赋值
  • 如果构造方法有多个参数,通过index和name属性指定为构造方法的哪个形参赋值, 对于没有赋值的形参遵循属性的默认赋值规范
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值