天天看點

從注解和JAVA配置的角度看Spring

Spring簡史

第一階段:xml配置

在Spring1.x時代,Spring采用xml的方式來配置bean。

第二階段:注解配置

在Spring2.x時代,随着JDK1.5帶來了注解支援,Spring提供了聲明Bean的注解,大大減少了配置量。

第三階段:Java配置

從Spring3.x到現在,Spring提供了Java配置的能力。我們目前剛好處于這個時代,Spring4.x和Spring Boot都推薦使用Java配置。

基于IntelliJ IDEA搭建Spring

(1)建立Maven項目。單擊File--New--Project--Maven

從注解和JAVA配置的角度看Spring

(2)輸入Maven項目坐标值

從注解和JAVA配置的角度看Spring

(3)選擇存儲路徑

從注解和JAVA配置的角度看Spring

(4)修改pom.xml檔案。增加Spring依賴,添加編譯插件,将編譯級别設定為1.7。

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>my</groupId>
    <artifactId>spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.7</java.version>
    </properties>

    <dependencies>
        <!-- Spring依賴 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 添加編譯插件,将編譯級别設定為1.7 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
           

Spring架構的四大特征

(1)使用POJO進行輕量級和最小侵入式開發。

(2)提供IoC容器,通過依賴注入和基于接口程式設計實作松耦合。

(3)通過AOP和預設習慣進行聲明式程式設計。

(4)使用AOP和模闆(template)減少模式化代碼。

Spring基礎知識

Spring IoC容器(Application Context):負責建立Bean。Spring提供使用xml、注解、Java配置、groovy配置來實作Bean的建立和注入。

依賴注入:負責建立對象和維護對象之間的依賴關系,而不是通過對象本身負責自己的建立和解決自己的依賴。

注意:控制反轉是通過依賴注入實作的。

聲明Bean的注解:

l @Component元件,沒有明确角色。

l @Service在業務邏輯層(Service層)使用。

l @Controller在展現層使用。

l @Repository在資料通路層(dao層)使用。

注入Bean的注解,一般情況下通用。注意的是,不适用于局部變量。

l @Autowired:Spring提供的注解。

l @Resource:JSR-250提供的注解。

l @Inject:JSR-330提供的注解。

JAVA配置

Java配置是通過@Configuration和@Bean來實作的。

l @Configuration聲明目前類是一個配置類,相當于Spring配置的一個xml檔案。

l @Bean注解在方法上,聲明目前方法傳回值為一個Bean。

何時使用Java配置或者注解配置呢?

我們的原則是:全局配置使用Java配置(如資料庫相關配置、MVC相關配置),業務Bean的配置使用注解配置(@Component、@Repository、@Service、@Controller)。

示例

示例采用簡單的Java配置。

(1)編寫功能類Bean:UserService

package my.spring.service;

public class UserService {

    public void say(String word) {
        System.out.println("hello " + word + " !");
    }

}
           

(2)編寫配置類

package my.spring.configuration;

import my.spring.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean
    public UserService userService() {
        return new UserService();
    }
}
           

(3)運作

package my.spring;

import my.spring.configuration.Config;
import my.spring.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        // 加載配置類
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        // 獲得Bean
        UserService userSrv = context.getBean(UserService.class);

        userSrv.say("qinxia");
        // 關閉IoC容器
        context.close();

    }
}
           

示例講解與擴充

在配置類Config中:

(1)使用@Configuration注解表明目前類是一個配置類,這意味着這個類中可能有0個或多個Bean注解。

(2)使用@Bean注解聲明目前方法的傳回值是一個Bean。方法名是可以随便起的,但是為了規範,我們與Bean的類名保持一緻,單駝峰辨別。

另外:在Spring容器中,隻要容器中存在某個Bean,就可以在另外一個Bean的聲明方法的參數中注入。(參見示例2)

示例2

(1)編寫功能類Bean:UserService和UseUserService

package my.spring.service;

public class UserService {

    public String say(String word) {
        return "hello " + word + " !";
    }

}
           
package my.spring.service;

public class UseUserService {

    UserService userSrv;

    public void setUserSrv(UserService userSrv) {
        this.userSrv = userSrv;
    }

    public String say(String word) {
        return userSrv.say(word);
    }
}
           

(2)編寫配置類

package my.spring.configuration;

import my.spring.service.UseUserService;
import my.spring.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean
    public UserService userService() {
        return new UserService();
    }

    @Bean
    public UseUserService useUserService(UserService userSrv) {
        UseUserService useUserService = new UseUserService();
        useUserService.setUserSrv(userSrv);
        return useUserService;
    }

//    @Bean
//    public UseUserService useUserService() {
//        UseUserService useUserService = new UseUserService();
//        useUserService.setUserSrv(userService());
//        return useUserService;
//    }
}
           

(3)運作

package my.spring;

import my.spring.configuration.Config;
import my.spring.service.UseUserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        // 加載配置類
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        // 獲得Bean
        UseUserService useUserService = context.getBean(UseUserService.class);

        System.out.println(useUserService.say("qinxia"));

        context.close();


    }
}
           

示例3

示例3基于注解的Bean的初始化和依賴注入。

(1)編寫功能類Bean:UserService和UseUserService

package my.spring.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public String say(String word) {
        return "hello " + word + " !";
    }

}
           
package my.spring.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UseUserService {

    @Autowired
    UserService userService;

    public String say(String word) {
        return userService.say(word);
    }
}
           

(2)編寫配置類

package my.spring.configuration;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("my.spring")
public class Config {
}
           

(3)運作

ackage my.spring;

import my.spring.configuration.Config;
import my.spring.service.UseUserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        // 加載配置類
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        // 獲得Bean
       UseUserService useUserSrv = context.getBean(UseUserService.class);

       System.out.println(useUserSrv.say("qinxia"));

       context.close();

    }
}
           

示例3講解

@Service注解聲明目前類是Spring管理的一個Bean。

@Autowired注解将UserService的實體Bean注入到UseUserService中。

@Configuration注解聲明目前類是一個配置類。

@ComponentScan(“包名”)注解自動掃描指定包下面的所有使用@Component、@Repository、@Controller和@Service的類,并注冊為Bean。

AOP

AOP:面向切面程式設計。

好處:

   可以動态地添加和删除在切面上的邏輯,而不影響原來的執行代碼。

   降低耦合度。

JoinPoint:符合條件的每一個被攔截處為連接配接點,即切點。

PointCut:切點集合。

Spring支援AspectJ的注解式切面程式設計:

l 使用@Aspect聲明一個切面。

l 使用@After、@Before、@Around定義建言(advice),可直接将切點集合作為參數。

攔截方式:

l 基于注解攔截

l 基于方法規則攔截

示例(基于注解的攔截)

(1)修改pom.xml:添加spring aop依賴及AspectJ依賴。

<!-- Spring aop依賴 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>4.3.12.RELEASE</version>
    </dependency>

    <!-- aspectj依賴 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.8.5</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.5</version>
    </dependency>
</dependencies>
           

(2)建立注解:用來聲明切點,也就是被攔截處。

package my.spring.annotations;

import java.lang.annotation.*;

@Documented     // 文檔:聲明該注解的有關資訊
@Target(ElementType.METHOD)     // 指定注解的作用範圍:作用于方法上
@Retention(RetentionPolicy.RUNTIME)     // 指定注解的生存周期:會在class位元組碼檔案中存在,運作時通過反射可獲得
public @interface Cut1 {	//①
    String name();
}
           

(3)編寫被攔截類

package my.spring.service;

import my.spring.annotations.Cut1;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Cut1(name="注解式攔截")	//②
    public void say1() {
        System.out.println("hello world!");
    }

}
           

(4)編寫切面

package my.spring.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect		//③
public class LogAspect {

    @Pointcut("@annotation(my.spring.annotations.Cut1)")	//④
    public void annotationPointCut1() {};


    @Before("annotationPointCut1()")	//⑤
    public void before() {
        System.out.println("===before===");
    }

    @After("annotationPointCut1()")	//⑥
    public void after() {
        System.out.println("===after===");
    }

}
           

(5)編寫配置類

package my.spring.configuration;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("my.spring")
@EnableAspectJAutoProxy     //⑦開啟Spring對AspectJ的支援
public class Config {
}
           

(6)運作

package my.spring;

import my.spring.configuration.Config;
import my.spring.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        // 加載配置類
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        // 獲得Bean
        UserService userService = context.getBean(UserService.class);

        userService.say1();

       context.close();

    }
}
           

示例講解

① 建立注解@Cut1,用來聲明切點。也就是說隻要我們在被攔截處的添加@Cut1注解,就指定了該處為一個切點,執行切面上與之對應的建言。

② 在say()方法上添加@Cut1注解,聲明該方法為一個切點。

③ @Aspect聲明該類為一個切面。

④ @Pointcut("@annotation(my.spring.annotations.Cut1)")聲明了一個切點集合,參數用來指定特定的切點。即@Cut1聲明的切點;@annotation用來比對持有特定注解的方法,參數為自定義的注解類。

⑤ @Before("annotationPointCut1()")聲明一個建言,作用于指定的切點集合,并在切點之前執行before()方法。

⑥ @After("annotationPointCut1()")聲明一個建言,作用于指定的切點集合,并在切點之後執行after()方法。

⑦ @EnableAspectJAutoProxy開啟Spring對AspectJ的支援。

示例2(基于規則方法的攔截)

在示例的基礎上做如下修改:

(1)修改被攔截類

package my.spring.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public void say1() {
        System.out.println("hello world!");
    }

}
           

(2)修改切面

package my.spring.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class LogAspect {

    @Before("execution(* my.spring.service..*.*(..))")	//①
    public void before() {
        System.out.println("===before===");
    }

    @After("execution(* my.spring.service..*.*(..))")
    public void after() {
        System.out.println("===after===");
    }

}
           

(3)運作:同上,不變。

示例2解析

① @Before("execution(* my.spring.service..*.*(..))")指定了在執行特定方法之後,執行before方法。

execution()是最常用的切點函數,其文法如下所示:

整個表達式可以分為五個部分:

1、execution():表達式主體。

2、第一個*号:表示傳回類型,*号表示所有的類型。

3、包名:表示需要攔截的包名,後面的兩個句點表示目前包和目前包的所有子包,my.spring.service包、子孫包下所有類的方法。

4、第二個*号:表示類名,*号表示所有的類。

5、*(..):最後這個星号表示方法名,*号表示所有的方法,後面括弧裡面表示方法的參數,兩個句點表示任何參數。

總結

通過示例和示例2,我們可以看出,不管是基于注解的攔截還是基于方法規則的攔截,對于切面而言,最重要的是明确切點集合,也就是建言的作用範圍。

基于注解的攔截:需要在被攔截處使用自定義的注解進行聲明,需要在切面中通過@Pointcut(“@annotation(注解類)”)注解作用于方法的形式來明确切點集合,然後方法名作為advise的參數,來确定該建言的作用範圍。

基于規則方法的攔截:被攔截處不需要加任何注解,隻需要在切面中通過execution(方法集合)作為建言的參數,來确定該建言的作用範圍。

其中,注解式攔截能夠很好地控制要攔截的粒度和獲得更豐富的資訊。Spring本身在事務處理(@Transcational)和資料緩存(@Cacheable等)上面都使用此種形式的攔截。

(歡迎指導,共同進步!)