Spring AOP与动态代理

Noella ·
更新时间:2024-11-13
· 990 次阅读

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做动态代理的

SpringAOPProcess

使用 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动态代理生成。


作者:电商技术进阶



spring 代理 动态代理 aop 动态

需要 登录 后方可回复, 如果你还没有账号请 注册新账号