概念
面向切面編程(也叫面向方面編程):Aspect Oriented Programming(AOP),是軟件開發(fā)中的一個熱點,也是Spring框架中的一個重要內(nèi)容。利用AOP可以對業(yè)務(wù)邏輯的各個部分進行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發(fā)的效率。
主要的功能是:日志記錄,性能統(tǒng)計,安全控制,事務(wù)處理,異常處理等等。
主要的意圖是:將日志記錄,性能統(tǒng)計,安全控制,事務(wù)處理,異常處理等代碼從業(yè)務(wù)邏輯代碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導(dǎo)業(yè)務(wù)邏輯的方法中,進而改變這些行為的時候不影響業(yè)務(wù)邏輯的代碼。

我們要做的,是定義一個切面,在切面的縱向定義處理方法,處理完成后,回到橫向業(yè)務(wù)流(靜態(tài)代理模式實現(xiàn) proxy)。
因為Java 是一門靜態(tài)的強類型語言, 代碼一旦寫好, 編譯成 java class 以后 ,可以在運行時通過反射(Reflection)來查看類的信息, 但是對類進行修改的話很困難。有如下方式來實現(xiàn):
- 在編譯的時候, 根據(jù)AOP的配置信息,悄悄的把日志,安全,事務(wù)等“切面”代碼 和業(yè)務(wù)類編譯到一起去。【預(yù)編譯】
- 在運行期,業(yè)務(wù)類加載以后, 通過Java動態(tài)代理技術(shù)為業(yè)務(wù)類生產(chǎn)一個代理類, 把“切面”代碼放到代理類中, Java 動態(tài)代理要求業(yè)務(wù)類需要實現(xiàn)接口才行。【運行期動態(tài)代理】
- 在運行期, 業(yè)務(wù)類加載以后, 動態(tài)的使用字節(jié)碼構(gòu)建一個業(yè)務(wù)類的子類,將“切面”邏輯加入到子類當(dāng)中去, CGLIB就是這么做的。
spring 采用(1)+(2)方式
實現(xiàn)方式
1. 實現(xiàn) AOP 接口
2. 通過.xml配置方式
<bean id="personDao" class="com.itheima12.spring.aop.xml.transaction.PersonDaoImpl"></bean>
<bean id="transaction" class="com.itheima12.spring.aop.xml.transaction.Transaction"></bean>
<aop:config>
<!-- 切入點表達式 確定目標(biāo)類 -->
<expression="execution(* com.itheima12.spring.aop.xml.transaction.PersonDaoImpl.*(..))" id="perform"/>
<!-- ref指向的對象就是切面 -->
<aop:aspect ref="transaction">
<!--
前置通知
1、在目標(biāo)方法執(zhí)行之前
2、獲取不到目標(biāo)方法的返回值
-->
<!--
<aop:before method="beginTransaction" pointcut-ref="perform"/>
-->
<!--
后置通知
1、后置通知可以獲取到目標(biāo)方法的返回值
2、當(dāng)目標(biāo)方法拋出異常,后置通知將不再執(zhí)行
-->
<!--
<aop:after-returning method="commit" pointcut-ref="perform" returning="val"/>
-->
<!--
最終通知
無論目標(biāo)方法是否拋出異常都將執(zhí)行
-->
<aop:after method="finallyMethod" pointcut-ref="perform"/>
<!--
異常通知(多個異常的處理)這個異常處理是完全獨立于系統(tǒng)之外的,脫離業(yè)務(wù)邏輯
-->
<aop:after-throwing method="throwingMethod" throwing="ex" pointcut-ref="perform"/>
<!--
環(huán)繞通知(可以進行權(quán)限管理,比如 shiro 底層)
1. 能控制目標(biāo)方法的執(zhí)行
2. 前置通知和后置通知能在目標(biāo)方法的前面和后面加一些代碼,但是不能控制目標(biāo)方法的執(zhí)行
-->
<aop:around method="aroundMethod" pointcut-ref="perform"/>
</aop:aspect>
</aop:config>
一些概念
- 切面(aspect):用來切插業(yè)務(wù)方法的類。
- 連接點(joinpoint):是切面類和業(yè)務(wù)類的連接點,其實就是封裝了業(yè)務(wù)方法的一些基本屬性,作為通知的參數(shù)來解析。
- 通知(advice):在切面類中,聲明對業(yè)務(wù)方法做額外處理的方法。
- 切入點(pointcut):業(yè)務(wù)類中指定的方法,作為切面切入的點。其實就是指定某個方法作為切面切的地方。
- 目標(biāo)對象(target object):被代理對象。
- AOP代理(aop proxy):代理對象。
- 通知
- 前置通知(before advice):在切入點之前執(zhí)行。
- 后置通知(after returning advice):在切入點執(zhí)行完成后,執(zhí)行通知。
- 環(huán)繞通知(around advice):包圍切入點,調(diào)用方法前后完成自定義行為。
- 異常通知(after throwing advice):在切入點拋出異常后,執(zhí)行通知。
切入點表達式

Spring AOP 的原理:
- 當(dāng)spring啟動的時候,加載兩個bean,對兩個bean進行實例化
- 當(dāng)spring容器對配置文件解析到
<aop:config>的時候,把切入點表達式解析出來,按照切入點表達式匹配spring容器內(nèi)的bean。 - 如果匹配成功,則為該
bean創(chuàng)建對象 - 當(dāng)客戶端利用context.getBean獲取一個對象時,如果該對象有代理對象,則返回代理對象。如果沒有,則返回本身
應(yīng)用實例
1. AOP 權(quán)限管理(環(huán)繞通知)
自定義注解
@Target(ElementType.METHOD) //范圍:在方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface PrivlegeInfo {
String name() default "";
}
編寫注解解析器
public class AnnotationParse {
/*
* targetClass 目標(biāo)類的class形式
* methodName 在客戶端調(diào)用哪個方法,methodName就代表哪個方法
*/
public static String parse(Class targetClass,String methodName) throws Exception{
String methodAccess = "";
/**
* 該方法沒有參數(shù)
*/
Method method = targetClass.getMethod(methodName);
//判斷方法上面是否存在PrivilegeInfo注解
if(method.isAnnotationPresent(PrivlegeInfo.class)){
//得到方法上面的注解
PrivlegeInfo privlegeInfo = method.getAnnotation(PrivlegeInfo.class);
methodAccess = privlegeInfo.name();
}
return methodAccess;
}
}
service方法
public class PersonServiceImpl implements PersonService{
@PrivlegeInfo(name="savePerson")
public void savePerson() {
System.out.println("save person");
}
@PrivlegeInfo(name="updatePerson")
public void updatePerson() {
System.out.println("update person");
}
}
用戶權(quán)限的切面類
public class PrivilegeAspect {
/**
* 用戶擁有的權(quán)限
*/
private List<Privilege> privileges = new ArrayList<Privilege>();
public List<Privilege> getPrivileges() {
return privileges;
}
public void setPrivileges(List<Privilege> privileges) {
this.privileges = privileges;
}
public void isAccessMethod(ProceedingJoinPoint joinPoint) throws Throwable{
/**
* 1、獲取訪問目標(biāo)方法應(yīng)該具備的權(quán)限
* 得到
* 1、目標(biāo)類的class形式
* 2、方法的名稱
*/
Class targetClass = joinPoint.getTarget().getClass();
String methodName = joinPoint.getSignature().getName();
//得到訪問該方法的權(quán)限
String methodAccess = AnnotationParse.parse(targetClass, methodName);
boolean flag = false;
//遍歷用戶所有的權(quán)限,查看是否用訪問該方法的權(quán)限
for (Privilege privilege : privileges) {
//該用戶能夠訪問目標(biāo)方法
if(methodAccess.equals(privilege.getName())){
flag = true;
}
}
if(flag){//訪問目標(biāo)方法
joinPoint.proceed();
}else{
System.out.println("對不起,您沒有權(quán)限訪問");
}
}
}
application 配置文件
<aop:config>
<aop:pointcut
expression="execution(* com.itheima12.spring.aop.xml.privilege.service.impl.*.*(..))" id="perform"/>
<aop:aspect ref="privilegeAspect">
<aop:around method="isAccessMethod" pointcut-ref="perform"/>
</aop:aspect>
</aop:config>
測試
public class PrivilegeTest {
@Test
public void testPrivilege(){
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
/**
* 初始化用戶的權(quán)限
*/
PrivilegeAspect privilegeAspect = (PrivilegeAspect)context.getBean("privilegeAspect");
Privilege privilege1 = new Privilege();
privilege1.setName("savePerson");
Privilege privilege2 = new Privilege();
privilege2.setName("updatePerson");
privilegeAspect.getPrivileges().add(privilege2);
privilegeAspect.getPrivileges().add(privilege1);
PersonService personService = (PersonService)context.getBean("personService");
personService.savePerson();
personService.updatePerson();
}
}
2. AOP 緩存
application
<context:component-scan base-package="com.itheima12.spring.aop.xml.transaction">
</context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
切面類
/**
* @Aspect
* @Pointcut("execution(* com.itheima12.spring.aop.xml.transaction.PersonDaoImpl.*(..))")
* private void aa(){}
就相當(dāng)于
* <aop:config>
<aop:pointcut
expression="execution(* com.itheima12.spring.aop.xml.transaction.PersonDaoImpl.*(..))"
id="aa()"/>
</aop:config>
* @author zd
*
*/
@Component("transaction") // 加入到spring容器中
@Aspect // 證明這個注解所在類是切面類
public class Transaction {
@Pointcut("execution(* com.itheima12.spring.aop.xml.transaction.PersonDaoImpl.*(..))")
private void aa(){} //方法簽名
@Before("aa()") // 前置通知
public void beginTransaction(){
System.out.println("begin transaction");
}
@AfterReturning("aa()") // 后置通知
public void commit(){
System.out.println("commit");
}
}
3. AOP 日志管理
此項目是在 spring boot 環(huán)境下實現(xiàn)。
1、 添加maven依賴注解
<!--springBoot 的aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、添加數(shù)據(jù)庫表
DROP TABLE IF EXISTS `journal`;
CREATE TABLE `journal` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '日志id',
`uid` int(11) NOT NULL COMMENT '用戶id',
`modularType` int(2) NOT NULL COMMENT '模塊類型',
`operationType` int(2) NOT NULL COMMENT '操作類型:0:增/1:刪/2:改/3:關(guān)閉/4:移動',
`operationTime` datetime NOT NULL COMMENT '操作時間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3、增加對應(yīng)的實體類
4、日志添加 Mapper
/**
* 日志管理
* Created by 陳梓平 on 2017/8/12.
*/
public interface JournalMapper {
/**日志添加*/
int addJournalInfo(JournalInfo journalInfo);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chen.mapper.JournalMapper">
<!--添加日志信息-->
<insert id="addJournalInfo">
INSERT INTO journal (uid,modularType,operationType,operationTime)
VALUES (10086,#{modularType},#{operationType},NOW())
</insert>
</mapper>
5、日志工具類
@Component
@Transactional
public class JournalUtils {
@Autowired
private JournalMapper jouUtilsJournalMapper;
@Autowired
private JournalInfo journalInfo;
/**
* 添加日志
* @param modeularType
* @param operationType
*/
public void addJournalInfo(int modeularType,int operationType,int uid) {
journalInfo.setModularType(modeularType);
journalInfo.setOperationType(operationType);
journalInfo.setUid(uid);
jouUtilsJournalMapper.addJournalInfo(journalInfo);
}
}
6、靜態(tài)類(包括模塊和操作)
/**
* 靜態(tài)信息
* Created by Administrator on 2017/8/12.
*/
public class StaticInfo {
/**-------------------- 模塊類型 ----------------*/
//模塊1
public static final int MODEULARTTYPE_FIRST= 1;
/**-------------------- 操作類別 ---------------*/
//增加
public static final int OPERATIONTYPE_ADD = 0;
//刪除
public static final int OPERATIONTYPE_UPDATE = 1;
//修改
public static final int OPERATIONTYPE_DELETE = 2;
//開啟
public static final int OPERATIONTYPE_OPEN = 3;
//關(guān)閉
public static final int OPERATIONTYPE_CLOSE = 4;
//移動
public static final int OPERATIONTYPE_MOVER = 5;
/**--------------- AOP代理 --------------------*/
public static final String AOP_OPERATION_TYPE_ADD = "add";
public static final String AOP_OPERATION_TYPE_EDIT = "edit";
public static final String AOP_OPERATION_TYPE_MOVE = "move";
public static final String AOP_OPERATION_TYPE_DELETE = "delete";
public static final String AOP_OPERATION_TYPE_OPENORCLOSE = "openOrClose";
public static final String AOP_MODULAR_TYPE_FIRST = "Journal";
public static final String AOP_SPIT_CLASSNAME = "impl.";
public static final String AOP_SPIT_MODULAR_TYPE= "ServiceImpl";
}
7、日志切面AOP
@Component
@Aspect
public class JournalAspect {
/**日志輸出*/
private static final Logger logger = LoggerFactory.getLogger(JournalAspect.class);
/**日志工具類*/
@Autowired
private JournalUtils aspectJournalUtils;
/**service層切面*/
private final String POINT_CUT = "execution(* com.chen.service..*(..))";
@Pointcut(POINT_CUT)
private void pointcut(){}
/**
* 后置最終通知(目標(biāo)方法只要執(zhí)行完了就會執(zhí)行后置通知方法)
* 日志管理
* @param joinPoint
*/
@After(value = "pointcut()")
@Transactional
public void doAfterAdvice(JoinPoint joinPoint) throws CustomException {
//用的最多 通知的簽名
Signature signature = joinPoint.getSignature();
//1.獲取模塊類型
//AOP代理類的名字(包括包名)
String declaringTypeName = signature.getDeclaringTypeName();
logger.info("AOP代理類的名字"+declaringTypeName);
//獲取代理類的類名
String[] split = declaringTypeName.split(StaticInfo.AOP_SPIT_CLASSNAME);
String className = split[1];
//獲取模塊名
String[] modularTypeNames = className.split(StaticInfo.AOP_SPIT_MODULAR_TYPE);
String modularTypeName = modularTypeNames[0];
int modulerType = -1;
//模塊類型篩選
modulerType = this.getModularType(modularTypeName, modulerType);
//2.獲取操作類型
//代理的是哪一個方法
String methodName = signature.getName();
logger.info("AOP代理方法的名字"+signature.getName());
int opreationType = -1;
opreationType = getOpreationType(joinPoint, signature, opreationType,methodName);
if (modulerType==-1&&opreationType==-1)
if (!StringUtils.isBlank(methodName)||!StringUtils.isBlank(modularTypeName))
throw new CustomException(ResultEnum.JOURNAL_LOG_ERROR);
//3.添加日志
if (modulerType!=-1&&opreationType!=-1)
//TODO 3.1 從請求獲取用戶id
aspectJournalUtils.addJournalInfo(modulerType,opreationType, 10086);
}
/**
* 模塊類型篩選
* @param modularTypeName
* @param type
* @return
*/
private int getModularType(String modularTypeName, int type) {
//模塊類型篩選
switch (modularTypeName){
case StaticInfo.AOP_MODULAR_TYPE_FIRST:
type = StaticInfo.MODEULARTTYPE_FIRST;
break;
//多模塊添加
}
return type;
}
/**
* 獲取操作類型
* @param joinPoint
* @param signature
* @param opreationType
* @return
*/
private int getOpreationType(JoinPoint joinPoint, Signature signature, int opreationType,String methodName ) {
switch (methodName){
case StaticInfo.AOP_OPERATION_TYPE_ADD:
opreationType = StaticInfo.OPERATIONTYPE_ADD;
break;
case StaticInfo.AOP_OPERATION_TYPE_EDIT:
opreationType = StaticInfo.OPERATIONTYPE_UPDATE;
break;
case StaticInfo.AOP_OPERATION_TYPE_MOVE:
opreationType = StaticInfo.OPERATIONTYPE_MOVER;
break;
case StaticInfo.AOP_OPERATION_TYPE_DELETE:
opreationType = StaticInfo.OPERATIONTYPE_DELETE;
break;
case StaticInfo.AOP_OPERATION_TYPE_OPENORCLOSE:
Object[] obj = joinPoint.getArgs();
int arg = (int) obj[1];
if (arg==1)
opreationType = StaticInfo.OPERATIONTYPE_OPEN;
else
opreationType = StaticInfo.OPERATIONTYPE_CLOSE;
break;
}
return opreationType;
}
}
8、添加Controller測試
@RestController
@RequestMapping
public class JournalController {
@Autowired
private JournalService journalService;
@PostMapping(value = "journalAdd")
public Result add(){
return journalService.add();
}
}
9、添加Service測試
@Service
public class JournalServiceImpl implements JournalService {
@Override
public Result add() {
return ResultUtils.success(ResultEnum.OK);
}
}
4. AOP 實現(xiàn)分布式鎖
改造前:
所有應(yīng)用分布式鎖的地方都需要如下代碼:
RLock redissonLock = redissonUtil.getRedisson().getLock("saveCourseApplyResource"+courseApplyResource.getUserId());
boolean res = false;
try {
//等待3秒,有效期5秒
res = redissonLock.tryLock(3, 5, TimeUnit.SECONDS);
if(res){
//執(zhí)行業(yè)務(wù)操作
}
}catch (RuntimeException e){
throw e;
} catch (InterruptedException e) {
e.printStackTrace();
throw new RuntimeException("網(wǎng)絡(luò)錯誤,請重試");
} finally {
if(res){
redissonLock.unlock();
}
}
改造后:
- 首先需要一個注解:
/**
* Description: 分布式鎖應(yīng)用注解<br>
*
* @author: name:yuxin <br>email: yuruixin@ixincheng.com <br>
* Create Time: 2018/3/4 0004-下午 8:48<br>
*/
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {
//分布式鎖的key前綴
String lockName() default "";
//等待時長
int waitTime() default 3;
//有效期時長
int effectiveTime() default 5;
}
- 然后,需要一個切面服務(wù)類
/**
* Description: 分布式鎖切面服務(wù)<br>
*
* @author: name:yuxin <br>email: yuruixin@ixincheng.com <br>
* Create Time: 2018/3/4 0004-下午 8:46<br>
*/
import com.xczhihui.bxg.online.common.utils.RedissonUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
@Aspect
public class LockService {
public static String LOCK_NAME = "lockName";
public static String WAIT_TIME = "waitTime";
public static String EFFECTIVE_TIME = "effectiveTime";
@Autowired
private RedissonUtil redissonUtil;
protected Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut("@annotation(com.xczhihui.common.Lock)")
public void lockPointcut() {
}
@Around("lockPointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Map<String,Object> map = getLockParams(point);
String lockName = (String) map.get(LOCK_NAME);
int waitTime = (int) map.get(WAIT_TIME);
int effectiveTime = (int) map.get(EFFECTIVE_TIME);
Object[] methodParam = null;
Object object=null;
boolean resl = false;
//獲取方法參數(shù)
methodParam = point.getArgs();
String lockKey = (String) methodParam[0];
// 獲得鎖對象實例
RLock redissonLock = redissonUtil.getRedisson().getLock(lockName+lockKey);
try {
//等待3秒 有效期8秒
resl = redissonLock.tryLock(waitTime, effectiveTime, TimeUnit.SECONDS);
if(resl){
object = point.proceed(point.getArgs());
}
}catch (RuntimeException e){
throw e;
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("網(wǎng)絡(luò)錯誤,請重試");
}finally {
if(resl){
logger.info("開鎖,{}",lockName+lockKey);
redissonLock.unlock();
}else{
logger.error("未獲得鎖,{}",lockName+lockKey);
throw new RuntimeException("網(wǎng)絡(luò)錯誤,請重試");
}
}
return object;
}
/**
* Description:獲取方法的中鎖參數(shù)
* creed: Talk is cheap,show me the code
* @author name:yuxin <br>email: yuruixin@ixincheng.com
* @Date: 2018/3/4 0004 下午 8:59
**/
public Map<String,Object> getLockParams(ProceedingJoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] method = targetClass.getMethods();
Map<String,Object> map = new HashMap<>();
for (Method m : method) {
if (m.getName().equals(methodName)) {
Class[] tmpCs = m.getParameterTypes();
if (tmpCs.length == arguments.length) {
Lock lock = m.getAnnotation(Lock.class);
if (lock != null) {
String lockName = lock.lockName();
int waitTime = lock.waitTime();
int effectiveTime = lock.effectiveTime();
map.put(LOCK_NAME,lockName);
map.put(WAIT_TIME,waitTime);
map.put(EFFECTIVE_TIME,effectiveTime);
}
break;
}
}
}
return map;
}
}
- 在需要加鎖的方法上添加注解:
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Lock(lockName = "addCollectionApply",waitTime = 2,effectiveTime = 3)
public void saveCollectionApply4Lock(String lockKey,CourseApplyInfo courseApplyInfo){
//業(yè)務(wù)邏輯處理
}