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
(2)輸入Maven項目坐标值
(3)選擇存儲路徑
(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等)上面都使用此種形式的攔截。
(歡迎指導,共同進步!)