Spring–AOP 面向切面程式設計
一.AOP介紹
為什麼使用AOP?
- java是oop面向對象的語言,OOP引入封裝、繼承和多态等概念來建立一種對象層次結構,也就是說,OOP允許你定義從上到下的關系,但并不适合定義從左到右的關系。例如日志功能。日志代碼一般都是水準地散布在所有對象層次中,但是它和對象的核心功能毫無關系。對于其他類型的代碼,如安全性、異常處理也是如此。這種代碼就稱為橫切代碼,在OOP設計中,它會導緻了大量代碼的重複,而不利于各個子產品的重用。
- AOP的核心就是代理,不用修改任何已經編寫好的代碼,隻要使用代理就可以靈活的加入任何東西,将來不喜歡了,不用也不會影響原來的代碼。
JDK動态代理
- 代理設計模式的原理: 使用一個代理将對象包裝起來, 然後用該代理對象取代原始對象. 任何對原始對象的調用都要通過代理.代理對象決定是否以及何時将方法調用轉到原始對象上。
- JDK動态代理開發
- 接口類
package com.wmy.spring.ao;
import java.math.BigDecimal;
public interface BankService {
public void transfer(String target, String source, BigDecimal money);
public String withdraw(String account, BigDecimal money);
public int saveMoney(String account,BigDecimal money);
}
- 接口實作類
package com.wmy.spring.ao;
import java.math.BigDecimal;
public class BankServiceImpl implements BankService{
public void transfer(String target, String source, BigDecimal money) {
}
public String withdraw(String account, BigDecimal money) {
return "AOP";
}
public int saveMoney(String account, BigDecimal money) {
return 100;
}
}
- 代理類
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ServiceProxy implements InvocationHandler{
private Object target;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法:" + method.getName() + "被執行");
for(Object arg : args) {
System.out.println("參數:" + arg);
}
Object returnValue = method.invoke(target, args);
System.out.println("方法:" + method.getName() + "執行完畢,傳回值:" + returnValue);
return returnValue;
}
public Object createProxy(Object target) {
this.target = target;
return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
}
}
- 測試
package com.wmy.spring.ao;
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
BankService bs = new BankServiceImpl();
BankService bsProxy = (BankService) new ServiceProxy().createProxy(bs);
bsProxy.withdraw( "李四", new BigDecimal("500"));
bsProxy.saveMoney("張三", new BigDecimal("10000"));
}
}
AOP的優勢
- 降低子產品耦合度
- 使系統容易擴充
- 更好的代碼複用性
AOP的術語
- 切面(Aspect)
一個橫切關注點的子產品化,這個關注點可能會橫切多個對象。它是橫切關注點的另一種表達方式。
- 連接配接點(Joinpoint)
在程式執行過程中某個特定的點,比如某方法調用的時候或者處理異常的時候。
- 切入點(Pointcut)
比對連接配接點的斷言。它通常是一個表達式,有專門的文法,用于指明在哪裡(或者說在哪些方法調用上)嵌入橫切邏輯
- 通知(Advice)
在切面的某個特定的連接配接點上執行的動作,也就是我們前面提到的橫切邏輯,如日志處理邏輯,事務處理邏輯。
- 目标對象(Target Object)
被一個或者多個切面所通知的對象,也被稱作被通知對象
- 代理對象(Proxy Object)
AOP架構建立的對象,它和目标對象遵循同樣的接口,使用它的方式和使用目标對象的方式是一樣的,但是它是目标對象的增強版,“通知”中的代碼執行将會被代理對象的方法調用觸發。在Spring中,AOP代理可以是JDK動态代理或者CGLIB代理。
二.Spring AOP開發
方法一:利用xml
1. 相關jar包
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
2. xml配置檔案
表達式:expression=“execution(* springboot.aop.xml. * .* (…))”
第一個表示不限制傳回值類型
第二個 表示springboot.aop.xml包下所有的JAVA BEAN
第三個* javabean 所有的方法
(…) 表示對方法的參數沒有限制
<aop:config>
<aop:pointcut expression="execution(* springboot.aop.xml.*.*(..))" id="loggerPointCut"/>
<aop:aspect ref="loggerAspect">
<aop:before method="logerBefore" pointcut-ref="loggerPointCut"/>
</aop:aspect>
</aop:config>
3. 業務代碼
通知方法的編寫
- 前置通知:在方法執行之前執行。
xml:
通知類java類:
public void logerBefore(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("method: " + methodName + "将要被執行!");
Object[] args = jp.getArgs();
for(Object arg : args) {
System.out.println("=============參數:>" + arg);
}
}
- 後置通知:在方法執行之後執行
xml:
通知類java類:
public void logerAfter(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("method: " + methodName + "已經被執行!");
Object[] args = jp.getArgs();
for(Object arg : args) {
System.out.println("=============參數:>" + arg);
}
}
- 傳回通知:在連接配接點完成之後執行的, 即連接配接點傳回結果或者抛出異常的時候。
屬性 returning 的值必須與方法logerAfterReturning 傳回值的傳回值參數名保持一緻
xml:
通知類java類:
public void logerAfterReturning(JoinPoint jp, Object returnValue) {
String methodName = jp.getSignature().getName();
System.out.println("後置傳回通知 =========>method: " + methodName + "已經被執行!");
System.out.println("後置傳回通知 傳回值:" + returnValue);
}
- 異常通知, 在方法抛出異常之後
xml:
通知類java類:
public void loggerAfterThrowing(JoinPoint jp, Exception exception) {
System.out.println("後置異常通知:" + exception);
}
- 環繞通知
xml:
通知類java類:
public Object logerAround(ProceedingJoinPoint pjp) {
String methodName = pjp.getSignature().getName();
System.out.println("環繞通知 =========>method: " + methodName + "将要被執行!");
Object[] args = pjp.getArgs();//獲取參數
args[0] = "小明";
try {
Object returnValue = pjp.proceed(args);
System.out.println("環繞通知 =========>method: " + methodName + "已經被執行傳回值:! " + returnValue);
return new BigDecimal("999999999999999");
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
方法二:利用注解
1.spring配置檔案
<context:component-scan base-package="com.zzxtit.spring.aop.anno"></context:component-scan>
<!-- 開啟spring AOP 注解方式 自動代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.通知類java類編寫
使用@Aspect 和@Component标記為切面的Spring Bean元件
- 前置通知
第一個 * 表示不限制傳回值類型
第二個* 表示包下所有的JAVA BEAN
第三個* javabean 所有的方法
(…) 表示對方法的參數沒有限制
@Before("execution(* com.zzxtit.spring.aop.anno.*.*(..))")
public void beforeAdvice(JoinPoint jp) {
System.out.println("==================列印日志=====================");
Object[] args = jp.getArgs();
int index = 1;
for(Object arg : args) {
System.out.println("======第" + index +"個參數=======>" + arg);
index++;
}
System.out.println("方法:" + jp.getSignature().getDeclaringTypeName() + "." + jp.getSignature().getName());
}
- 後置通知
@After("execution(* com.zzxtit.spring.aop.anno.*.*(..))")
public void afterAdvice(JoinPoint jp) {
System.out.println("-----------列印日志-----後置通知-----------------------------");
}
- 後置傳回通知
@AfterReturning(value = "execution(* com.zzxtit.spring.aop.anno.*.*(..))", returning="returnValue")
public void afterReturningAdvice(JoinPoint jp, Object returnValue) {
System.out.println("----------------後置傳回通知-----------------------------" + returnValue);
}
- 環繞通知
@Around("execution(* com.zzxtit.spring.aop.anno.*.*(..))")
public void aroundAdvice(ProceedingJoinPoint pjp) {
Object returnValue = null;
try {
returnValue = pjp.proceed();//執行目标方法
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("----------------環繞通知-----------------------------" + returnValue);
}
- 異常通知
@AfterThrowing(value = "execution(* com.zzxtit.spring.aop.anno.*.*(..))", throwing="exception")
public void afterThrowing(JoinPoint jp, Exception exception) {
}
3.通過pointCut進行切入點的代碼抽離
@Pointcut("execution(* com.zzxtit.spring.aop.anno.*.*(..))")
public void loggerPointCut() {
}
@After("loggerPointCut()")
public void afterAdvice(JoinPoint jp) {
System.out.println("-----------列印日志-----後置通知-----------------------------");
}
/**
* 如果指定 pointcut的值,将會覆寫value屬性值
* @param jp
* @param returnValue
*/
@AfterReturning(value = "execution(* com.zzxtit.spring.aop.anno.*.*(..))", pointcut="loggerPointCut()", returning="returnValue")
public void afterReturningAdvice(JoinPoint jp, Object returnValue) {
System.out.println("----------------後置傳回通知-----------------------------" + returnValue);
}
4. @Order指定切面的優先級
值從0開始,越小優先級越高。
@Order(1)
@Aspect
@Component
三.Spring JDBC開發
Jdbc的配置
<context:property-placeholder location="config/DB.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${mysql_driver}"></property>
<property name="url" value="${mysql_url}"></property>
<property name="username" value="${mysql_username}"></property>
<property name="password" value="${mysql_passwd}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" value="#{dataSource}"></property>
</bean>
Java的Dao 層引用JdbcTemplate
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
}
- 增加
public void insertUserInfo(SysUserInfo su) {
String sql="insert into t_sys_user (user_name, passwd, salt, real_name, avatar, phone, email, gender, create_time) "
+ "values (?, ?, ?, ?, ?, ?, ?, ?, now())";
jdbcTemplate.update(sql, su.getUserName(), su.getPasswd(), su.getSalt(),
su.getRealName(), su.getAvatar(), su.getPhone(),
su.getEmail(), su.getGender());
}
- 删除
public void deleteUserInfo(SysUserInfo su) {
String sql = "delete from t_sys_user where passwd=?";
jdbcTemplate.update(sql, su.getPasswd());
}
- 修改
public void updateUserInfo(SysUserInfo su) {
String sql="update t_sys_user set user_name = ?, salt=?, real_name=?, avatar=?, phone=?, email=?, gender=?, create_time=? where passwd=?";
jdbcTemplate.update(sql, su.getUserName(), su.getSalt(),
su.getRealName(), su.getAvatar(), su.getPhone(),
su.getEmail(), su.getGender(),su.getPasswd());
}
- 查詢
public SysUserInfo getSysUserById(int userId) {
String sql = "select * from t_sys_user where user_id = ?";
List<SysUserInfo> suList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<SysUserInfo>(SysUserInfo.class), userId);
if(suList != null && suList.size() > 0) {
return suList.get(0);
}else {
return null;
}
}
JDBC中具名參數的使用
1.xml配置
<bean id="namedJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg index="0" ref="dataSource"></constructor-arg>
</bean>
2.Dao層的使用
public void insertUserInfoByNJP(SysUser su) {
String sql = "insert into t_sys_user (user_name, passwd, salt, real_name, avatar, phone, "
+ "email, gender, create_time) values ({userName:}, {passwd:}, {salt:}, {realName:}, {avatar:}, {phone:}, {email:}, {gender:}, now())";
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("userName", su.getUserName());
paramMap.put("passwd", su.getPasswd());
paramMap.put("salt", su.getSalt());
paramMap.put("realName", su.getRealName());
paramMap.put("avatar", su.getAvatar());
paramMap.put("phone", su.getPhone());
paramMap.put("email", su.getEmail());
paramMap.put("gender", su.getGender());
npjTemplate.update(sql, paramMap);
}