一、入門案例
1.導入依賴:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
2.編寫beans.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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
bean 標簽:用于配置讓 spring 創建對象,并且存入 ioc 容器之中
id 屬性:對象的唯一標識。
class 屬性:指定要創建對象的全限定類名
-->
<!-- 配置 service -->
<bean id="userService" class="com.hcx.service.impl.UserSerivceImpl"></bean>
<!-- 配置 dao -->
<bean id="userDao" class="com.hcx.dao.impl.UserDaoImpl"></bean>
</beans>
3.使用
public class UserController {
public static void main(String[] args) {
//獲取核心容器
//ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");//更常用
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\config\\beans.xml");
//根據id獲取Bean對象
//方式一
UserService userService = (UserService) applicationContext.getBean("userService");
//方式二
UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
//com.hcx.service.impl.UserSerivceImpl@3e6fa38a
System.out.println(userService);
//com.hcx.dao.impl.UserDaoImpl@66a3ffec
System.out.println(userDao);
// UserService userService = (UserService) BeanFactory.getBean("userService");
// userService.saveUser();
}
}
二、重要類詳解
1.BeanFactory
在構建核心容器時,創建對象采取的策略是延遲加載。即根據id獲取對象時才創建對象。
適用場景:多例對象
public class UserController {
public static void main(String[] args) {
Resource resource = new ClassPathResource("beans.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
UserService userService = (UserService) beanFactory.getBean("userService");
System.out.println(userService);
}
}
2.ApplicationContext
在構建核心容器時,創建對象采取的是立即加載的方式。即一讀取完配置文件就馬上創建對象。
適用場景:單例對象
public class UserController {
public static void main(String[] args) {
//獲取核心容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// ApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\config\\beans.xml");
//根據id獲取Bean對象
//方式一
UserService userService = (UserService) applicationContext.getBean("userService");
//方式二
UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
//com.hcx.service.impl.UserSerivceImpl@3e6fa38a
System.out.println(userService);
//com.hcx.dao.impl.UserDaoImpl@66a3ffec
System.out.println(userDao);
}
}
ApplicationContext的三個常用實現類:
①ClassPathXmlApplicationContext
可以加載類路徑下的配置文件,要求配置文件必須在類路徑下,否則無法加載。
②FileSystemXmlApplicationContext
可以加載磁盤任意路徑下的配置文件(須有訪問權限)
③AnnotationConfigApplicationContext
用于讀取注解創建的容器
三、Bean詳解
1.創建Bean對象的三種方式
①方式一:采用默認構造函數
<bean id="userService" class="com.hcx.service.impl.UserSerivceImpl"></bean>
spring配置文件中使用bean標簽,僅配置id和class屬性(沒有其他屬性和標簽),此時采用默認構造函數創建對象,如果類中沒有默認構造函數,則對象無法創建。
<!--
bean 標簽:用于配置讓 spring 創建對象,并且存入 ioc 容器之中
id 屬性:對象的唯一標識。
class 屬性:指定要創建對象的全限定類名
-->
<!-- 配置 service -->
<bean id="userService" class="com.hcx.service.impl.UserSerivceImpl"></bean>
②方式二:使用普通工廠中的方法創建對象
即使用某個類中的方法創建對象,并存入spring容器
當使用一些外部類時,如引入的jar中的類,都是字節碼文件。
InstanceFactory:
/**
* 模擬工廠類(該類可能存在于jar包中,無法修改代碼提供默認構造函數)
*/
public class InstanceFactory {
public UserService getUserService(){
return new UserSerivceImpl();
}
}
此時要獲取的是InstanceFactory 類中的getUserService返回值
<!--得到工廠實例-->
<bean id="instanceFactory" class="com.hcx.factory.InstanceFactory"></bean>
<!--
factory-bean:指定工廠bean
factory-method:獲取對象的方法名
userService對象由instanceFactory id所指向的工廠中的getUserService方法獲取
-->
<bean id="userService" factory-bean="instanceFactory" factory-method="getUserService"></bean>
③方式三:使用工廠中的靜態方法創建對象
使用某個類中的靜態方法,并存入spring容器
StaticFactory:
public class StaticFactory {
public static UserService getUserService(){
return new UserSerivceImpl();
}
}
xml配置:
<bean id="userService" class="com.hcx.factory.StaticFactory" factory-method="getUserService"></bean>
④注解方式:
作用和xml配置的<bean>
標簽功能一樣
@Component
:把當前類對象存入spring容器中
屬性value用于指定bean的id,不寫時默認為類名首字母小寫
@Controller
:一般用于表現層
@Service
:一般用于業務層
@Repository
:一般用于持久層
使用注解的方式,需要加入context命名空間,并且要配置告訴spring使用注解的方式,需要spring去掃描對應的包,配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 告知 spring 創建容器時要掃描的包 -->
<context:component-scan base-package="com.hcx"></context:component-scan>
</beans>
2.bean對象的作用范圍
①xml配置:
默認情況spring創建的bean都是單例的。
bean的作用范圍通過bean標簽的scope屬性可以指定:
取值:
- singleton:單例(默認值)
- prototype:多例
- request:作用于web應用的請求范圍
- session:作用于web應用的會話范圍
- global-session:作用于集群環境的會話范圍(全局會話范圍),當不是集群環境時,等同于session
<bean id="userService" class="com.hcx.service.impl.UserSerivceImpl" scope="singleton"></bean>
②注解配置:
@Scope:
指定bean的作用范圍
屬性value:指定范圍的取值,常用取值:singleton
、prototype
3.bean對象的生命周期
①單例對象
生命周期跟容器的相同,讓其創建時對象創建,只要容器還在,對象一直存活,容器銷毀,對象死亡。
通過init-method
和destory-method
屬性指定
UserSerivceImpl:
public class UserSerivceImpl implements UserService {
public UserSerivceImpl() {
System.out.println("對象創建了");
}
public void saveUser() {
System.out.println("service實現類被調用了");
// userDao.saveUser();
}
public void init() {
System.out.println("對象初始化");
}
public void destory() {
System.out.println("對象銷毀");
}
}
xml:
<bean id="userService" class="com.hcx.service.impl.UserSerivceImpl" scope="singleton"
init-method="init" destroy-method="destory"></bean>
調用:
public class UserController {
public static void main(String[] args) {
//獲取核心容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//根據id獲取Bean對象
//方式一
UserService userService = (UserService) applicationContext.getBean("userService");
//方式二
// UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
userService.saveUser();
//手動關閉容器
applicationContext.close();
}
}
結果:
對象創建了
對象初始化
service實現類被調用了
對象銷毀
②多例對象
出生:使用對象時spring框架創建
存活:對象只要在使用則一直存活
死亡:當對象長時間不用且沒有其他對象引用時,由垃圾回收器回收
即使手動關閉容器,對象也不會銷毀
<bean id="userService" class="com.hcx.service.impl.UserSerivceImpl" scope="prototype"
init-method="init" destroy-method="destory"></bean>
打印結果:
對象創建了
對象初始化
service實現類被調用了
③使用注解指定生命周期
作用與bean標簽中使用init-method
和destroy-method
作用相同
PostConstruct:
用于指定初始化方法
PreDestroy:
用于指定銷毀方法
UserSerivceImpl :
@Service("userService")
public class UserSerivceImpl implements UserService {
@Resource(name = "userDao1")
private UserDao userDao;
@PostConstruct
public void init(){
System.out.println("初始化方法");
}
@PreDestroy
public void destroy(){
System.out.println("銷毀方法");
}
public void saveUser() {
userDao.saveUser();
}
}
調用:
public class UserController {
@Autowired
private UserService userService;
public static void main(String[] args) {
//獲取核心容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//根據id獲取Bean對象
UserService userService = (UserService) applicationContext.getBean("userService");
userService.saveUser();
//手動關閉才會調用銷毀方法
applicationContext.close();
}
}
打印結果:
初始化方法
保存用戶1
銷毀方法
四、Spring的依賴注入
依賴注入:Dependency Injection。它是 spring 框架核心 ioc 的具體實現
程序在編寫時,通過控制反轉,把對象的創建交給了 spring,但是代碼中不可能出現沒有依賴的情況。
ioc 解耦只是降低他們的依賴關系,但不會消除。
例如:我們的業務層仍會調用持久層的方法。
這種業務層和持久層的依賴關系,在使用 spring 之后,就讓 spring 來維護了。
簡單的說,就是框架把持久層對象傳入業務層,而不用我們自己去獲取。
依賴關系的管理:交由spring來維護
當前類需要用到其他類對象時,由spring為我們提供,我們只需要在配置文件中說明。
能注入的數據類型:
- 基本類型和String
- 其他bean類型(在配置文件或者注解配置過的bean)
- 復雜類型(集合類型)
注入的方式:
- 使用構造器
- 使用set方法
- 使用注解
注意:經常變化的一些數據不適用于注入的方式
1.構造函數注入
在bean標簽中使用標簽constructor-arg
,屬性:
- type:指定要注入的數據的數據類型,該數據類型是構造函數中某個或某些參數的數據類型
- index:指定給構造函數中指定索引位置的參數賦值。(索引從0開始)
- name:指定給構造函數中指定名稱的參數賦值
- value:賦的值是基本數據類型和 String 類型
- ref:賦的值是其他 bean 類型(必須是在配置文件中配置過的 bean)
注意:
type
、index
、name
用于指定給構造函數中的哪個參數賦值
UserSerivceImpl:
public class UserSerivceImpl implements UserService {
private String name;
private Integer age;
private Date birthday;
public UserSerivceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public void saveUser() {
System.out.println("service實現類被調用了:"+name+","+age+","+birthday);
}
}
beans.xml
<bean id="userService" class="com.hcx.service.impl.UserSerivceImpl">
<constructor-arg name="name" value="極多人小紅"></constructor-arg>
<constructor-arg name="age" value="24"></constructor-arg>
<constructor-arg name="birthday" ref="date"></constructor-arg>
</bean>
<!--配置日期對象-->
<bean id="date" class="java.util.Date"></bean>
使用:
public class UserController {
public static void main(String[] args) {
//獲取核心容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//根據id獲取Bean對象
UserService userService = (UserService) applicationContext.getBean("userService");
userService.saveUser();
}
}
結果:
service實現類被調用了:極多人小紅,24,Wed Dec 11 20:09:09 CST 2019
此種方式的優缺點:
優點:在獲取bean對象時,注入數據是必須的操作,否則無法創建對象(如果同時提供無參構造器則沒問題)
缺點:改變了bean對象的實例化方式,使得在創建對象時,即使不使用這些數據也必須提供。
2.set 方法注入
通過配置文件給 bean 中的屬性傳值:使用 set 方法的方式,
property標簽:
- name:找的是類中 set 方法后面的部分(并不是屬性的名稱)
- ref:給屬性賦值是其他 bean 類型的
- value:給屬性賦值是基本數據類型和 string 類型的
UserSerivceImpl:
public class UserSerivceImpl implements UserService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void saveUser() {
System.out.println("service實現類被調用了:"+name+","+age+","+birthday);
}
}
beans.xml:
<!-- 配置 service -->
<bean id="userService" class="com.hcx.service.impl.UserSerivceImpl">
<property name="name" value="極多人小紅"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="date"></property>
</bean>
<!--配置日期對象-->
<bean id="date" class="java.util.Date"></bean>
優點:創建對象時沒有明確的限制,可以直接使用默認構造函數
缺點:如果某個成員必須有值,則獲取對象時有可能set方法沒有執行。
復雜類型注入
用于給List結構注入的標簽包括:list
、array
、set
用于給Map結構集合注入的標簽包括:map
、properties
即結構相同的數據類型,標簽都通用。
UserSerivceImpl:
public class UserSerivceImpl implements UserService {
private String[] strArrs;
private List<String> strList;
private Set<String> strSet;
private Map<String,String> strMap;
private Properties properties;
public void setStrArrs(String[] strArrs) {
this.strArrs = strArrs;
}
public void setStrList(List<String> strList) {
this.strList = strList;
}
public void setStrSet(Set<String> strSet) {
this.strSet = strSet;
}
public void setStrMap(Map<String, String> strMap) {
this.strMap = strMap;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void saveUser() {
System.out.println("service實現類被調用了");
System.out.println(Arrays.toString(strArrs));
System.out.println(strList);
System.out.println(strSet);
System.out.println(strMap);
System.out.println(properties);
}
}
beans.xml:
<bean id="userService" class="com.hcx.service.impl.UserSerivceImpl">
<property name="strArrs">
<array>
<value>arr1</value>
<value>arr2</value>
<value>arr3</value>
</array>
</property>
<property name="strList">
<list>
<value>list1</value>
<value>list2</value>
<value>list3</value>
</list>
</property>
<property name="strSet">
<set>
<value>set1</value>
<value>set2</value>
<value>set3</value>
</set>
</property>
<property name="strMap">
<map>
<entry key="keya" value="map1"></entry>
<entry key="keyb">
<value>map2</value>
</entry>
</map>
</property>
<property name="properties">
<props>
<prop key="propertieskey1">properties1</prop>
<prop key="propertieskey2">properties2</prop>
</props>
</property>
</bean>
調用:
public class UserController {
public static void main(String[] args) {
//獲取核心容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//根據id獲取Bean對象
UserService userService = (UserService) applicationContext.getBean("userService");
userService.saveUser();
}
}
打印結果:
3.注解注入
@Autowired
:按照類型注入
只要容器中有唯一的一個bean對象類型和要注入的變量類型匹配,就能注入成功。
如果容器中沒有bean的類型和要注入的變量匹配,報錯。
如果容器中有多個類型匹配時,報錯。
注意:當有多個類型都匹配上時,會再次按照名稱匹配,如果相同了,也能匹配上。
即注入時的類的名稱private UserDao userDao2;
userDao2與聲明時的注解@Repository("userDao2")
匹配上了。
@Qualifier
:按照類型注入的基礎之上再按照名稱注入。
(給方法參數注入時可以單獨使用,給類成員注入時則不能單獨使用,必須配合@Autowired注解)
value屬性:用于指定注入bean的id
@Service("userService")
public class UserSerivceImpl implements UserService {
@Autowired
@Qualifier("userDao1")
private UserDao userDao;
public void saveUser() {
userDao.saveUser();
}
}
@Resource
:按照bean的id注入,可以單獨使用
屬性name:用于指定bean的id
@Service("userService")
public class UserSerivceImpl implements UserService {
// @Autowired
// @Qualifier("userDao1")
@Resource(name = "userDao1")
private UserDao userDao;
public void saveUser() {
userDao.saveUser();
}
}
總結:以上三個注解只能注入bean類型的數據。
@Value
:用于注入基本類型和String類型數據
屬性value:指定數據的值,可以使用SpEL(spring的el表達式)
SpEL寫法:${表達式}
位置:變量、方法
五、使用純注解的方式配置(沒有beans.xml配置文件)
beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 告知 spring 創建容器時要掃描的包 -->
<context:component-scan base-package="com.hcx"></context:component-scan>
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springdemo"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
@Configuration
:指定當前類是一個配置類
當配置類作為AnnotationConfigApplicationContext對象創建的參數傳遞時,可以省略該注解。
@ComponentScan(basePackage="com.hcx")
:指定spring在創建容器時需要掃描的包
屬性value和basePackages:作用相同,都是用于指定創建容器時需要掃描的包。
使用此注解就相當于在xml中配置了:<context:component-scan base-package="com.hcx"></context:component-scan>
@Bean
:用于把當前方法的返回值作為bean對象存入spring的ioc容器中
屬性name:指定bean的id,默認值為當前方法名
@import
:導入其他配置類
屬性value:用于指定其他配置類的字節碼(使用了該注解的類就是主配置類)
@PropertySource
:用于指定配置文件的路徑
屬性value:指定文件名稱和路徑(classpath:類路徑)
SpringConfiguration:
@Configuration
@ComponentScan("com.hcx")
public class SpringConfiguration {
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean(name = "dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/springdemo");
comboPooledDataSource.setUser("root");
comboPooledDataSource.setPassword("root");
return comboPooledDataSource;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
測試:
public class UserServiceTest {
@Test
public void testSelectAll(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
List<User> userList = userService.findAll();
for(User user : userList){
System.out.println(user);
}
}
}
注意:使用注解配置,方法如果有參數,spring會尋找有沒有對應的bean對象,查找方式與Autowired一樣。
@import注解
當有多個配置類時,可以通過@import注解導入其他的配置類
主配置類:
@ComponentScan("com.hcx")
@Import(JdbcConfig.class)
public class SpringConfiguration {
}
Jdbc配置類:
public class JdbcConfig {
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean(name = "dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/springdemo");
comboPooledDataSource.setUser("root");
comboPooledDataSource.setPassword("root");
return comboPooledDataSource;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@PropertySource注解
使用讀取配置文件的方式讀取數據庫配置信息:
將數據庫的配置抽取到單獨的properties文件中:
jdbcConfig.properties:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/springdemo
jdbc.useranme=root
jdbc.password=root
JdbcConfig:
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean(name = "dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(username);
comboPooledDataSource.setPassword(password);
return comboPooledDataSource;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
在配置類中指定讀取該配置:
SpringConfiguration:
@ComponentScan("com.hcx")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}
在方法參數上使用@Qualifier注解
當有多個數據源時,可以在創建對象時通過@Qualifier注解參數指定具體使用哪一個: