链接: spring中的AOP.
AOP(Aspect Orient Programming),面向切面编程,是面向对象编程 OOP 的一种补充。
在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
AOP的优点就是降低代码之间的耦合,提高代码的复用性。
例如转账功能,在转账代码的前后需要一些非业务方面的处理,权限控制,记录日志,事务的开启与结束,这些代码就可以使用AOP将其切入到转账代码的前后,这样就可以很好地分离业务代码和非业务代码。
spring底层就是采用动态代理模式实现AOP的。采用了两种代理:
JDK 的动态代理,如果被代理了实现了接口,会默认使用jdk的动态代理。底层通过反射方式创建代理类的对象
CGLIB的动态代理,如果类没有实现接口,会使用CGLIB动态代理。底层是对代理类生成的class文件加载进来,通过修改其字节码生成子类来创建代理对象
(1)目标对象(Target)
目标对象指 将要被增强的对象。即包含主业务逻辑的类的对象。
上例中的UserDaoImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。
当然,不被增强,也就无所谓目标不目标了。
(2)切面(Aspect)
切面泛指非业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面有通知,实际就是对业务逻辑的一种增强。
(3)连接点(JoinPoint)
连接点指可以被切面织入的方法。通常业务接口中的方法均为连接点。
(4)切入点(Pointcut)
切入点指切面具体织入的方法。在 UserDaoImpl 类中,若 addUser()被增强,而doOther()不被增强,
则 addUser()为切入点,而 doOther()仅为连接点。
被标记为 final 的方法是不能作为连接点与切入点的,因为是不能被修改的,不能被增强的。
(5)通知(Advice)
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。
上例中的MyInvocationHandler 就可以理解为是一种通知。
换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。
通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。
Advice有下面几种,这里使用常用的AspectJ方式:
前置通知(Before advice):在连接点之前执行,即目标方法执行之前执行。
后置通知(After returning advice):在连接点正常结束之后执行,如果连接点抛出异常,则不执行。
异常通知(After throwing advice):在连接点抛出异常后执行
最终通知(After (finally) advice):在连接点结束之后执行,无论是否抛出异常,都会执行。
环绕通知(Around advice):在连接点之前和之后均执行。
(6)织入(Weaving)
织入是指将切面代码插入到目标对象的过程。上例中 MyInvocationHandler 类中的 invoke()
方法完成的工作,就可以称为织入。
(7)aop代理(AOP proxy)
spring中的aop代理有两种:jdk自带的动态代理和CGLIB代理。
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.49version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义aop切点注解
*/
@Target({
ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Power {
String value() default "";
}
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Aspect
@Component
public class PowerAspect {
/**
* Controller层切点 注解拦截
*/
@Pointcut("@annotation(com.zm.aop.Power)")
public void controllerAspect() {
}
/**
* 前置通知 用于拦截Controller层记录用户的操作的开始时间
*/
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint){
Power method = getMethod(joinPoint);
String shopId = getAnnotationValue(joinPoint, method.value());
System.out.println("shopId :" + shopId);
// 获取参数,执行逻辑...
if("1001".equals(shopId)){
System.out.println("1001无权限访问");
return;
}
System.out.println("正常访问");
}
/**
* 后置通知 用于拦截Controller层记录用户的操作
*/
@After("controllerAspect()")
public void doAfter(JoinPoint joinPoint){
}
/**
* 获取注解中对方法的描述信息
*/
public static Power getMethod(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature.getMethod().getAnnotation(Power.class);
}
/**
* 获取注解中传递的动态参数的参数值
*
* @param joinPoint
* @param name
* @return
*/
public String getAnnotationValue(JoinPoint joinPoint, String name) {
String paramName = name;
// 获取方法中所有的参数
Map<String, Object> params = getParams(joinPoint);
// 参数是否是动态的:#{paramName}
if (paramName.matches("^#\\{\\D*\\}")) {
// 获取参数名
paramName = paramName.replace("#{", "").replace("}", "");
// 是否是复杂的参数类型:对象.参数名
if (paramName.contains(".")) {
String[] split = paramName.split("\\.");
// 获取方法中对象的内容
Object object = getValue(params, split[0]);
// 转换为JsonObject
JSONObject jsonObject = (JSONObject) JSONObject.toJSON(object);
// 获取值
Object o = jsonObject.get(split[1]);
return String.valueOf(o);
}
// 简单的动态参数直接返回
return String.valueOf(getValue(params, paramName));
}
// 非动态参数直接返回
return name;
}
/**
* 根据参数名返回对应的值
*
* @param map
* @param paramName
* @return
*/
public Object getValue(Map<String, Object> map, String paramName) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals(paramName)) {
return entry.getValue();
}
}
return null;
}
/**
* 获取方法的参数名和值
*
* @param joinPoint
* @return
*/
public Map<String, Object> getParams(JoinPoint joinPoint) {
Map<String, Object> params = new HashMap<>(8);
Object[] args = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] names = signature.getParameterNames();
for (int i = 0; i < args.length; i++) {
params.put(names[i], args[i]);
}
return params;
}
}
public class TestReq {
private String shopId;
public String getShopId() {
return shopId;
}
public void setShopId(String shopId) {
this.shopId = shopId;
}
}
import com.zm.aop.TestReq;
public interface TestService {
String testService2(TestReq testReq);
}
import com.zm.aop.Power;
import com.zm.aop.TestReq;
import com.zm.aop.service.TestService;
import org.springframework.stereotype.Service;
// 业务层无侵入使用
@Service
public class TestServiceImpl implements TestService {
// 使用对象参数
@Power("#{testReq.shopId}")
@Override
public String testService2(TestReq testReq) {
return "testService2";
}
}
import com.zm.aop.Power;
import com.zm.aop.TestReq;
import com.zm.aop.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.websocket.server.PathParam;
@RestController
public class TestController {
@Autowired
private TestService testService;
// 控制层无侵入使用
// 使用参数
@Power("#{shopId}")
@GetMapping("/test1")
public String test1(@PathParam("shopId")String shopId){
return shopId;
}
@PostMapping("/test2")
public String test2(@RequestBody TestReq testReq){
return testService.testService2(testReq);
}
}