Java框架学习:SpringIOC及依赖注入

Spring概述

Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。
Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架。这两个特点也是Spring框架的核心。
在使用Spring框架时要导入框架的jar包

控制反转IOC

控制反转(Inversion of Control,缩写为IOC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。

通过程序理解控制反转

传统方式中程序员在程序中自己创建并维护对象,即通过new实例化对象;现在将创建对象的权力(或者说对创建对象的控制)交给Spring容器,这就称作控制反转。

代码实现
我们在service层中创建一个类用于处理用户业务,类中方法userRegister用于注册用户

public class UserService {
    public void userRegister(){
        System.out.println("用户注册方法");
    }
}

在该类对应的测试方法中我们需要创建该类对象,并调用userRegister方法

public class userServiceTest {
    public static void main(String[] args) {
    	//传统方法,通过new来创建对象
        UserService userService = new UserService();
        userService.userRegister();
    }
}

现在摒弃传统new对象的方法,将创建对象的权力交给Spring容器
首先创建Spring框架的配置文件:applicationContext.xml(名称固定,最好不要改动)

<!--在<beans>节点下创建该标签
	beans是javabean集合,javabean实际上泛指任意java类,只不过java类需要有一个无参构造方法-->
<!--这行配置的意义:将UserService类交给Spring容器管理-->
<!--id是Spring容器中唯一的标识,在程序中通过id获取bean对象-->
<!--class是类的全路径,要从包开始指定,程序要通过反射技术来加载类对象
	该类类必须严格遵守javabean规范,即要有无参构造方法-->
<bean id="userService" class="com.xupt.service.UserService"/>
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class userServiceTest {
    public static void main(String[] args) {
    	//初始化Spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //从Spring容器中获取userService对象
        UserService userService = context.getBean("userService", UserService.class);
        userService.userRegister();
    }
}

代码解释:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
解释:
ApplicationContext最顶层继承了BeanFactoty
BeanFactoty是最顶级原始的bean工厂类,该接口提供了最基础的Spring容器方法,ApplicationContext扩展了一些内容
ClassPathXmlApplicationContext是ApplicationContext的实现类,在类路径下加载配置文件
在之后会使用到AnnotationConfigApplicationContext,该类也是ApplicationContext的实现类,用于解析注解。
除此之外,ApplicationContext还有一个实现类FileSystemXmlApplicationContext,用于从磁盘中加载配置文件。

UserService userService = context.getBean("userService", UserService.class);
解释:
getBean方法通过beanid获取对象,可以传入一个该类字节码文件对象
也可以通过强转来获取对象,如下:
UserService userService= (UserService) context.getBean("userService");
bean作用域

我们从Spring容器中获取了两个userService对象,比较其地址值,其地址值相同

public class userServiceTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService us1 = context.getBean("userService", UserService.class);
        UserService us2 = context.getBean("userService", UserService.class);
        //输出true
        System.out.println(us1==us2);
    }
}

解释如下:

在Spring配置文件定义bean时,通过声明scope配置项,可以定义bean对象的作用域,作用域限定了Spring Bean的作用范围。
scope配置项的值
singleton:应用单例模式管理容器中的bean对象,容器默认采用的方式。
	优点:减少类对象的创建,提升系统性能。
prototype:应用原型模式管理容器中的bean对象。
当使用singleton配置bean时,Spring容器仅创建一个bean实例,容器每次返回的是同一个bean实例;
当使用prototype配置bean时,Spring容器会创建多个bean实例,每次返回的都是一个新的实例。
在JavaWeb开发时,scope配置项的值还有request、session、application、websocket。

配置文件内容如下:

<!--如果scope="prototype",则上述java代码中System.out.println(us1==us2);输出false-->
<bean id="userService" class="com.xupt.service.UserService" scope="prototype"/>
Spring创建bean对象的方式

bean对象的创建就是java类对象的创建

1.使用无参构造方法实例化bean对象
<!--配置文件-->
<bean id="userService" class="com.xupt.service.UserService"/>

在UserService类中的构造方法中增加一条输出语句

public class UserService {
    public UserService() {
        System.out.println("无参构造方法被调用了");
    }

    public void userRegister(){
        System.out.println("用户注册方法");
    }
}
public class userServiceTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
}

运行userServiceTest类中的main方法,控制台会输出"无参构造方法被调用了"

解释:
Spring容器在初始化时,默认会创建javabean对象,创建方式就是调用无参构造方法。
如果UserService类中我们提供一个有参构造的方法,而不提供无参构造方法,运行main方法会报错。
如果使用IDEA工具时,配置文件中会在以下位置报错:
class="com.xupt.service.UserService"
因为Spring容器是通过反射调用无参构造方法来创建javabean对象的
2.使用静态工厂方法获取bean对象

如果无参构造方法是被private修饰,比如Runtime类,采用了单例设计模式,所以无参构造方法是私有的

//传统方式调用getRuntime方法获取Runtime类实例
Runtime runtime = Runtime.getRuntime();
<!--私有构造方法保证类在外部不能被访问,但是Spring为了编写代码的方便,破坏了java的封装性,可以在类的外部访问私有属性。但这不是此刻讨论的-->
<!--使用Runtime类中的静态工厂方法获取bean对象-->
<!--如果使用第三方提供的类,无法修改代码时,可以使用该方法,不常用-->
<bean id="runtime" class="java.lang.Runtime" factory-method="getRuntime"/>
public class userServiceTest {
    public static void main(String[] args) throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Runtime runtime = context.getBean("runtime", Runtime.class);
        //执行打开计算器的命令
        runtime.exec("calc");
    }
}
3.使用实例工厂方法获取bean对象

创建UserFactory类,通过该类中的getUserService可以获取一个UserService对象

public class UserFactory {
    public UserService getUserService(){
        System.out.println("创建工厂方法");
        return new UserService();
    }
}
<!--配置文件-->
<bean id="userFactory" class="com.xupt.service.UserFactory"/>
<!--指定工厂类userFactory,指定工厂方法getUserService,Spring容器会通过工厂类中的工厂方法获取bean对象-->
<bean id="userService" factory-bean="userFactory" factory-method="getUserService" />
public class userServiceTest {
    public static void main(String[] args) throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService);
    }
}

依赖注入DI

bean的依赖关系

bean的依赖关系:一个程序中会有多个类,类与类之间会相互调用。
比如service层中的类中会调用DAO层中的类中的方法,那么就是service层中的类会依赖DAO层中的类,这种依赖关系具有耦合性。

在dao包中定义UserDao接口,并创建其实现类UserDaoImpl,重写抽象方法。

public interface UserDao {
    public void saveUser();
    public Object queryByName();
}
public class UserDaoImpl implements UserDao {
    public void saveUser() {
        System.out.println("userdao saveuser");
    }

    public Object queryByName() {
        System.out.println("userdao query by name");
        return null;
    }
}

在传统方式中,我们会在UserService类中定义一个成员变量来存储UserDaoImpl对象,通过该对象调用UserDaoImpl类中方法

public class UserService {
    private UserDao udao=new UserDaoImpl();

    public void userRegister(){
        udao.queryByName();
        udao.saveUser();
        System.out.println("用户注册方法");
    }
}

现在存在一个需求,UserDao类的udao对象需要更换实现类,即将UserDaoImpl类对象更改为UserDaoOracle类对象,操作Oracle数据库,两个类之间的依赖关系(耦合性)使我们只能改变代码来实现这个需求

public class UserDaoOracle implements UserDao {
    public void saveUser() {
        System.out.println("oracle save user");
    }

    public Object queryByName() {
        System.out.println("oracle query by name");
        return null;
    }
}
通过程序理解依赖注入

现在不通过new来创建对象(udao默认为null),而是通过Spring容器在UserService类中为udao设置(或注入)对象,这就是依赖注入

通过setter方法注入对象

容器可以通过setter方法在外部注入udao对象

public class UserService {
    private UserDao udao;
	
	public void setUdao(UserDao udao) {
        this.udao = udao;
    }
	
    public void userRegister(){
        udao.queryByName();
        udao.saveUser();
        System.out.println("用户注册方法");
    }
}

配置文件如下:

<!--将参与依赖的bean对象交给Spring容器-->
<bean id="userDao" class="com.xupt.dao.UserDaoImpl"/>
<!--userService类引用了userDao对象-->
<bean id="userService" class="com.xupt.service.UserService">
        <!--Spring容器为userService类中的属性udao实现/注入实现对象,这就是依赖注入-->
        <!--name属性值对应的是setxxx方法
            ref是引用,是javabean在Spring容器的id值-->
       
        <property name="udao" ref="userDao"/>
</bean>
过程:
实现类ClassPathXmlApplicationContext在加载配置文件时,会先创建UserDaoImpl类对象,
然后在读取创建userService对象,通过<property>标签根据name属性值"udao"通过对应的setUdao方法为udao属性注入id为userDao的引用

在userServiceTest中获取userService对象并调用userRegister方法

public class userServiceTest {
    public static void main(String[] args) throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.userRegister();
    }
}

现在我们来实现更换UserDao实现类的需求
配置文件如下:

 <bean id="userDaoOracle" class="com.xupt.dao.UserDaoOracle"/>
<bean id="userDao" class="com.xupt.dao.UserDaoImpl"/>
<bean id="userService" class="com.xupt.service.UserService">
	<property name="udao" ref="userDaoOracle"/>
</bean>

此时再次运行userServiceTest中的main方法,userRegister方法中就会调用userDaoOracle类中的方法。

通过构造方法注入对象
public class UserService {
    private UserDao udao;
	
	public UserService(UserDao udao) {
        this.udao=udao;
    }
	
    public void userRegister(){
        udao.queryByName();
        udao.saveUser();
        System.out.println("用户注册方法");
    }
}

配置文件如下:

<bean id="userDao" class="com.xupt.dao.UserDaoImpl"/>
<!--javabean编写了有参数的构造方法,无参数的构造方法被覆盖,Spring容器无法通过默认方式创建javabean对象-->
<bean id="userService" class="com.xupt.service.UserService">
	<!--constructor-arg:用于配置构造方法中的参数
	 	ref:引用其他javabean作为构造方法的参数-->
	<constructor-arg ref="userDao"/>
</bean>

实现更换UserDao实现类的需求
配置文件如下:

<bean id="userDaoOracle" class="com.xupt.dao.UserDaoOracle"/>
<bean id="userDao" class="com.xupt.dao.UserDaoImpl"/>
<bean id="userService" class="com.xupt.service.UserService">
	<constructor-arg ref="userDaoOracle"/>
</bean>

两种该方法都可以使用,构造方法会强制检查,但是编程中通常习惯使用无参构造方法。

使用value属性注入成员变量

在上述例子中,演示了使用ref(引用)属性注入成员变量,并以此来理解依赖注入。
我们也可以使用value属性指定值来实现注入对象

存在BeanProperty类,以此为例注入该类的成员变量

import java.util.List;
import java.util.Map;
import java.util.Properties;

public class BeanProperty {
    private int age;
    private String uploadPath;
    private List<String> list;
    private Map<String,Object> map;
    private Properties properties;

    public void setAge(int age) {
        this.age = age;
    }

    public void setUploadPath(String uploadPath) {
        this.uploadPath = uploadPath;
    }

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

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public int getAge() {
        return age;
    }

    public String getUploadPath() {
        return uploadPath;
    }

    public List<String> getList() {
        return list;
    }

    public Map<String, Object> getMap() {
        return map;
    }

    public Properties getProperties() {
        return properties;
    }
}
基本数据类型和String类型成员变量

配置文件:

<!--对于基本数据类型和String类型,可直接在value属性中赋值-->
<bean id="beanProperty" class="com.young.beans.BeanProperty">
	<property name="age" value="20"/>
    <property name="uploadPath" value="abcdef"/>
</bean>
List集合类型成员变量

配置文件如下:

<!--通过list标签实现注入,在list标签下的value标签中配置List集合中的元素-->
<!--在bean节点下配置如下内容-->
<property name="list">
	<list>
		<value>身份证</value>
		<value>护照</value>
		<value>驾驶证</value>
	</list>
</property>
Map集合类型成员变量

配置文件如下:

<!--通过map标签实现注入,在map标签下的entry标签中配置属性key和value,分别代表键和值-->
<!--在bean节点下配置如下内容-->
<property name="map">
    <map>
		<entry key="001" value="北京市"/>
        <entry key="002" value="上海市"/>
        <entry key="003" value="深圳市"/>
	</map>
</property>
Properties集合类型成员变量

注入Properties集合类型成员变量有两种方式

1.在<value>标签下直接设置键值对
配置文件如下:

<!--在bean节点下配置如下内容-->
<property name="properties">
	<value>
        username=aaa
        password=123
    </value>
</property>

2.在<props>标签下的<prop>标签中设置key属性,在该标签下设置对应的值

配置文件如下:

<!--在bean节点下配置如下内容-->
<property name="properties">
    <props>
        <prop key="username">aaa</prop>
        <prop key="password">123</prop>
    </props>
</property>

我们可以在测试类中进行测试

import com.alibaba.fastjson.JSON;
import com.young.beans.BeanProperty;
import com.young.beans.DBUtils;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestIOC {
    @Test
    public void testBean(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        BeanProperty beanProperty = context.getBean("beanProperty", BeanProperty.class);
        System.out.println(beanProperty.getAge());
        System.out.println("--------------");
        System.out.println(beanProperty.getUploadPath());
        System.out.println("--------------");
        System.out.println(JSON.toJSONString(beanProperty.getList()));
        System.out.println("--------------");
        System.out.println(JSON.toJSONString(beanProperty.getMap()));
        System.out.println("--------------");
        System.out.println(JSON.toJSONString(beanProperty.getProperties()));
    }
}
读取配置文件中的数据注入成员变量

需求将配置文件db.properties下的数据注入类DBUtils中的成员变量
DBUtils类

public class DBUtils {
    private String username;
    private String password;

    public DBUtils(){
        System.out.println("构造方法");
    }
    
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "DBUtils{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

resources目录下存在一个配置文件“db.properties”,文件内容如下:

username=aaa
password=123

applicationContext.xml配置文件如下:

<!--PropertiesFactoryBean是Spring中提供的一个bean,用于读取配置文件-->
<bean id="propertiesFactoryBean" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <!--locations可以在<array>标签中配置多个配置文件-->
    <property name="locations">
        <array>
            <value>classpath:db.properties</value>
        </array>
    </property>
</bean>
<!--将DBUtils类交给Spring容器-->
<bean id="dbUtils" class="com.young.beans.DBUtils">
    <!--在#{}中获取配置文件中的数据-->
    <property name="username" value="#{propertiesFactoryBean.username}"/>
    <property name="password" value="#{propertiesFactoryBean.password}"/>
</bean>

测试类:

import com.young.beans.DBUtils;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestIOC {
    @Test
    public void testBean(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        DBUtils dbUtils = context.getBean("dbUtils", DBUtils.class);
        System.out.println(dbUtils);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值