Spring框架
課程目標(biāo)
- 理解Spring框架的兩大核心
- 掌握Spring框架三種依賴(lài)注入的實(shí)現(xiàn)方式
- 掌握Spring框架AOP的配置及使用
課程重點(diǎn)
- Spring框架的三種依賴(lài)注入的實(shí)現(xiàn)方式
- Spring框架AOP的配置及使用
1.Spring框架
1.1.Spring框架簡(jiǎn)介
Spring是一個(gè)基于java的輕量級(jí)的、一站式框架。 雖然Spring是一個(gè)輕量級(jí)框架,但并不表示它的功能少。實(shí)際上,spring是一個(gè)龐然大物,包羅萬(wàn)象。 時(shí)至今日,Spring已經(jīng)成為java世界中事實(shí)上的標(biāo)準(zhǔn)。
Spring之父:Rod Johnson(羅德.約翰遜) 他是悉尼大學(xué)音樂(lè)學(xué)博士,而計(jì)算機(jī)僅僅是學(xué)士學(xué)位。 由于Rod對(duì)JAVAEE笨重、臃腫的現(xiàn)狀深?lèi)和唇^,以至于他將他在JAVAEE實(shí)戰(zhàn)中的經(jīng)歷稱(chēng)為噩夢(mèng)般的經(jīng)歷。他決定改變這種現(xiàn)狀,于是就有了Spring。


1.2.Spring體系架構(gòu)

Spring 總共大約有 20 個(gè)模塊,由 1300 多個(gè)不同的文件構(gòu)成。而這些組件被分別整合在6 個(gè)模塊中:
- 核心容器(Core Container)
- AOP(Aspect Oriented Programming)
- 設(shè)備支持(Instrmentation)
- 數(shù)據(jù)訪問(wèn)及集成(Data Access/Integeration)
- Web報(bào)文發(fā)送(Messaging)
- Test測(cè)試
1.3.Spring兩大核心
DI:依賴(lài)注入(Dependency Injection) AOP:面向切面編程(Aspect Oriented Programming)
2.DI(依賴(lài)注入)
依賴(lài)注入(Dependency Injection)是一種設(shè)計(jì)模式,也是Spring框架的核心概念之一。其作用是去除組件之間的依賴(lài)關(guān)系,實(shí)現(xiàn)解耦合。 也就是說(shuō):所謂依賴(lài)注入,是指工程中需要的組件無(wú)須自己創(chuàng)建,而是依賴(lài)于外部環(huán)境注入。
Spring實(shí)現(xiàn)容器管理依賴(lài)注入有三種方式:
- xml配置文件方式(了解)
2.注解方式(官方推薦方式)
3.JavaConfig方式。
2.1.使用xml配置文件實(shí)現(xiàn)
下面使用 Spring 來(lái)重構(gòu)dao層組件與service層組件。 也就是說(shuō):由Spring創(chuàng)建dao層組件和service層組件,并使用Spring將dao層組件注入給service層組件。
2.1.1. 修改pom.xml添加Spring依賴(lài)
<dependencies>
<!-- 此依賴(lài)會(huì)關(guān)聯(lián)引用Spring中的所有基礎(chǔ)jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.15</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
2.1.2.創(chuàng)建日志配置文件
在 resources 文件夾中創(chuàng)建log4j.properties配置文件
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %m%n
log4j.rootLogger=debug,stdout
2.1.3.創(chuàng)建dao接口與實(shí)現(xiàn)類(lèi)
public interface UserDao {
void insert();
}
public interface UserDaoImpl implements UserDao {
public void insert(){
System.out.println("插入一行User數(shù)據(jù)");
}
}
2.1.4.創(chuàng)建service接口與實(shí)現(xiàn)類(lèi)
public interface UserService {
public void addUser();
}
import com.neuedu.spring.di.dao.UserDao;
public class UserServiceImpl implements UserService{
private UserDao dao;
public void setDao(UserDao dao) {
this.dao= dao;
}
@Override
public void addUser() {
System.out.println("添加User業(yè)務(wù)");
dao.insert();
}
}
2.1.5.創(chuàng)建Spring配置文件
在resources目錄下創(chuàng)建spring.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:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<bean id="userDao" class="com.neuedu.spring.di.dao.UserDaoImpl"></bean>
<bean id="userService" class="com.neuedu.spring.di.service.UserServiceImpl">
<property name="dao" ref="userDao"/>
</bean>
</beans>
- Spring框架相當(dāng)于一個(gè)容器。此容器中負(fù)責(zé)創(chuàng)建對(duì)象,并實(shí)現(xiàn)對(duì)象與對(duì)象之間的裝配。
- java中每一個(gè)類(lèi)都是一個(gè)bean。所以上面的bean標(biāo)簽,就是在容器中創(chuàng)建一個(gè)java對(duì)象。
- bean標(biāo)簽中的class屬性,就是類(lèi)名; id屬性,就是對(duì)象名。
- property標(biāo)簽,是給bean的屬性注入其它對(duì)象。name屬性,就是對(duì)象屬性名; ref屬性,就是給屬性注入的對(duì)象。(如果想要注入基本數(shù)據(jù)類(lèi)型,那么使用value屬性)
- 給bean的屬性注入其它對(duì)象,默認(rèn)使用 get/set 方法注入。也可以使用其它方式注入:構(gòu)造方法注入、接口注入等詳細(xì)參看。
2.1.6. 測(cè)試
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.neuedu.spring.di.service.UserService;
public class XmlTest
{
ApplicationContext context = null;
@Before
public void before() throws Exception {
context = new ClassPathXmlApplicationContext("spring.xml");
}
@Test
public void test() {
UserService service = (UserService)context.getBean("userService");
service.addUser();
}
}
2.2.使用注解實(shí)現(xiàn)
注解(Annotation),也叫元數(shù)據(jù)。它是一種代碼級(jí)別的說(shuō)明,是jdk1.5之后引入的一個(gè)特性。
注解的作用:
- 編寫(xiě)文檔:通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)生成文檔。
- 代碼分析:通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)對(duì)代碼進(jìn)行分析。
- 編譯檢查:通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)讓編譯器能夠?qū)崿F(xiàn)基本的編譯檢查。
2.2.1.修改dao實(shí)現(xiàn)類(lèi)
@Component
public interface UserDaoImpl implements UserDao {
public void insert(){
System.out.println("插入一行User數(shù)據(jù)");
}
}
@Component:此類(lèi)的對(duì)象創(chuàng)建由Spring容器負(fù)責(zé)。 @Component("xxxx"):創(chuàng)建此類(lèi)的對(duì)象,取一個(gè)對(duì)象名,并放入到Spring容器中。
2.2.2.修改Service實(shí)現(xiàn)類(lèi)
@Component
public class UserServiceImpl implements UserService{
@Autowired //自動(dòng)裝配
private UserDao dao;
//注意:dao屬性自動(dòng)注入,所以就可以不用get/set方法了
public void setDao(UserDao dao) {
this.dao= dao;
}
@Override
public void addUser() {
System.out.println("添加User業(yè)務(wù)");
dao.insert();
}
}
@Autowired:默認(rèn)按照類(lèi)型在Spring容器尋找對(duì)象,并注入到屬性中。 所以此時(shí)要注意:UserDao接口的實(shí)現(xiàn)類(lèi)只能有一個(gè)。
@Resource注解 @Resource 是Java標(biāo)準(zhǔn)規(guī)范(JSR-250 JakartaEE)中定義的注解,用于進(jìn)行依賴(lài)注入。
@Autowired和@Resource的區(qū)別:
- @Autowired默認(rèn)按類(lèi)型裝配,@Resource默認(rèn)是按名字裝配 。
- @Autowire如果想使用名稱(chēng)裝配可以結(jié)合@Qualifier注解進(jìn)行使用 。
- 使用Spring框架時(shí),建議使用@Autowired
2.2.3. 測(cè)試
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AnnotationTest
{
ApplicationContext context = null;
@Before
public void before() throws Exception {
context = new AnnotationConfigApplicationContext("com.neuedu.spring");
}
@Test
public void test() {
UserService service = context.getBean(UserService.class);
service.addUser();
}
}
2.2.4.相關(guān)注解說(shuō)明
2.2.4.1.組件級(jí)注解
除了@Component這個(gè)泛指組件的注解外,Spring還提供了與@Component功能相同的三個(gè)語(yǔ)義化注解。
- @Service 業(yè)務(wù)層組件
- @Controller 控制層組件
- @Repository 數(shù)據(jù)層組件
修改上面代碼,使用@Repository 和 @Service 替換 dao 與 service 組件上的注解。
2.2.4.2.Bean作用范圍注解
@Scope注解:設(shè)置Bean的作用域。值如下:

2.3.使用JavaConfig實(shí)現(xiàn)
JavaConfig,是在 Spring 3.0 開(kāi)始從一個(gè)獨(dú)立的項(xiàng)目并入到 Spring 中的。javaConfig是一個(gè)用于完成 Bean配置的 java 類(lèi)。
一個(gè)類(lèi)中只要標(biāo)注了@Configuration注解,這個(gè)類(lèi)就可以為spring容器提供Bean定義的信息了,或者說(shuō)這個(gè)類(lèi)就成為一個(gè)spring容器了。
類(lèi)中的每個(gè)標(biāo)注了@Bean的方法都相當(dāng)于提供了一個(gè)Bean的定義信息。
2.3.1.創(chuàng)建Config類(lèi)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class AppConfig {
@Bean
public List<String> list() {
return new ArrayList<>();
}
}
2.3.2.測(cè)試
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.*;
public class JavaConfigTest
{
ApplicationContext context = null;
@Before
public void before() throws Exception {
context = new AnnotationConfigApplicationContext("com.neuedu.spring");
}
@Test
public void test() {
List<String> list = context.getBean(List.class);
System.out.print(list.hashCode());
}
}
2.4.IOC與DI
IOC:控制反轉(zhuǎn)(Inversion of Control):它是一種控制權(quán)的轉(zhuǎn)移。即組件與組件之間的依賴(lài)由主動(dòng)變?yōu)楸粍?dòng)。也就是說(shuō):應(yīng)用程序本身不再負(fù)責(zé)組件的創(chuàng)建、維護(hù)等,而是將控制權(quán)移交出去。
DI:依賴(lài)注入(Dependency Injection):依賴(lài)其他容器(比如spring)來(lái)創(chuàng)建和維護(hù)所需要的組件,并將其注入到應(yīng)用程序中。
IOC只是將組件控制權(quán)移交出去,但并沒(méi)有說(shuō)明組件如何獲取。而DI明確說(shuō)明:組件依賴(lài)Spring容器獲取。 所以可以這樣說(shuō):DI是IOC思想的一種具體實(shí)現(xiàn)。
3.AOP(面向切面)
AOP:全稱(chēng)是 Aspect Oriented Programming 即:面向切面編程。
簡(jiǎn)單的說(shuō)它就是把我們程序重復(fù)的代碼抽取出來(lái),在需要執(zhí)行的時(shí)候,使用動(dòng)態(tài)代理的技術(shù),在不修改源碼的基礎(chǔ)上,對(duì)我們的已有方法進(jìn)行增強(qiáng)。
即當(dāng)需要擴(kuò)展功能時(shí),傳統(tǒng)方式采用縱向繼承方式實(shí)現(xiàn)。但這種方式有很多缺點(diǎn)。 比如:父類(lèi)方法名稱(chēng)改變時(shí),子類(lèi)也要修改。給多個(gè)方法擴(kuò)展功能時(shí),子類(lèi)也需要修改。 因此,spring的AOP,實(shí)際上是采用橫向抽取機(jī)制,取代傳統(tǒng)的縱向繼承體系。
實(shí)現(xiàn)AOP示意圖:
1.先將方面代碼抽取出來(lái)

2.運(yùn)行時(shí)將業(yè)務(wù)代碼和方面代碼編織在一起運(yùn)行

3.1.使用注解方式實(shí)現(xiàn)AOP
3.1.1.抽取方面代碼封裝通知對(duì)象
在實(shí)際開(kāi)發(fā)中,除了業(yè)務(wù)邏輯這個(gè)主要功能之外,還需要處理許多輔助功能。 比如:日志、異常處理、事務(wù)、輸入驗(yàn)證、安全等等,我們將這些代碼稱(chēng)為:方面代碼。而方面代碼,就是我們要抽取出來(lái)的。
下面抽取日志方面代碼:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Component
@EnableAspectJAutoProxy // 關(guān)鍵注解:?jiǎn)⒂肁spectJ自動(dòng)代理
@Aspect //@Aspect定義此類(lèi)為方面代碼。
public class MyAspect {
//通知方法 切入點(diǎn)
@Before("execution(* com.neuedu.spring.di.service.*.*(..))")
public void beforeMethod(JoinPoint joinpoint){
System.out.println(joinpoint.getSignature().getName() + "-開(kāi)始執(zhí)行");
}
}
- @Aspect注解:定義此類(lèi)為方面代碼。
- @Before注解:定義一個(gè)前置通知。即在目標(biāo)方法執(zhí)行前切入此注解標(biāo)注的方法。
- execution() 是一個(gè)Aspect表達(dá)式,語(yǔ)法為:execution(返回值類(lèi)型 包名.類(lèi)名.方法名 (參數(shù)) 異常)
例如:execution(* com.neuedu.spring.di.service..(..))
- 第一個(gè) *:所有的返回值類(lèi)型
- 第二個(gè) *:所有的類(lèi)
- 第三個(gè) *:所有的方法
- 第四個(gè) .. :所有的參數(shù)
3.1.2.測(cè)試
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.neuedu.spring.di.service.UserService;
public class AopTest
{
ApplicationContext context = null;
@Before
public void before() throws Exception {
context = new AnnotationConfigApplicationContext("com.neuedu.spring");
}
@Test
public void test() {
UserService service = (UserService)context.getBean(UserService.class);
service.addUser();
}
}
3.2.五種通知類(lèi)型
方面代碼一般也稱(chēng)為通知:定義一個(gè)“切面”要實(shí)現(xiàn)的功能。通知有五種:
- 前置通知:在某連接點(diǎn)(JoinPoint 就是要織入的業(yè)務(wù)方法)之前執(zhí)行的通知。
- 后置通知:當(dāng)某連接點(diǎn)退出時(shí)執(zhí)行的通知(不論是正常結(jié)束還是發(fā)生異常)。
- 返回通知:(最終通知)在這里可以得到業(yè)務(wù)方法的返回值。但在發(fā)生異常時(shí)無(wú)法得到返回值。
- 環(huán)繞通知:包圍一個(gè)連接點(diǎn)的通知,也就是在業(yè)務(wù)方法執(zhí)行前和執(zhí)行后執(zhí)行的通知。
- 異常通知:在業(yè)務(wù)方法發(fā)生異常時(shí)執(zhí)行的通知。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Component
@EnableAspectJAutoProxy // 關(guān)鍵注解:?jiǎn)⒂肁spectJ自動(dòng)代理
@Aspect //@Aspect定義此類(lèi)為方面代碼。
public class MyAspect {
//通知方法 切入點(diǎn)
@Pointcut("execution(* com.neuedu.spring.di.service.*.*(..))")
private void anyMethod(){}
@Before("anyMethod()")
public void beforeMethod(JoinPoint joinpoint){
System.out.println( "前置通知" );
}
/* @After("anyMethod()")
public void afterMethod(JoinPoint joinpoint){
System.out.println( "后置通知" );
}
@AfterReturning(pointcut="anyMethod()",returning="result")
public void afterReturnning(JoinPoint joinpoint,Object result){
System.out.println( "返回通知" );
}
@AfterThrowing(pointcut="anyMethod()",throwing="ex")
public void afterThrowing(JoinPoint joinpoint,Exception ex){
System.out.println( "拋出通知" );
}
@Around("anyMethod()")
public Object aroundMethod(ProceedingJoinPoint pjp) {
Object obj = null;
try{
System.out.println("環(huán)繞通知日志-1" );
obj = pjp.proceed();
System.out.println("環(huán)繞通知日志-2" );
}catch(Throwable e){
e.printStackTrace();
}
return obj;
}*/
}
注意:有了環(huán)繞通知,異常通知也將失去作用
3.3.SpringAop內(nèi)部實(shí)現(xiàn)
3.3.1.動(dòng)態(tài)代理模式
動(dòng)態(tài)代理是一種常用的設(shè)計(jì)模式,廣泛應(yīng)用于框架中,Spring框架的AOP特性就是應(yīng)用動(dòng)態(tài)代理實(shí)現(xiàn)的。

Spring框架采用兩種形式動(dòng)態(tài)代理:
- jdk動(dòng)態(tài)代理:根據(jù)目標(biāo)類(lèi)接口獲取代理類(lèi)實(shí)現(xiàn)規(guī)則,生成代理對(duì)象。這個(gè)代理對(duì)象,也是目標(biāo)類(lèi)接口的一個(gè)實(shí)現(xiàn)類(lèi)。詳情參考
- cglib動(dòng)態(tài)代理:根據(jù)目標(biāo)類(lèi)本身獲取代理類(lèi)實(shí)現(xiàn)規(guī)則,生成代理對(duì)象。這個(gè)代理對(duì)象,也是目標(biāo)類(lèi)的一個(gè)子類(lèi)。 (如果目標(biāo)類(lèi)為final,則不能使用CGLib實(shí)現(xiàn)動(dòng)態(tài)代理)詳情參考
SpringAOP使用規(guī)則如下: - 如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,采用jdk動(dòng)態(tài)代理實(shí)現(xiàn)aop。
- 如果目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)接口,采用CGLib動(dòng)態(tài)代理實(shí)現(xiàn)aop。