Spring框架

Spring框架

課程目標(biāo)

  1. 理解Spring框架的兩大核心
  2. 掌握Spring框架三種依賴(lài)注入的實(shí)現(xiàn)方式
  3. 掌握Spring框架AOP的配置及使用

課程重點(diǎn)

  1. Spring框架的三種依賴(lài)注入的實(shí)現(xiàn)方式
  2. 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。



J2EE development without EJB

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


Spring 總共大約有 20 個(gè)模塊,由 1300 多個(gè)不同的文件構(gòu)成。而這些組件被分別整合在6 個(gè)模塊中:

  1. 核心容器(Core Container)
  2. AOP(Aspect Oriented Programming)
  3. 設(shè)備支持(Instrmentation)
  4. 數(shù)據(jù)訪問(wèn)及集成(Data Access/Integeration)
  5. Web報(bào)文發(fā)送(Messaging)
  6. 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)注入有三種方式:

  1. 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>
  1. Spring框架相當(dāng)于一個(gè)容器。此容器中負(fù)責(zé)創(chuàng)建對(duì)象,并實(shí)現(xiàn)對(duì)象與對(duì)象之間的裝配。
  2. java中每一個(gè)類(lèi)都是一個(gè)bean。所以上面的bean標(biāo)簽,就是在容器中創(chuàng)建一個(gè)java對(duì)象。
  3. bean標(biāo)簽中的class屬性,就是類(lèi)名; id屬性,就是對(duì)象名。
  4. property標(biāo)簽,是給bean的屬性注入其它對(duì)象。name屬性,就是對(duì)象屬性名; ref屬性,就是給屬性注入的對(duì)象。(如果想要注入基本數(shù)據(jù)類(lèi)型,那么使用value屬性)
  5. 給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è)特性。
注解的作用:

  1. 編寫(xiě)文檔:通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)生成文檔。
  2. 代碼分析:通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)對(duì)代碼進(jìn)行分析。
  3. 編譯檢查:通過(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ū)別:

  1. @Autowired默認(rèn)按類(lèi)型裝配,@Resource默認(rèn)是按名字裝配 。
  2. @Autowire如果想使用名稱(chēng)裝配可以結(jié)合@Qualifier注解進(jìn)行使用 。
  3. 使用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ǔ)義化注解。

  1. @Service 業(yè)務(wù)層組件
  2. @Controller 控制層組件
  3. @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í)行");
    }
}
  1. @Aspect注解:定義此類(lèi)為方面代碼。
  2. @Before注解:定義一個(gè)前置通知。即在目標(biāo)方法執(zhí)行前切入此注解標(biāo)注的方法。
  3. 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)的功能。通知有五種:

  1. 前置通知:在某連接點(diǎn)(JoinPoint 就是要織入的業(yè)務(wù)方法)之前執(zhí)行的通知。
  2. 后置通知:當(dāng)某連接點(diǎn)退出時(shí)執(zhí)行的通知(不論是正常結(jié)束還是發(fā)生異常)。
  3. 返回通知:(最終通知)在這里可以得到業(yè)務(wù)方法的返回值。但在發(fā)生異常時(shí)無(wú)法得到返回值。
  4. 環(huán)繞通知:包圍一個(gè)連接點(diǎn)的通知,也就是在業(yè)務(wù)方法執(zhí)行前和執(zhí)行后執(zhí)行的通知。
  5. 異常通知:在業(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)代理:

  1. 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)。詳情參考
  2. 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ī)則如下:
  3. 如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,采用jdk動(dòng)態(tài)代理實(shí)現(xiàn)aop。
  4. 如果目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)接口,采用CGLib動(dòng)態(tài)代理實(shí)現(xiàn)aop。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容