AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类。注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。
(1)JDK动态代理
自定义注解PersonAnnotation
package com.calvin.exercise.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PersonAnnotation {
String language() default "english";
}
定义Aspect
package com.calvin.exercise.aspect;
import com.calvin.exercise.annotation.PersonAnnotation;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PersonAspect {
private Logger logger = LoggerFactory.getLogger(PersonAspect.class);
/**
* 定义切点
*/
@Pointcut("@annotation(com.calvin.exercise.annotation.PersonAnnotation)")
public void pointCut() {
}
/**
* 切点前执行
*/
@Before("pointCut()")
public void before() {
logger.info("before method proceed............");
}
/**
* 切点后执行
*/
@After("pointCut()")
public void after() {
logger.info("after method proceed............");
}
@Around("pointCut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info("around before............");
// 执行完成目标方法
joinPoint.proceed();
logger.info("around after..............");
}
}
定义接口PersonService及接口实现类
package com.calvin.exercise.service;
public interface PersonService {
void say(String name);
}
package com.calvin.exercise.service.impl;
import com.calvin.exercise.annotation.PersonAnnotation;
import com.calvin.exercise.service.PersonService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class PersonServiceImpl implements PersonService {
private Logger logger = LoggerFactory.getLogger(PersonServiceImpl.class);
public PersonServiceImpl() {
super();
logger.info("正在生成一个PersonService实例");
}
/**
* 该注解是用来定义切点
* @param name
*/
@Override
@PersonAnnotation(language = "Chinese")
public void say(String name) {
logger.info("say method : say {}", name);
}
}
测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAopTest {
@Autowired
private PersonService personService;
@Test
public void testJdkAopSay() throws Exception{
personService.say("chinese");
}
}
控制台输出结果:
可以看到,环绕的执行顺序是@Around→@Before→@After→@Around执行ProceedingJoinPoint.proceed() 。因此 ,@Around可以实现@Before和@After的功能,并且只需要在一个方法中就可以实现。
当然,如果我们想获取注解中的值 ,可以使用反射来获取。
将@Around注解的方法改为:
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 方法签名
Signature signature = joinPoint.getSignature();
Method method = ((MethodSignature) signature).getMethod();
// 这个方法才是目标对象上有注解的方法
Method realMethod = joinPoint.getTarget().
getClass().getDeclaredMethod(signature.getName(),
method.getParameterTypes());
// 通过反射获取注解
PersonAnnotation annotation = realMethod.getDeclaredAnnotation(PersonAnnotation.class);
String language = annotation.language();
logger.info("language:{}", language);
// 执行完成目标方法
return joinPoint.proceed();
}
再次测试,查看控制台 输出,发现可以获取到注解的值。
然而通过Debug我们发现动态代理生成的对象是CGLIB动态代理生成的。
而我们希望的是对象通过jdk动态代理生成 。原因其实是笔者基于Spring Boot2.0开发 ,而在 Spring Boot 2.0 中,Spring Boot现在默认使用CGLIB动态代理(基于类的动态代理), 包括AOP。 如果需要基于接口的动态代理(JDK基于接口的动态代理) ,只需要在application.properties设置spring.aop.proxy-target-class属性为false即可。
配置文件添加该属性并重启后,再次Debug发现,对象已经是基于JDK动态代理生成。
(2)CGLIB动态代理
创建一个不实现接口的类WorkerService
package com.calvin.exercise.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WorkerService {
private Logger logger = LoggerFactory.getLogger(WorkerService.class);
public WorkerService() {
super();
logger.info("正在生成一个WorkerService实例");
}
public void say(String name) {
logger.info("say method : say {}", name);
}
}
创建动态代理类CglibProxy
代理类实现方法拦截器,我们可以在重写的intercept()方法中织入执行内容,类似于切面。
package com.calvin.exercise.cjlib;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
/**
* @Title CglibProxy
* @Description Cglib动态代理
* @author calvin
* @date: 2020/2/23 6:17 PM
*/
public class CglibProxy implements MethodInterceptor {
private Logger logger = LoggerFactory.getLogger(CglibProxy.class);
private Enhancer enhancer = new Enhancer();
/**
* 产生一个代理对象
* @param clazz 目标对象
* @return
*/
public Object createProxyInstance(Class clazz) {
// 利用enhancer动态生成class对象的实例
// 设置目标对象为父类
enhancer.setSuperclass(clazz);
// 设置回调,在这里指本身,MethodInterceptor 实现了callback接口,如果回调会调用intercept方法
enhancer.setCallback(this);
// 使用字节码技术动态创建子类的实例
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
logger.info("say before");
Object result = methodProxy.invokeSuper(o, objects);
logger.info("say after");
return result;
}
}
测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAopCjLibTest {
@Test
public void testCjLibAopSay() {
CglibProxy proxy = new CglibProxy();
WorkerService workerService = (WorkerService) proxy.createProxyInstance(WorkerService.class);
workerService.say("english");
}
}
控制台输出结果:
同样的,我们通过Debug也来看看,当前对象是否通过CGLIB动态代理生成。
可以看到,此时生成的对象是基于CGLIB动态代理生成。