前置类的更改
src/main/java/pojo/IdCard
package pojo;
import java.io.Serializable; import java.util.Objects;
public class IdCard implements Serializable { private Integer id; private String idNo; private String address;
public IdCard() { }
@Override public String toString() { return "IdCard{" + "id=" + id + ", idNo='" + idNo + '\'' + ", address='" + address + '\'' + '}'; }
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; IdCard idCard = (IdCard) o; return Objects.equals(id, idCard.id) && Objects.equals(idNo, idCard.idNo) && Objects.equals(address, idCard.address); }
@Override public int hashCode() { return Objects.hash(id, idNo, address); }
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getIdNo() { return idNo; }
public void setIdNo(String idNo) { this.idNo = idNo; }
public String getAddress() { return address; }
public void setAddress(String address) {this.address = address;} }
|
src/main/java/pojo/People
package pojo;
import java.io.Serializable; import java.util.Objects;
public class People implements Serializable { private Integer id; private String name; private IdCard idCard;
public People() { System.out.println("无参数构造方法"); }
public People(IdCard idCard) { System.out.println("(IdCard idCard)参数构造方法"); this.idCard = idCard; }
public People(Integer id, String name, IdCard idCard) { System.out.println("(Integer id, String name, IdCard idCard) 参数构造方法"); this.id = id; this.name = name; this.idCard = idCard; }
@Override public String toString() { return "People{" + "id=" + id + ", name='" + name + '\'' + ", idCard=" + idCard + '}'; }
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; People people = (People) o; return Objects.equals(id, people.id) && Objects.equals(name, people.name) && Objects.equals(idCard, people.idCard); }
@Override public int hashCode() { return Objects.hash(id, name, idCard); }
public void init(){ System.out.println("初始化方法运行。"); }
public Integer getId() { return id; }
public void setId(Integer id) { System.out.println("setId方法运行"); this.id = id; }
public String getName() { return name; }
public void setName(String name) { System.out.println("setName方法运行"); this.name = name; }
public IdCard getIdCard() { return idCard; }
public void setIdCard(IdCard idCard) { System.out.println("setIdCart方法运行:" + idCard); this.idCard = idCard; } }
|
自动注入(自动装配)
被 Spring 容器管理的若干个 Bean 对象,如果对象之间有关系,可以实现自动装配。
自动装配:按照某种指定的规则,自动把容器种管理的 bean 对象,做赋值注入。
自动装配规则配置: 可以有两个位置,分别是根标签的 beans 属性和 bean 标签的属性。
根标签的属性配置是全局自动装配,bean 标签的是局部自动装配。都配置,局部优先。
default-autowire
在根标签 <beans>
中配置 default-autowire 属性。标签整个 Spring 中自动注入的策略。可取值有 5 个。
default:默认值。不自动注入。
no:不自动注入。
byName:通过名称自动注入。按照 bean 对象中的属性名,自动寻找容器中与当前属性同名的 bean 进行注入。如果同名 bean 的类型不匹配,抛出异常。
byType:通过类型自动注入。按照 bean 对象中的属性类型,自动寻找容器中与当前 bean 类型匹配的 bean 进行注入。如果有多个相同类型的 bean 注入会出现异常。
constructor:通过构造方法进行注入。
按照 bean 对象的构造方法,从容器中找构造参数同类型或同名称的 bean 对象。如果有自动注入进去。类型先 byType,如果同类型的 bean 有多个,再 byName,如果没有同名的,则不使用构造方法自动装配。
注意:构造方法类型和其他 Bean 的类型相同。
src/main/resources/applicationContext.xml
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byType"> <bean id="people" class="pojo.People"> <property name="id" value="10"/> <property name="name" value="张三"/> </bean> <bean id="idCard" class="pojo.IdCard"> <property name="id" value="1"/> <property name="idNo" value="1"/> <property name="address" value="监狱"/> </bean> </beans>
|
在测试类中测试效果
src/test/java/TestAutowired
import pojo.People; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAutowired { @Test public void testAutowired(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); People people = context.getBean("people", People.class); System.out.println(people); } }
|
由于 id 为 people 的 bean 中包含 IdCard 类型的 idCard 属性,类型相同,所以也会将 id 为 idCard 的 bean 也自动装填。
若是 byName ,则会去 bean 寻找 id 为 idCard 这个 bean 对象,找不到就返回 null。
输出:
无参数构造方法 setId方法运行 setName方法运行 setIdCart方法运行:IdCard{id=1, idNo='1', address='监狱'} People{id=10, name='张三', idCard=IdCard{id=1, idNo='1', address='监狱'}}
|
测试一下 constructor:
src/main/resources/applicationContext.xml
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="constructor"> <bean id="people" class="pojo.People"> <property name="id" value="10"/> <property name="name" value="张三"/> </bean> <bean id="idCard" class="pojo.IdCard"> <property name="id" value="1"/> <property name="idNo" value="1"/> <property name="address" value="监狱"/> </bean> </beans>
|
输出:
(IdCard idCard)参数构造方法 setId方法运行 setName方法运行 People{id=10, name='张三', idCard=IdCard{id=1, idNo='1', address='监狱'}}
|
会发现相比较与 byName 调用无参构造方法 ,constructor 调用的是只有一个参数的构造方法。
如果将其注释掉则会调用无参构造方法并赋值为 null 而不是调用多个参数的构造方法。
autowire
在 <bean>
标签中配置 autowire 属性,和 default-autowire 取值相同。
当前 bean 对象的自动装配方案,局部优先。默认是 default。
唯一注意 default 表示全局 default-autowire 的值。如果 autowire 和 default-autowire 同时存在,autowire 生效。
src/main/resources/applicationContext.xml
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="constructor"> <bean id="people" class="pojo.People" autowire="byName"> <property name="id" value="10"/> <property name="name" value="张三"/> </bean> <bean id="idCard" class="pojo.IdCard" autowire="byName"> <property name="id" value="1"/> <property name="idNo" value="1"/> <property name="address" value="监狱"/> </bean> </beans>
|
注意:自动注入指的都是 bean 之间的自动注入。能够自动注入必须保证 Spring 容器中包含能被自动注入的 bean
在测试类中测试效果
测试类与上文一致
输出:
无参数构造方法 setId方法运行 setName方法运行 setIdCart方法运行:IdCard{id=1, idNo='1', address='监狱'} People{id=10, name='张三', idCard=IdCard{id=1, idNo='1', address='监狱'}}
|
因为局部优先,调用 byName 所以不调用有参构造。
bean 标签的 scope 属性
Spring 中 <bean>
的 scope 控制的是 bean 的有效范围。
一共有 6 个可取值:
singleton:默认值。单例。一个 bean 标签一个 bean 对象,同一标签调用 getbean 方法每次获取 bean 都是同一个对象。
prototype:多例。每次获取 bean 都重新实例化,即同一标签调用 getbean 方法每次获取 bean 是不同对象。
request(Spring - web.jar):每次请求重新实例化对象,同一个请求中多次获取时是单例的。
session(Spring - web.jar):每个会话内 bean 是单例的。
application(Spring - web.jar):整个应用程序对象内 bean 是单例的。
websocket(Spring - websocket.jar):同一个 websocket 对象内对象是单例的。
里面的 singleton 和 prototype 在 Spring 最基本的环境中就可以使用,不需要 web 环境(需要添加依赖)。
但是里面的 request、session、application、websocket 都只有在 web 环境才能使用。
测试一下前两个:
singleton
默认情况下,在 ApplicationContext 创建结束时,bean 对象就都创建完成了。
src/main/resources/applicationContext.xml
<bean id="people" class="pojo.People" scope="singleton">
|
src/test/java/TestAutowired
import pojo.People; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAutowired { @Test public void testAutowired(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println("Spring容器创建完毕"); System.out.println(context.getBean("people", People.class)); System.out.println(context.getBean("people", People.class)); } }
|
输出:
无参数构造方法 setId方法运行 setName方法运行 Spring容器创建完毕 People{id=10, name='张三', idCard=null} People{id=10, name='张三', idCard=null}
|
只回显了一次 “无参数构造方法” 说明只调用一次,只有一个对象。
可以使用 bean 标签的属性 lazy-init = “true”,让 ApplicationContext 创建时,不初始化 bean 对象。
lazy-init 默认是 default,采用全局的,就是 false。可选值有 false 和 true。
false:不延迟初始化,ApplicationContext 创建时,就初始化 bean 对象。
true:延迟初始化,getBean 方法调用时,才初始化 bean 对象。
src/main/resources/applicationContext.xml
<bean id="people" class="pojo.People" scope="singleton" lazy-init="true">
|
输出:
Spring容器创建完毕 无参数构造方法 setId方法运行 setName方法运行 People{id=10, name='张三', idCard=null} People{id=10, name='张三', idCard=null}
|
prototype
在 ApplicationContext 创建结束时,不会创建这个 scope 有效范围的 bean 对象。
直到 getBean 方法被调用,才创建 bean 对象,getBean 方法调用多少次,创建多少对象。
极少的情况下,需要用多例。
src/main/resources/applicationContext.xml
<bean id="people" class="pojo.People" scope="prototype">
|
输出:
Spring容器创建完毕 无参数构造方法 setId方法运行 setName方法运行 People{id=10, name='张三', idCard=null} 无参数构造方法 setId方法运行 setName方法运行 People{id=10, name='张三', idCard=null}
|
只回显了两次 “无参数构造方法” 说明多次调用,有多个对象。
bean 对象初始化方法
init-method="方法名"
当 bean 对象创建,且属性注入后,调用的初始化方法。
src/main/resources/applicationContext.xml
<bean id="people" class="pojo.People" scope="singleton" lazy-init="false" init-method="init">
|
输出:
无参数构造方法 setId方法运行 setName方法运行 初始化方法运行。 Spring容器创建完毕 People{id=10, name='张三', idCard=null} People{id=10, name='张三', idCard=null}
|
destroy-method
当 bean 对象被销毁前,执行的方法。一般用于回收资源。
单例的 bean 对象。是在 spring 容器 ApplicationContext 销毁的时候,才会销毁。
单例设计模式
设计模式:根据面向对象五大设计思想衍生出的 23 中常见代码写法,每种写法可以专门解决一类问题。
单例设计模式:保证某个类在整个应用程序中只有一个对象。
单例写法分为两种:饿汉式、懒汉式。
饿汉式和懒汉式的选择:
如果不知道该如何选择,优先使用饿汉式。
饿汉式:
优点:没有线程安全隐患。
缺点:只要类加载,不管是否需要那个单例的对象,都会创建,对内存压力相对较高。
懒汉式:
优点:不使用单例对象,就不创建单例对象。对内存的压力相对较低。
缺点:需要考虑线程安全隐患。
饿汉式
饿汉式:当类加载的时候,就把单例对象创建好了。
src/main/java/module/Singleton1
package module;
public class Singleton1 { private Singleton1(){} private static Singleton1 OBJECT = new Singleton1(); public static Singleton1 getInstance(){ return OBJECT; } }
|
在测试类中测试效果
src/test/java/TestSingleton
import module.Singleton1; import module.Singleton2; import org.junit.Test;
public class TestSingleton { @Test public void testSingleton1(){ Singleton1 s1 = Singleton1.getInstance(); Singleton1 s2 = Singleton1.getInstance(); System.out.println(s1 == s2); } }
|
懒汉式
懒汉式: 当要获取对象的时候,再去创建唯一的单例对象。
src/main/java/module/Singleton2
package module;
public class Singleton2 { private Singleton2(){} private static Singleton2 OBJECT; public static Singleton2 getInstance(){ if(OBJECT == null){ synchronized (Singleton2.class){ if(OBJECT == null){ OBJECT = new Singleton2(); } } } return OBJECT; } }
|
在测试类中测试效果
src/test/java/TestSingleton
import module.Singleton1; import module.Singleton2; import org.junit.Test;
public class TestSingleton { @Test public void testSingleton1(){ Singleton1 s1 = Singleton1.getInstance(); Singleton1 s2 = Singleton1.getInstance(); System.out.println(s1 == s2); } }
|
Spring 循环注入
循环注入即多个类相互依赖,产生了一个闭环。
当两个类都是用构造注入时,没有等当前类实例化完成就需要注入另一个类,而另一个类没有实例化完整还需要注入当前类,所以这种情况是无法解决循环注入问题的的。会出现 BeanCurrentlyInCreationException 异常。
如果两个类都使用设值注入且 scope 为 singleton 的就不会出现问题,可以正常执行。因为单例默认下有三级缓存(DefaultSingletonBeanRegistry),可以暂时缓存没有被实例化完成的 Bean。
但是如果两个类的 scope 都是 prototype 依然报 BeanCurrentlyInCreationException 异常。
测试一下:
src/main/java/pojo/A
package pojo;
import java.io.Serializable; import java.util.Objects;
public class A implements Serializable { private String aName; private B b;
public A(String aName, B b) { this.aName = aName; this.b = b; }
public A() { System.out.println("A无参构造运行"); }
@Override public String toString() { return "A{" + "aName='" + aName + '\'' + '}'; }
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; A a = (A) o; return Objects.equals(aName, a.aName) && Objects.equals(b, a.b); }
@Override public int hashCode() { return Objects.hash(aName, b); }
public String getaName() { return aName; }
public void setaName(String aName) { this.aName = aName; }
public B getB() { return b; }
public void setB(B b) { System.out.println("a.setB运行"); this.b = b; } }
|
src/main/java/pojo/B
package pojo;
import java.io.Serializable; import java.util.Objects;
public class B implements Serializable { private String bName; private A a;
public B(String bName, A a) { this.bName = bName; this.a = a; }
public B() { System.out.println("B无参构造运行"); }
@Override public String toString() { return "B{" + "bName='" + bName + '\'' + '}'; }
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; B b = (B) o; return Objects.equals(bName, b.bName) && Objects.equals(a, b.a); }
@Override public int hashCode() { return Objects.hash(bName, a); }
public String getbName() { return bName; }
public void setbName(String bName) { this.bName = bName; }
public A getA() { return a; }
public void setA(A a) { System.out.println("b.setA运行"); this.a = a; } }
|
src/main/resources/applicationContext.xml
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="default"> <bean id="a" class="pojo.A" scope="singleton"> <property name="aName" value="aaa"></property> <property name="b" ref="b"></property> </bean> <bean id="b" class="pojo.B" scope="singleton"> <property name="bName" value="bbb"></property> <property name="a" ref="a"></property> </bean> </beans>
|
src/test/java/TestCyc
import pojo.A; import pojo.B; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestCyc { @Test public void testCyc(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println("ApplicationContext创建结束"); A a = context.getBean("a", A.class); System.out.println(a); System.out.println(a.getB());
System.out.println("=========================");
B b = context.getBean("b", B.class); System.out.println(b); System.out.println(b.getA()); } }
|
输出:
A无参构造运行 B无参构造运行 b.setA运行 a.setB运行 ApplicationContext创建结束 A{aName='aaa'} B{bName='bbb'} ========================= B{bName='bbb'} A{aName='aaa'}
|
Spring 整合 web
Spring 项目不需要依赖 Web 环境,但是 Java 项目大多数是 Web 项目,所以 Spring 也支持集成到 Web 环境中。
Spring 集成 Web 环境是通过 Listener 实现的,在 ServletContext 对象创建时加载 Spring 容器。Spring 已经在 spring-web.jar 包中提供了 ContextLoaderListener 实现加载 Spring 配置文件的代码。我们只需要在 web.xml 配置 <listener>
标签让 ContextLoaderListener 生效,并且告诉 ContextLoaderListener 加载 Spring 配置文件的路径即可。
创建项目,并添加依赖
创建 Maven 的 web 项目(添加上 webapp 目录等,并配置 web 模块),并在 pom.xml 配置依赖。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>MySpring</artifactId> <groupId>MySpring</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<artifactId>MySpring_3</artifactId>
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.20</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.20</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <path>/</path> <port>8080</port> </configuration> </plugin> </plugins> </build> </project>
|
2. 编写类
随意创建一个类,为了测试是否能从容器中获取到 bean
创建 src/main/java/People
public class People { private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "People{" + "id=" + id + '}'; } }
|
3.编写 Spring 配置文件
建议:建立上 applicationContext.xml 的模板。
里面创建 People 的 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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="people" class="People"> <property name="id" value="123"></property> </bean> </beans>
|
4. 配置 web.xml
在 web.xml 中配置监听器,让 web 项目启动时自动创建 Spring 容器对象( WebApplicationContext )。
上下文参数名 param-name 的值是固定的 contextConfigLocation。这个值是监听器需要获取的值。
上下文参数值需要带有 classpath,表示加载类路径内容。target/classes 目录内容。如果写成 classpath*:
表示当前项目依赖的 jar 中资源也会寻找。classpath:
后面的值支持星号。例如 applicationContext-*.xml
表示加载所有以 applicationContext-
开头的 xml 文件。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
</web-app>
|
5.编写Servlet
创建 src/main/java/DemoServlet
WebApplicationContextUtils 是工具类,可以快速获取到容器对象。
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import service.UserService;
import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
@WebServlet("/demo") public class DemoServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { WebApplicationContext wac = WebApplicationContextUtils.findWebApplicationContext(req.getServletContext()); People peo = wac.getBean("peo", People.class); System.out.println(peo); } }
|
6.访问测试
访问:http://localhost:8080/demo 观察 IDEA 控制台是否输出 people 对象的值。