說在前面
github位址
https://github.com/Michilay/Spring
spring系列視訊如下
Spring三天入門(一):IOC、注入以及注解
Spring三天入門(二):代理模式,動态代理,spring實作AOP,切入
Spring三天入門(三):spring整合mybatis,aop事務切入
spring簡介
傳統的ssh指的是struct2+spring+hibernate
現在通常使用ssm指的是spring+spring mvc+mybatis
spring相當于是個大雜燴,整合了現有的技術架構
- spring是一個開源的免費容器
- spring是一個輕量級的非入侵的架構,引入了spring不會對原來的項目有任何影響
- 控制反轉IOC和面向切面程式設計AOP
- 支援事務的處理,聲明式事務因為AOP。對架構整合的支援
springIOC思想
idea建立一個maven項目,導入項目依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
導入spriing-webmvc一個包會有多個依賴項,junit用來測試
dao層寫使用者和使用者實作
package com.michilay.dao;
public interface UserDao {
void getUser();
}
package com.michilay.dao;
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("預設擷取使用者的資料");
}
}
service寫業務類和業務實作類
package com.michilay.service;
public interface UserService {
void getUser();
}
package com.michilay.service;
import com.michilay.dao.UserDao;
import com.michilay.dao.UserDaoImpl;
import com.michilay.dao.UserDaoMySqlImpl;
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
建立測試看看能不能走通
import com.michilay.dao.UserDaoImpl;
import com.michilay.service.UserServiceImpl;
import org.junit.jupiter.api.Test;
public class MyTest {
@Test
public void test(){
//使用者實際上調用業務層,dao層不需要接觸
UserServiceImpl userService = new UserServiceImpl();
userService.getUser();
}
}
預設擷取使用者的資料
程序已結束,退出代碼為 0
這樣是完全沒問題的
但是當我們需要有新的持久層需求的時候,比如這時候有mysql的持久層,新加了一個UserDaoMySqlImpl.class
package com.michilay.dao;
public class UserDaoMySqlImpl implements UserDao{
@Override
public void getUser() {
System.out.println("mysql擷取使用者資料");
}
}
如果要和業務層連結的話,就得在業務層修改mysql的執行個體化
package com.michilay.service;
import com.michilay.dao.UserDao;
import com.michilay.dao.UserDaoImpl;
import com.michilay.dao.UserDaoMySqlImpl;
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoMySqlImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
這樣就很麻煩,使用者的需求可能會影響我們原來的代碼,如果代碼量大的話,修改一次的成本就會十分昂貴,需要在代碼裡面修改,大大降低了維護性,這就完全違反了IOC,那麼我們要怎麼去修改呢
使用一個Set接口實作
private UserDao userDao;
//利用set進行動态實作值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
使用了set注入,程式不再具有主動性,而是成為了被動的接受對象
@Test
public void test(){
//使用者實際上調用業務層,dao層不需要接觸
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(new UserDaoImpl());
//userService.setUserDao(new UserDaoMySqlImpl());
userService.getUser();
}
想擷取什麼樣子的dao就可以擷取什麼樣的,這種思想從本質上解決的IOC的問題,可以更加專注于業務層,降低耦合性。
IOC的本質
DI(依賴注入)是實作ioc的一種方法,依賴對象的方式反轉了。是哦那個Bean的xml來配置,将配置檔案放在Bean内,可以将定義資訊和實作分離,之後可以使用注解,達到零配置
但是這樣仍然有點麻煩,如果是内部的對象好說,但是外部的其他類型的非java對象該怎樣導入進來呢?
spring基本配置
建立一個新的子產品
建立pojo.hello的class,聲明一個str對象,自動生成get,set和tostring方法
package com.michilay.pojo;
public class Hello {
private String str;
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
這裡spring提供beans來建立對象,在配置檔案中建立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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用spring來建立對象,在spring這些都成為Bean-->
<bean id="hello" class="com.michilay.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
測試類如下
@Test
public void test(){
//擷取spring的上下文對象
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//對象都交給spring管理,使用對象直接取出
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
}
依賴倒轉原則,使用set來注入,有主動的程式的建立對象變成了被動的接受對象
那麼回到剛才的項目,如果是多個sql需求我們應該怎麼改動呢?
在配置檔案裡下如下内容
<bean id="mysqlImpl" class="com.michilay.dao.UserDaoMySqlImpl"/>
<bean id="impl" class="com.michilay.dao.UserDaoImpl"/>
<bean id="userServiceImpl" class="com.michilay.service.UserServiceImpl">
<!-- ref:引用spring容器中建立好的對象-->
<property name="userDao" ref="mysqlImpl"/>
</bean>
如果是對象屬性,就是用ref,如果是普通屬性,就用value
執行個體化的對象,直接交給spring以每一個bean的形式建立,将id,set注入給業務層實作類,測試類如下
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
UserServiceImpl userServiceImpl= (UserServiceImpl) context.getBean("userServiceImpl");
userServiceImpl.getUser();
}
IOC就是由spring來建立,管理,裝配!
ioc建立對象的方式
- 預設使用無參構造建立對象
private String name;
public User(){
System.out.println("user的無參構造");
}
<bean id="user" class="com.michilay.pojo.User">
<property name="name" value="michilay"/>
</bean>
- 如果沒有無參構造函數的話
- 第一種方式,下标指派
private String name;
public User(String name){
this.name = name;
}
<!-- 下表指派-->
<bean id="user" class="com.michilay.pojo.User">
<constructor-arg index="0" value="michilay"/>
<!-- <property name="name" value="michilay"/>-->
</bean>
- 第二種方式,通過類型建立
<!-- 不建議使用-->
<bean id="user" class="com.michilay.pojo.User">
<constructor-arg type="java.lang.String" value="michilay"/>
</bean>
- 第三種方式,直接通過參數名來設定
<!-- 直接通過參數名來設定-->
<bean id="user" class="com.michilay.pojo.User">
<constructor-arg name="name" value="michilay"/>
</bean>
注意:在context擷取上下文對象之後,Beans裡面的對象就已經都被建立了,而且這個對象隻有一個位址,不管它被指向其他對象多少次
spring配置深入
spring别名
使用getBean拿到的對象是一個
bean屬性
一個對象就是一個Bean,
id = 對象名名,相當于spring根據這個名字獲得配置對象,id有唯一性
class = new 的對象,對象所在的全路徑,bean對象的全限定名:報名+屬性
name 對象名字,别名,而且name可以同時取多個
property 相當于給對象設定值
import
import适用于團隊開發,可以将多個配置檔案,導入為一個總的配置檔案
<import resource="Beans1.xml"/>
<import resource="Beans2.xml"/>
<import resource="Beans3.xml"/>
各種beans裡面的對象name相同,就會選取一個,如果後來的name相同,就用後面bean對象裡的屬性值覆寫前者
注入
構造器注入
構造器注入在ioc建立對象的方式中已有提到
set注入[重要]
依賴注入,也就是set注入,依賴注入分為依賴和注入,所謂依賴,就是bean建立的對象依賴于容器;注入,就是bean對象中的所有屬性,由容器來注入
- 測試對象student
public class Student { private String name; private Address address; private String[] books; private List<String> hobbys; private Map<String,String> card; private Set<String> games; private String wife; private Properties info; //還有set,get方法和tostring
- 複雜對象
public class Address { private String address; //還有set,get方法和tostring
- 各種類型對象的注入方式
<bean id="address" class="com.michilay.pojo.Address"> <property name="address" value="湖北"></property> </bean> <bean id="student" class="com.michilay.pojo.Student"> <!-- 普通值注入,value--> <property name="name" value="michilay"/> <!-- Bean注入,value--> <property name="address" ref="address"/> <!-- 數組注入--> <property name="books"> <array> <value>紅樓夢</value> <value>西遊記</value> <value>三國演義</value> <value>水浒傳</value> </array> </property> <!-- List集合注入--> <property name="hobbys"> <list> <value>聽歌</value> <value>學習</value> <value>吃飯</value> </list> </property> <!-- map--> <property name="card"> <map> <entry key="身份證" value="123214123"/> <entry key="銀行卡" value="92324322341"/> </map> </property> <!-- set--> <property name="games"> <set> <value>LOL</value> <value>MC</value> <value>WAR3</value> </set> </property> <!-- null--> <property name="wife"> <null/> </property> <!-- properties--> <property name="info"> <props> <prop key="id">200123</prop> <prop key="sex">男</prop> <prop key="username">張三</prop> </props> </property> </bean>
拓展方式注入
User實體類
public class User {
private String name;
private int age;
//加上get,set和tostring以及無參構造和有參構造
-
p命名空間
對簡單的對象進行注入
在beans标簽裡添加下面一行
-
c命名空間
對構造器進行注入
添加标簽
bean的作用域
-
單例模式
預設使用的就是單例模式,可以添加scope="singleton"屬性
@Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user1 = context.getBean("user2", User.class); User user = context.getBean("user2", User.class); System.out.println(user == user1); }
true
-
原型模式
每次從容器中get的時候,都會産生一個新的對象,scope=“prototype”
@Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user1 = context.getBean("user2", User.class); User user = context.getBean("user2", User.class); System.out.println(user1.hashCode()); System.out.println(user.hashCode()); System.out.println(user == user1); }
846254484
592983282
false
- 其餘的request、session、application存在于web開發中
Bean自動裝配
自動裝配是Spring滿足bean依賴的一種方式
spring會在上下文中尋找,并自動給bean裝配屬性!
在spring中有三種裝配的方式
- 在xml中顯示的配置
- 在java中顯示配置
- 隐式的自動裝配
people實體類
public class People {
private Cat cat;
private Dog dog;
private String name;
//set和get以及tostring方法
byName自動裝配
會自動在容器上下文中查找和自己對象set方法後面的值對應的bean的id,要保證所有bean的id唯一
<bean id="cat" class="com.michilay.pojo.Cat"/>
<bean id="dog" class="com.michilay.pojo.Dog"/>
<bean id="people" class="com.michilay.pojo.People" autowire="byName">
<property name="name" value="michilay"/>
</bean>
byType自動裝配
會自動在容器上下文中查找和自己對象屬性類型對應的bean,必須保證class全局唯一
<bean id="cat" class="com.michilay.pojo.Cat"/>
<bean id="do11g" class="com.michilay.pojo.Dog"/>
<bean id="people" class="com.michilay.pojo.People" autowire="byType">
<property name="name" value="michilay"/>
</bean>
注解
使用注解自動裝配
在spring4之後,使用注解開發必須要導入aop的包
導入context限制和配置注解的支援
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--開啟注解的支援 -->
<context:annotation-config/>
</beans>
@Autowired
直接在屬性上使用即可,也可以在set方法上使用
注解是通過反射的原理實作,可以不需要set方法,但是需要将改屬性在IOC容器中存在
當注入在IOC容器中的類型隻有一個的時候,按照byType進行比對,預設也是這樣;
當注入在IOC容器中存在多個同一類型的對象時,按照byName進行比對。
如果它的required屬性為false,說明這個對象可以為null,否則不允許為空
@Nullable
字段标記了這個注解,改注解就可以為null
@Qualifier(value=“xxx”)
如果@Autowired裝配的對象比較複雜,就可以使用@Qualifier設定和它id相同的值對應,指定一個唯一的bean
<bean id="cat111" class="com.michilay.pojo.Cat"/>
<bean id="dog" class="com.michilay.pojo.Dog"/>
<bean id="people" class="com.michilay.pojo.People" autowire="byType"/>
public class People {
@Autowired
@Qualifier(value = "cat111")
private Cat cat;
@Autowired
private Dog dog;
private String name;
@Resource
這個一個java的注解,但是也能起到Autowired的作用,它會先和名字比對,再和類型比對,它也可以取value值
注解開發
在使用下面注解之前,需要在配置檔案裡添加包掃描器
<context:component-scan base-package="com.michilay"/>
<context:annotation-config/>
@Component
元件,放在類的上面,說明這個類被spring給管理了,相當于這一行代碼
@Value
隻有類的托管還不夠,類的屬性就由Value注解,它相當于這一行代碼,如果這個屬性有set方法,把Value注解放在set方法效果相同**(優先級高于類屬性)**
@Component
public class User {
public String name = "Michilay";
@Value("123")
public void setName(String name) {
this.name = name;
}
}
@Repository
@Service
@Controller
這三個注解和component是一樣效果,隻不過它們分别用在web開發中不同的位置
dao層用repository注解,service層用service注解,controller層用controller注解
@Scope
作用域注解,和bean的作用域這一小節效果相同,注解後面加上(“singleton”)改屬性
注解總結
注解相對于xml友善,當類和屬性不多的時候
注解不是自己的類使用不了,維護更加負責
開發java最常用的組合就是
xml用來管理bean,注解隻負責完成屬性的注入
使用注解代替xml配置檔案
完全不使用spring的xml配置檔案,把所有的内容全部交給java來做
javaConfig是Spring的一個子項目,在Spring4之後,它成為了一個核心功能
這裡建立個config包寫一個config類
package com.michilay.config;
import com.michilay.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan("com.michilay.pojo")
@Import(myConfig2.class)
public class myConfig {
@Bean
public User getUser(){
return new User();//就是傳回要注入到bean的對象
}
}
@Configuration
代表這個類是一個注解類,相當于beans标簽
@ComponentScan
是一個包掃描器标簽
@Import
相當于import标簽,當有多個配置檔案的時候導入
@Bean
相當于Bean标簽,方法名就是bean中的id屬性,傳回值就是bean中的class屬性
pojo包中的User實體類
package com.michilay.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//說明這個類被spring接管了,注冊到了IOC中
//@Component
public class User {
private String name;
public String getName() {
return name;
}
@Value("michilay")
public void setName(String name) {
this.name = name;
}
}
測試類
import com.michilay.config.myConfig;
import com.michilay.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest {
@Test
public void test(){
//如果完全使用配置類,就隻能用這個上下文來擷取容器,通過配置類的class對象加載
ApplicationContext context = new AnnotationConfigApplicationContext(myConfig.class);
User getUser = context.getBean("getUser", User.class);
System.out.println(getUser.getName());
}
}
這種沒有xml的配置方式,在springboot中相當常見