天天看點

Spring--AOP 面向切面程式設計Spring–AOP 面向切面程式設計

Spring–AOP 面向切面程式設計

一.AOP介紹

為什麼使用AOP?

  • java是oop面向對象的語言,OOP引入封裝、繼承和多态等概念來建立一種對象層次結構,也就是說,OOP允許你定義從上到下的關系,但并不适合定義從左到右的關系。例如日志功能。日志代碼一般都是水準地散布在所有對象層次中,但是它和對象的核心功能毫無關系。對于其他類型的代碼,如安全性、異常處理也是如此。這種代碼就稱為橫切代碼,在OOP設計中,它會導緻了大量代碼的重複,而不利于各個子產品的重用。
  • AOP的核心就是代理,不用修改任何已經編寫好的代碼,隻要使用代理就可以靈活的加入任何東西,将來不喜歡了,不用也不會影響原來的代碼。

JDK動态代理

  • 代理設計模式的原理: 使用一個代理将對象包裝起來, 然後用該代理對象取代原始對象. 任何對原始對象的調用都要通過代理.代理對象決定是否以及何時将方法調用轉到原始對象上。
    Spring--AOP 面向切面程式設計Spring–AOP 面向切面程式設計
  • 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);
		
	}