1.概念
在 Java 项目开发过程中,耦合性是一个需要重点关注的问题。尤其是在项目后期进行功能的增加或删除时,最好避免直接修改源代码。为解决这一问题,通常会采用工厂模式结合反射模式来实现解耦。然而,这种方式存在一个弊端,即每次都需要重复创建类的实例化对象,从而导致资源开销增大。Spring 框架的出现则有效地解决了这些问题。Spring 通过将需要实例化的类纳入 Spring IoC(控制反转)容器进行管理,当需要使用类中的方法时,可直接调用容器中相关类的 Bean
小tips:一定要搞清楚bean的意思,在spring中可以就把它理解为类的实例化对象,不然后面一直提到bean就会很晕
2.spring入门环境的搭建
首先在pom.xml文件中导入关于spring的核心依赖
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.0.RELEASE</version></dependency></dependencies>
这里也可以使用其他的版本
然后要去定义dao service的接口及其实现类,接着在resources包中创建xml文件,通过bean的标签来管理bean,将bean放在spring的ioc容器中
<bean id="accountDao" class="com.xq.dao.Impl.AccountDaoImpl"/><bean id="accountService" class="com.xq.service.impl.AccountServiceImpl"/>
这里的id的值是任意的,但是它必须是唯一的,class值就是你想要实现的类的工程中的路径 ,
在配置好了你想要的类后,当你想使用它时,首先需要初始化一个ioc容器,
创建好之后,ioc容器中就会立刻加载相应类的实例化对象(加载的是单例bean不是多例bean)
//初始化ioc容器ApplicationContext context=new ClassPathXmlApplicationContext("加入之前配置的包含bean标签的xml文件");
然后调用ioc容器提供的getBean方法获取bean,也就是相应的类的实现对象,用对应的变量去接收它就可以用了。
AccountDao accountDao = (AccountDao) context.getBean("accountDao");
这里的括号里的东西就是之前xml文件中bean标签内的id,通过id去调用相应的bean,再把他强转成相应的数据类型就可以用了。
还有另外一种方法来获取bean,就是通过BeanFactory的方式创建对象,和前面的很像
//通过BeanFactory的方式创建对象Resource resource=new ClassPathResource("相应的xml文件");
//创建spring bean工厂BeanFactory beanFactory=new XmlBeanFactory(resource);
再通过BeanFactory的对象去调用getBean方法获取具体的bean就行了。但是他和前面的有点不同,刚刚提到了ioc容器是在刚创建时就立马创建相应的bean,而Bean Factory是在准备获取bean时才创建(就是调用getBean时)。
前面的ApplicationContext间接的继承了BeanFactory,对BeanFactory的方法进行了扩展。
不过这个方法已经被标记为废弃了,只需要了解即可,现在主要还是用第一个方法。
下面来具体讲一下关于ApplicationContext的实现类
ClassPathXmlApplicationContext:加载类路径下面的spring配置文件
FileSystemXmlApplicationContext:加载磁盘绝对路径下面的配置文件
AnnotationConfigApplicationContext:用于直接环境,创建容器(后面再介绍)
接着我们来讲一下管理bean的其他方法
第二种
<!--spring管理bean的第二种方式:基于实例化工厂管理beanfactory-bean:引用的是工厂bean的idfactory-method:引用的是工厂bean中的获取bean的方法名称--><bean id="personFactory" class="com.xq.factory.PersonFactory"></bean><bean id="personDao" class="com.xq.dao.impl.PersonDaoImpl" factory-bean="personFactory" factory-method="getPersonDao"></bean>
public class PersonFactory {//定义一个方法 方法的放回值就是需要被管理的bean类型public PersonDao getPersonDao() {return new PersonDaoImpl();}
}
factory-bean用于指定创建的工厂类的bean的id,factory-method用于指定工厂类中创建类的方法
调用的时候还是先加载xml文件,然后
PersonDao personDao =(PersonDao) context.getBean("放入id");
第三种
<!--spring中管理bean的第三种方式:使用静态实例化工厂管理bean--><bean id="orderDaoFactory" class="com.xq.factory.OredrDaoFactory" factory-method="getOrder"></bean>
public class PersonFactory {//定义一个方法 方法的放回值就是需要被管理的bean类型public PersonDao getPersonDao() {return new PersonDaoImpl();}
}
调用跟上面查不多
OrderDao orderDao=(OrderDao)context.getBean("bean中的id");
3.单例bean与多例bean
首先来讲一下什么叫做单例bean与多例bean
单例bean:在整个应用程序中,单例 Bean 只会存在一个实例。当应用程序启动时,单例 Bean 会被创建并初始化,并且在应用程序的生命周期内,这个 Bean 的实例只会被创建一次。
多例bean:多例 Bean 的创建由 Spring 容器负责,但在销毁时,Spring 容器不会像管理单例 Bean 那样进行统一管理。多例 Bean 在使用完后,如果没有其他对象引用它,会由 Java 的垃圾回收机制根据自身的算法决定是否回收该对象。这意味着多例 Bean 的生命周期管理主要依赖于 Java 的内存管理机制,而不是 Spring 容器的特定生命周期管理方法。
ioc容器默认创建的bean都是单例bean,
UserDao userDao1=(UserDao)context.getBean("userDao");UserDao userDao2=(UserDao)context.getBean("userDao");System.out.println(userDao1==userDao2);
像这样,结果是true,构造函数只执行一次
如果像创建多例bean,可以在xml文件中的bean标签中加入scope=prototype。那么此时就会输出false,并且构造函数会执行两次。
4.bean的生命周期
单例对象
单例对象的生命周期和容器的生命周期是一致的。当容器创建时,对象就实例化好了。当容器还在的时 候,对象也就一直存在。当容器销毁,对象也就消亡
我们可以在bean标签中所指向的类中加入init(初始化)和destroy(销毁)方法,并在bean标签中加入init-method=“init”和destroy-method=“destroy”
只要ioc容器被创建init就会被自动执行,最后销毁执行
((ClassPathXmlApplicationContext)context).close();这个方法即可。
多例bean
多例就不用管这么多了,只需要保证我们创建的是多例,销毁时 Spring 容器不会管理,而是由 Java 的垃圾回收机制根据对象是否还有引用决定是否回收。
5.依赖注入
依赖注入是一种设计模式,也是 Spring 框架的核心功能之一。其本质是将对象所依赖的其他对象通过某种方式(如构造函数、方法参数、属性等)传递给对象,实现对象之间的解耦,提高代码的可维护性和可测试性。同时,依赖注入体现了控制反转的思想,将对象依赖关系的管理从对象内部转移到外部容器,由容器负责创建和注入依赖对象
使用setter方法进行依赖注入
但使用该方法的前提是被管的bean所属的类必须提供setter方法。
首先就是在要被调用的bean所属的类中写上各个成员变量的setter方法,然后在xml文件中的bean标签中配置。
public class User {private String username;private int age;private String address;private Car car;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public Car getCar() {return car;}public void setCar(Car car) {this.car = car;}@Overridepublic String toString() {return "User{" +"username='" + username + '\'' +", age=" + age +", address='" + address + '\'' +", car=" + car +'}';}
}public class TestUser {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");User user = (User) context.getBean("user");System.out.println(user);}
}
xml相关配置如下
<!--第一种:默认的使用set方法进行注入,使用set方法进行注入的前提是被管理的bean所属的类必须提供set方法property标签:完成bean的属性的注入name:描述的bean的属性名称value:描述bean的属性值(基本数据类型和字符串)ref:注入的是引用数据类型的值 引用的是另外一个bean的id--><bean id="car" class="com.xq.pojo.Car"><property name="brand" value="宝马"></property><property name="type" value="BMW540"></property></bean><bean id="user" class="com.xq.pojo.User"><property name="username" value="kunkun"></property><property name="age" value="18"></property><property name="address" value="CHINA"></property><property name="car" ref="car"></property></bean>
先看下面那个bean标签中的内容,他给user类中的每个成员变量都附上了初值,name就是成员变量的名字,value就是想要附的值。但是这里有一点要注意这里的car是一个引用变量,因为一个类中有很多个成员变量,所以不可能向上面一样直接用value来赋值所以又开了一个bean标签单独把car进行了初始化,这里就又点嵌套那味了,相当于用一个ref来将一个类嵌套在一个类中,这里的ref的值就是上面的id。
ref能够在不同的 Bean 之间建立起依赖关系,使得一个 Bean 可以使用另一个 Bean 的功能和属性。
来看输出结果
User{username='kunkun', age=18, address='CHINA', car=Car{brand='宝马', type='BMW540'}}
当然要看输出结果记得在目标类中加上toString方法。
使用构造函数实现依赖注入
第一步还是一样的,现在资源文件夹中的xml文件中导入相关bean标签,其实跟前面那个差不多,相关配置如下
<!--spring的依赖注入的第二种方式:使用构造函数实现依赖注入constructor-arg:完成依赖注入 使用的是带参数的构造函数name:属性名称value:属性名称所属的值 必须是基本数据类型和字符串类型的值ref:引用数据类型的值--><bean id="departement" class="com.xq.pojo.Department"><constructor-arg name="id" value="1"></constructor-arg><constructor-arg name="name" value="技术部"></constructor-arg><constructor-arg name="address" value="Newyork"></constructor-arg></bean><bean id="emp" class="com.xq.pojo.Emp"><constructor-arg name="id" value="1001"></constructor-arg><constructor-arg name="name" value="kunkun"></constructor-arg><constructor-arg name="age" value="18"></constructor-arg><constructor-arg name="department" ref="departement"></constructor-arg></bean>
也就是bean标签内的property标签换成了constructor标签,前面那个理解了这个自然是一个道理。但是有点要注意,就像前面一样,既然使用setter方法进行依赖注入需要bean所属的类需要setter方法,那么这个也是一样,需要类中有构造方法才能使用
其余代码如下
public class Emp {private int id;private String name;private int age;private Department department;public Emp() {}@Overridepublic String toString() {return "Emp{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", department=" + department +'}';}public Emp(int id, String name, int age, Department department) {this.id = id;this.name = name;this.age = age;this.department = department;}
}public class TestUser {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");Emp emp = (Emp)context.getBean("emp");System.out.println(emp);}
}
输出结果如下
Emp{id=1001, name='kunkun', age=18, department=Department{id=1, name='技术部', address='Newyork'}}
进行复杂数据类型的依赖注入
上面只讲了简单数据类型,String类型和引用数据类型的依赖注入,下面就是复杂数据类性的依赖注入,例如各种集合,数组。
直接上代码:
public class Animal {private String[] strs;private List<String> list;private Set<String> set;private Map<String,String> map;private Properties pros;public String[] getStrs() {return strs;}public void setStrs(String[] strs) {this.strs = strs;}public List<String> getList() {return list;}public void setList(List<String> list) {this.list = list;}public Set<String> getSet() {return set;}public void setSet(Set<String> set) {this.set = set;}public Map<String, String> getMap() {return map;}public void setMap(Map<String, String> map) {this.map = map;}public Properties getPros() {return pros;}public void setPros(Properties pros) {this.pros = pros;}@Overridepublic String toString() {return "Animal{" +"strs=" + Arrays.toString(strs) +", list=" + list +", set=" + set +", map=" + map +", pros=" + pros +'}';}
}<!--进行复杂类型值的注入--><bean id="animal" class="com.xq.pojo.Animal"><!--注入数组类型的值--><property name="strs"><array><value>str1</value><value>str2</value><value>str3</value></array></property><!--注入list类型的值--><property name="list"><list><value>list1</value><value>list2</value><value>list3</value></list></property><!--注入set--><property name="set"><set><value>s1</value><value>s2</value><value>s3</value></set></property><!--注入Map--><property name="map"><map><entry key="k1" value="v1"></entry><entry key="k2" value="v2"></entry><entry key="k3" value="v3"></entry></map></property><!--注入properties--><property name="pros"><props><prop key="p1">v1</prop><prop key="p2">v2</prop><prop key="p3">v3</prop></props></property></bean>public class TestUser {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");Animal animal=(Animal)context.getBean("animal");System.out.println(animal);}
}
还是差不多的,标明不同的name以及具体的数据类型就行了。