为什么使用Spring

先看一个示例
有一个Address类

  1. package com.lff;
  2. public class Address {
  3. private String city;
  4. public String getCity() {
  5. return city;
  6. }
  7. public void setCity(String city) {
  8. this.city = city;
  9. }
  10. public Address(String city) {
  11. this.city = city;
  12. }
  13. public Address(){
  14. }
  15. }

有一个Person类

  1. package com.lff;
  2. public class Person {
  3. private Address address = Factory.getAddress();
  4. public void setAddress(Address address) {
  5. this.address = address;
  6. }
  7. }

上面Person类强依赖了Address类

  1. private Address address = new Address("BeiJing");

如果上面的地址发生变化,从BeiJing变为TianJing,就需要改这部分的代码。

  1. private Address address = new Address("TianJing");

这就产生了耦合。
耦合:我依赖你,你不见了,对我影响很大,我就需要改代码。
我们可以进一步优化,使用工厂设计模式
新建工厂类

  1. package com.lff;
  2. public class Factory {
  3. public static Address getAddress(){
  4. return new Address("TianJing");
  5. }
  6. }

Person类使用工厂获取相应的Address实例。

  1. private Address address = Factory.getAddress();

工厂设计模式降低耦合性
只依赖工厂,一般工厂不经常变动。工厂生产的产品可以随时改变。以后想变动产品(这里指Address实例),统一改工厂就可以了
但这样还是有耦合性,如果想换工厂生产的产品,还是要改代码,还是有耦合性。
更进一步
使用配置文件解决上面的问题。把所有的类放在配置文件里面,利用反射技术动态生成类以及对象。如果以后换工厂里面的产品,只需修改配置就可以了。这样在最大限度的办到解耦的目的(不需要修改代码,只需要修改配置就可以)。
新建配置文件factory.properties

  1. address=com.lff.Address
  2. city="TianJing"

修改工厂类

  1. package com.lff;
  2. import java.io.InputStream;
  3. import java.util.Properties;
  4. public class Factory {
  5. private static Properties properties;
  6. static {
  7. //加载配置文件
  8. try (InputStream is = Factory.class.getClassLoader().getResourceAsStream("factory.properties")) {
  9. properties = new Properties();
  10. properties.load(is);
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. //根据配置文件里面的内容修动态生成Address的实例
  16. public static Address getAddress(){
  17. try {
  18. String city = properties.getProperty("city");
  19. // 类名
  20. String clsName = properties.getProperty("address");
  21. Class cls = Class.forName(clsName);
  22. // 实例化对象
  23. Address address = (Address) cls.newInstance();
  24. address.setCity(city);
  25. System.out.println(city);
  26. return address;
  27. } catch (Exception e) {
  28. e.printStackTrace();;
  29. }
  30. return null;
  31. }
  32. }

外界使用

  1. public class Main {
  2. public static void main(String[] args) throws BeansException {
  3. Person person = new Person();
  4. System.out.println(person);
  5. }
  6. }

这样外界想改变Person类的address只需修改配置文件就可以了,代码一点都不用动。这样就把耦合降到最低了。
上面是我们自己创建的工厂,Spring内部就提供了一个功能更加强大的工厂。我们可以使用Spring进行改造

使用方式

Spring源码地址
Spring实战

maven依赖

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-context</artifactId>
  4. <version>5.2.8.RELEASE</version>
  5. </dependency>

类似于MyBatis,Spring也很容易集成日子框架:比如logback,只需添加依赖即可

  1. <dependency>
  2. <groupId>ch.qos.logback</groupId>
  3. <artifactId>logback-classic</artifactId>
  4. <version>1.2.3</version>
  5. </dependency>

新建一个核心配置文件applicationContext.xml,为什么要这样写,以后就明白了,先看一下效果。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="address" class="com.lff.Address">
  6. <constructor-arg value="fff"/>
  7. <property name="city" value="BeiJing"/>
  8. </bean>
  9. <bean id="person" class="com.lff.Person">
  10. <property name="address" ref="address" />
  11. </bean>
  12. </beans>

对上面的Person类进行改造

  1. package com.lff;
  2. public class Person {
  3. private Address address; //注意:这里并没有赋值
  4. public void setAddress(Address address) {
  5. this.address = address;
  6. }
  7. }

外界使用

  1. import com.lff.Person;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. public class Main {
  6. public static void main(String[] args) throws BeansException {
  7. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  8. Person person = (Person) context.getBean("person");
  9. System.out.println(person);
  10. }
  11. }

这样就创建了一个person对象,并且person的address属性已经有值了。

IoC容器

在没有Spring的时候,创建Person以及Address类的实例是我们手动创建的。而在使用Spring后创建Person以及Address类的实例交给Spring来管理了,spring决定在什么时候创建以及怎么创建,把这个控制权都交给了Spring进行管理。在Spring核心配置文件里面的所有Bean都放在一个容器里面,这个容器叫IoC容器。
这篇文章比较通俗详细介绍了控制反转的内容

依赖注入(Dependency Injection)

可以理解设置实例对象的属性。比如Person类有个属性address。我需要给address属性赋值,赋值的过程就叫注入。
比如我们通过下面的配置

  1. <bean id="person" class="com.lff.Person">
  2. <property name="address" ref="address" />
  3. </bean>

调用Person类address属性的setter方法

  1. public void setAddress(Address address) {
  2. this.address = address;
  3. }

把配置文件的property引用的实例注入到上面的setAddress方法里面,这样属性address就有值了。这种注入方式我们称为Setter属性方式注入,还有一种就是constructor构造方法的方式注入(这种我们后面会讲到)。
我们改造Person类,对不同类型的属性进行注入

  1. package com.lff;
  2. import java.math.BigDecimal;
  3. import java.util.List;
  4. import java.util.Map;
  5. import java.util.Properties;
  6. import java.util.Set;
  7. public class Person {
  8. //注入基本类型
  9. private int age;
  10. private String name;
  11. private BigDecimal money;
  12. //注入集合类型(数组、Map、List、Set、Properties)
  13. private String[] aliNamesArray;
  14. private List[] aliNamesList;
  15. private Map<String,String> aliNamesMap;
  16. private Set[] aliNamesSet;
  17. private Properties properties;
  18. //注入Bean自定义类型
  19. private Address address;
  20. public int getAge() {
  21. return age;
  22. }
  23. public void setAge(int age) {
  24. this.age = age;
  25. }
  26. public String getName() {
  27. return name;
  28. }
  29. public void setName(String name) {
  30. this.name = name;
  31. }
  32. public BigDecimal getMoney() {
  33. return money;
  34. }
  35. public void setMoney(BigDecimal money) {
  36. this.money = money;
  37. }
  38. public String[] getAliNamesArray() {
  39. return aliNamesArray;
  40. }
  41. public void setAliNamesArray(String[] aliNamesArray) {
  42. this.aliNamesArray = aliNamesArray;
  43. }
  44. public List[] getAliNamesList() {
  45. return aliNamesList;
  46. }
  47. public void setAliNamesList(List[] aliNamesList) {
  48. this.aliNamesList = aliNamesList;
  49. }
  50. public Map<String, String> getAliNamesMap() {
  51. return aliNamesMap;
  52. }
  53. public void setAliNamesMap(Map<String, String> aliNamesMap) {
  54. this.aliNamesMap = aliNamesMap;
  55. }
  56. public Set[] getAliNamesSet() {
  57. return aliNamesSet;
  58. }
  59. public void setAliNamesSet(Set[] aliNamesSet) {
  60. this.aliNamesSet = aliNamesSet;
  61. }
  62. public Properties getProperties() {
  63. return properties;
  64. }
  65. public void setProperties(Properties properties) {
  66. this.properties = properties;
  67. }
  68. public Address getAddress() {
  69. return address;
  70. }
  71. public void setAddress(Address address) {
  72. this.address = address;
  73. }
  74. }

核心配置文件如下

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="address" class="com.lff.Address">
  6. <property name="city" value="BeiJing"/>
  7. </bean>
  8. <bean id="person" class="com.lff.Person">
  9. <!-- 注入Bean-->
  10. <property name="address" ref="address" />
  11. <!-- 注入基本类型-->
  12. <property name="name" value="lifufa" />
  13. <property name="money" value="100.00" />
  14. <property name="age" value="20" />
  15. <!-- 注入集合类型-->
  16. <property name="aliNamesArray" >
  17. <array>
  18. <value>jack</value>
  19. <value>rose</value>
  20. <value>Cliff</value>
  21. </array>
  22. </property>
  23. <property name="aliNamesList">
  24. <list>
  25. <value>jack</value>
  26. <value>rose</value>
  27. <value>Cliff</value>
  28. </list>
  29. </property>
  30. <property name="aliNamesMap">
  31. <map>
  32. <entry key="jack" value="jack" />
  33. <entry key="rose" value="rose" />
  34. <entry key="xiao" value="xiao" />
  35. </map>
  36. </property>
  37. <property name="properties">
  38. <props>
  39. <prop key="haha">哈哈</prop>
  40. <prop key="hehe">呵呵</prop>
  41. <prop key="heihei">嘿嘿</prop>
  42. </props>
  43. </property>
  44. <property name="aliNamesSet">
  45. <set>
  46. <value>jack</value>
  47. <value>rose</value>
  48. <value>cliff</value>
  49. </set>
  50. </property>
  51. </bean>
  52. </beans>

外界使用

  1. import com.lff.Person;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. public class Main {
  6. public static void main(String[] args) throws BeansException {
  7. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  8. Person person = (Person) context.getBean("person");
  9. System.out.println(person);
  10. }
  11. }

image.png

还有的人是这样写的,在xml根标签加p标签xmlns:p=”http://www.springframework.org/schema/p”

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:p="http://www.springframework.org/schema/p"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="address" class="com.lff.Address" p:city="Beijing" />
  7. <bean id="person" class="com.lff.Person"
  8. p:name="lifufa" p:age="20" p:money="100.00"
  9. p:address-ref="address" />
  10. </beans>

写法上会简单一点
上面使用的是Setter属性的方式注入,如果想使用构造函数的方式注入核心配置文件应该怎么写呢?

比如下面的Person类

  1. package com.lff;
  2. public class Person {
  3. //注入基本类型
  4. private int age;
  5. private String name;
  6. public int getAge() {
  7. return age;
  8. }
  9. public void setAge(int age) {
  10. this.age = age;
  11. }
  12. public String getName() {
  13. return name;
  14. }
  15. public void setName(String name) {
  16. this.name = name;
  17. }
  18. public Person(int age, String name) {
  19. this.age = age;
  20. this.name = name;
  21. }
  22. }

使用XML的方式

  1. <bean id="person" class="com.lff.Person">
  2. <constructor-arg index="0" value="20" />
  3. <constructor-arg index="1" value="lifufa" />
  4. </bean>

使用index标识传入构造函数哪个位置。

相对复杂的方式创建对象

在很多情况下,创建对象并不是简单的new一下,而是通过比较复杂的方式创建或者通过别人写好的方式去调用获取一个对象。

使用工厂方法进行依赖注入

静态工厂

比如创建Person对象,我们使用以下工厂创建

  1. package com.lff;
  2. public class PersonFactory {
  3. //类方法,使用类来调用
  4. public static Person getPerson(){
  5. return new Person(20,"hahaha");
  6. }
  7. //实例方法,通过PersonFactory的实例来调用
  8. public Person getPersonInstance(){
  9. return new Person(18,"哈哈");
  10. }
  11. }

配置文件,我们添加factory-method工厂方法

  1. <bean id="person" class="com.lff.PersonFactory" factory-method="getPerson"/>

外界使用person这个id的时候,就会调用PersonFactory类的getPerson方法

外界使用

  1. public class Main {
  2. public static void main(String[] args) throws BeansException {
  3. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  4. Person person = (Person) context.getBean("person");
  5. System.out.println(person);
  6. }
  7. }

实例工厂

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <!-- <bean id="person" class="com.lff.PersonFactory" factory-method="getPerson"/>-->
  6. <!-- 这种情况会创建PersonFactory的实例-->
  7. <bean id="personFactory" class="com.lff.PersonFactory" />
  8. <!-- 通过factory-bean拿到PersonFactory的实例,调用factory-method的方法-->
  9. <bean id="person" factory-bean="personFactory" factory-method="getPersonInstance" />
  10. </beans>

通过FactoryBean接口创建对象

实现FactoryBean接口,实现接口的时候需要一个泛型,泛型的内容就是要创建的实例类型

  1. package com.lff;
  2. import org.springframework.beans.factory.FactoryBean;
  3. public class PersonFactoryBean implements FactoryBean<Person> {
  4. @Override
  5. public Person getObject() throws Exception { //返回创建的对象
  6. return new Person(18,"哈哈");
  7. }
  8. @Override
  9. public Class<?> getObjectType() { //创建对象的类型
  10. return Person.class;
  11. }
  12. }

xml配置直接写这样就可以了,因为已经实现了FactoryBean这个接口

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <!-- 以前这样写会创建PersonFactoryBean类型的对象,但是这里由于PersonFactoryBean已经实现了FactoryBean接口,所以外界通过id取的时候,会调用FactoryBean接口的getObject()方法-->
  6. <bean id="person" class="com.lff.PersonFactoryBean" />
  7. </beans>

外界使用

  1. public class Main {
  2. public static void main(String[] args) throws BeansException {
  3. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  4. Person person = (Person) context.getBean("person");
  5. System.out.println(person);
  6. }
  7. }

上面就获取了一个person对象。
当然如果你确实想返回对应的工厂实例而不是person对象,需要这样获取

  1. public class Main {
  2. public static void main(String[] args) throws BeansException {
  3. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  4. System.out.println(context.getBean("&person")); //这里person前面加了一个&符合,Spring内部会获取对应工厂实例
  5. }
  6. }

Spring配置文件中引入外部配置文件

新建配置文件user.properties

  1. name=lifufa
  2. age=20

xml配置

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  6. http://www.springframework.org/schema/context
  7. http://www.springframework.org/schema/context/spring-context.xsd">
  8. <context:property-placeholder location="user.properties" />
  9. <bean id="person" class="com.lff.Person">
  10. <property name="name" value="${name}" />
  11. <property name="age" value="${age}" />
  12. </bean>
  13. </beans>