Spring架構(IOC,DI,AOP)
Spring架構是Java開源項目的一員,可提高項目的開發效率,其架構中有多個子產品,包括:
- 核心容器
- Spring-AOP
- Spring Data Access(資料通路)
- Web子產品
- 封包發送
- 單元測試
需要了解的重點是核心容器中的IOC(控制反轉)和DI(依賴注入)以及Spring-AOP。
首先我們需要搭建整個架構的配置檔案,建立maven項目後在pom.xml檔案中需要添加以下依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.6</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
同時在配置spring.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--此處添加後續需要使用的内容-->
</beans>
1. Spring IOC容器 Bean對象執行個體化
先建構UserDao類javabean以及靜态工廠類StaticFactory類和執行個體化工廠類UserFactory類
定義UserDao類:
public class UserDao {
private String uname;
private String pwd;
//空構造,帶參構造,get和set方法,toString方法就不寫出來了
}
定義靜态工廠類:
import com.gao.dao.UserDao;
public class StaticFactory {
public static UserDao getUserDao(){
return new UserDao();
}
}
定義執行個體化工廠類:
import com.gao.dao.UserDao;
public class UserFactory {
public UserDao getUserDao(){
return new UserDao();
}
}
對象執行個體化方式有三種:
- 構造器執行個體化:
- 靜态工廠執行個體化:
- 執行個體化工廠執行個體化:
<bean id="userFactory" class="com.gao.factory.UserFactory"></bean>
<bean id="userDao" factory-bean="userFactory" factory-method="getUserDao"></bean>
測試方法:
@Test
public void test(){
ApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
UserDao userDao = (UserDao) app.getBean("userDao");
System.out.println(userDao);
userDao.dao();
}
2. Spring IOC注入
2.1 手動注入
Spring支援的手動注入方式有四種:set 注⼊、構造器注⼊、靜态⼯⼚注⼊、執行個體化⼯⼚注⼊
定義UserDao類:
public class UserDao {
private String uname;
private String pwd;
//空構造,帶參構造,get和set方法,toString方法就不寫出來了
}
定義UserService02類:
public class UserService02 {
private UserDao userDao;
//空構造,帶參構造,get和set方法,toString方法就不寫出來了
}
定義TestDao類:
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class TestDao {
private List<Integer> list;
private Set<Integer> set;
private Map<Integer,String> map;
private Properties prof;
//空構造,帶參構造,get和set方法,toString方法就不寫出來了
}
2.1.1 set注入
推薦使用set注入,前提是屬性字段需要提供set方法
如果類的成員為常用對象和基本類型:
<bean id="userDao" class="com.gao.dao.UserDao">
<property name="uname" value="zhangsan"></property>
<property name="pwd" value="123456"></property>
</bean>
如果類的成員為javabean:
<bean id="userDao" class="com.gao.dao.UserDao"></bean>
<bean id="userService02" class="com.gao.service.UserService02">
<property name="userDao" ref="userDao"></property>-->
</bean>
如果類的成員為集合類型:
<bean id="testDao" class="com.gao.dao.TestDao">
<property name="list">
<list>
<value>2</value>
<value>3</value>
<value>4</value>
</list>
</property>
<property name="set">
<set>
<value>1</value>
<value>5</value>
<value>2</value>
</set>
</property>
<property name="map">
<map>
<entry>
<key><value>1</value></key>
<value>zhangsan</value>
</entry>
<entry>
<key><value>3</value></key>
<value>lisi</value>
</entry>
<entry>
<key><value>2</value></key>
<value>wangwu</value>
</entry>
</map>
</property>
<property name="prof">
<props>
<prop key="username">haha</prop>
<prop key="pwd">123456</prop>
</props>
</property>
</bean>
2.1.2 構造器注入
實作構造器注入前提是提供帶參構造器
如果類的成員為常用對象和基本類型:
<bean id="userDao" class="com.gao.dao.UserDao">
<constructor-arg index="0" value="lisi"></constructor-arg>
<constructor-arg index="1" value="123"></constructor-arg>
</bean>
如果類的成員為javabean:
<bean id="userDao" class="com.gao.dao.UserDao"></bean>
<bean id="userService02" class="com.gao.service.UserService02">
<constructor-arg index="0" ref="userDao"></constructor-arg>
</bean>
注:如果出現循環依賴即兩個類互相将對方作為成員,此時需要使用set注入
2.1.3 靜态工廠注入
使用靜态工廠執行個體化類中的bean對象成員,使用set注入
<bean id="userDao" class="com.gao.factory.StaticFactory" factory-method="getUserDao"></bean>
<bean id="userService02" class="com.gao.service.UserService02">
<property name="userDao" ref="userDao"></property>-->
</bean>
2.1.4 執行個體化工廠注入
使用執行個體化工廠執行個體化類中的bean對象成員,使用set注入
<bean id="userFactory" class="com.gao.factory.UserFactory"></bean>
<bean id="userDao" factory-bean="userFactory" factory-method="getUserDao"></bean>
<bean id="userService02" class="com.gao.service.UserService02">
<property name="userDao" ref="userDao"></property>-->
</bean>
2.2 自動注入
定義UserDao類:
public class UserDao {
private String uname;
private String pwd;
//空構造,帶參構造,get和set方法,toString方法就不寫出來了
}
定義UserService類:
public class UserService {
private UserDao userDao;
//空構造,帶參構造,get和set方法,toString方法就不寫出來了
}
開啟自動化注入:
<context:annotation-config/>-->
<bean id="userDao" class="com.gao.dao.UserDao"></bean>-->
<bean id="userService" class="com.gao.service.UserService"></bean>-->
2.2.1 @Resource注解
推薦使用@Resource注解
- 預設根據屬性字段名稱查找對應的bean對象 (屬性字段的名稱與bean标簽的id屬性值相等)
- 如果屬性字段名稱未找到,則會通過類型(Class類型)查找
- 屬性可以提供set⽅法,也可以不提供set⽅法
- 注解可以聲明在屬性上或set⽅法上
- 可以設定name屬性,name屬性值必須與bean标簽的id屬性值⼀緻;如果設定了name屬性值,就隻會按照name屬性值查找bean對象
- 當注⼊接⼝時,如果接⼝隻有⼀個實作則正常執行個體化;如果接⼝存在多個實作,則需要使⽤name屬性指定需要被執行個體化的bean對象
用法:
public class UserService {
//name可省略
@Resource(name = "userDao")
private UserDao userDao;
//空構造,帶參構造,get和set方法,toString方法就不寫出來了
}
2.2.2 @Autowired注解
- 預設通過類型(Class類型)查找bean對象,與屬性字段的名稱⽆關
- 屬性可以提供set⽅法,也可以不提供set⽅法
- 注解可以聲明在屬性上或set⽅法上
- 可以添加@Qualifier結合使⽤,通過value屬性值查找bean對象(value屬性值必須要設定,且值要與bean标簽的id屬性值對應)
用法:
public class UserService {
//@Qualifier可省略
@Autowired
@Qualifier(value="userDao")
private UserDao userDao;
//空構造,帶參構造,get和set方法,toString方法就不寫出來了
}
2.3 IOC掃描器
使用IOC掃描器後不用再設定bean标簽,設定自動化掃描範圍:
注解有以下幾種:
- @Repository(Dao層)
- @Service(Service層)
- @Controller(Controller層)
- @Component(任意層)
3. Spring AOP
AOP即面向切面程式設計,用來攔截整個面的功能,主要用于⽇志記錄,性能統計,安全控制,事務處理等⽅⾯,實作公共功能性的重複使⽤。
AOP的底層實作:動态代理(JDK+CGLIB)
3.1 AOP的基本概念
- Joinpoint(連接配接點):spring中指被攔截到的每一個方法
- Pointcut(切⼊點):比對規則定義攔截哪些方法,對哪些方法進行處理
- Advice(通知):攔截到方法後要做的操作,包括:
- 前置通知:before 執行方法前通知
- 傳回通知:afterReturn 方法正常結束傳回後的通知
- 異常抛出通知 afterThrow
- 最終通知:after 無論方法是否發生異常,均會執行該通知
- 環繞通知:around 可以在方法調用前後完成自定義行為,也會選擇是否繼續執⾏連接配接點或直接傳回它們⾃⼰的傳回值或抛出異常來結束執⾏
- Aspect(切⾯):切入點和通知的結合,與類相似,是對橫切關注點的抽象
- Target(⽬标對象):被代理的目标對象
- Weave(織⼊):将切面應用到目标對象并生成代理對象的過程
- Introduction(引⼊):在不修改源碼的前提下,在程式運⾏期為類動态添加⽅法或者字段的過程
3.2 注解實作AOP
AOP切入點表達式簡介:
- 執⾏任意公共⽅法:execution(public *(…))
- 執⾏任意的set⽅法:execution(* set*(…))
- 執⾏com.xxxx.service包下任意類的任意⽅法:execution(* com.xxxx.service..(…))
- 執⾏com.xxxx.service 包以及⼦包下任意類的任意⽅法:execution(* com.xxxx.service….(…))
注:表達式中第一個*是方法的修飾符
配置檔案:
<!--配置AOP代理-->
<aop:aspectj-autoproxy/>
定義切面的方法一:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAop {
//定義切點
@Pointcut("execution(* com.gao.controller.UserController.*(..))")
public void cut(){}
//聲明前置通知
@Before(value = "cut()")
public void aopBefore(){
System.out.println("before...");
}
//聲明傳回通知
@AfterReturning(value = "cut()")
public void aopAfterReturn() {
System.out.println("傳回通知.....");
}
//聲明最終通知
@After(value = "cut()")
public void aopAfter(){
System.out.println("after...");
}
//聲明異常通知
@AfterThrowing(value="cut()",throwing = "e")
public void aopAfterThrow(Exception e) {
System.out.println("異常通知....." + " 異常原因:" + e.getCause());
}
//聲明環繞通知
@Around(value = "cut()")
public Object aopAround(ProceedingJoinPoint pjp){
System.out.println("前置通知...");
Object obj = null;
try {
obj = pjp.proceed();
System.out.println(pjp.getTarget()+"-->"+pjp.getSignature());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("後置通知");
return obj;
}
}
定義切面的方法二(将切點與通知結合在一起):
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAop {
@Before("execution(* com.gao.controller.UserController.*(..))")
public void aopBefore(){
System.out.println("before...");
}
@AfterReturning("execution(* com.gao.controller.UserController.*(..))")
public void aopAfterReturn() {
System.out.println("傳回通知.....");
}
@After("execution(* com.gao.controller.UserController.*(..))")
public void aopAfter(){
System.out.println("after...");
}
@Around("execution(* com.gao.controller.UserController.*(..))")
public Object aopAround(ProceedingJoinPoint pjp){
System.out.println("前置通知...");
Object obj = null;
try {
obj = pjp.proceed();
System.out.println(pjp.getTarget()+"-->"+pjp.getSignature());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("後置通知");
return obj;
}
}
3.3 XML實作AOP
定義切面:
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Component
public class MyAop {
public void cut(){}
public void aopBefore(){
System.out.println("before...");
}
public void aopAfter(){
System.out.println("after...");
}
public Object aopAround(ProceedingJoinPoint pjp){
System.out.println("前置通知...");
Object obj = null;
try {
obj = pjp.proceed();
//System.out.println(pjp.getTarget()+"-->"+pjp.getSignature());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("後置通知...");
return null;
}
public void aopAfterReturn(){
System.out.println("傳回通知...");
}
public void aopAfterThrow(Exception e){
System.out.println("異常通知"+"異常原因:"+e.getCause());
}
}
配置xml檔案:
<!--配置aop-->
<aop:config>
<!--aop切面-->
<aop:aspect ref="myAop">
<!--定義aop切入點-->
<aop:pointcut id="cut" expression="execution(* com.gao.controller.UserController.*(..))"/>
<!--配置前置通知-->
<aop:before method="aopBefore" pointcut-ref="cut"/>
<!--配置最終通知-->
<aop:after method="aopAfter" pointcut-ref="cut"/>
<!--配置環繞通知-->
<aop:around method="aopAround" pointcut-ref="cut"/>
<!--配置傳回通知-->
<aop:after-returning method="aopAfterReturn" pointcut-ref="cut"/>
<!--配置異常通知-->
<aop:after-throwing method="aopAfterThrow" throwing="e" pointcut-ref="cut"/>
</aop:aspect>
</aop:config>