spring的AOP是如何实现的
什么是AOP
面向切面编程,能够让我们在不影响系统原有功能的前提下,增加横向扩展。比如增加日志、鉴权、迁移时接口转发等。
AOP的实现
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>3.0.4</version>
</dependency>
spring配置
package edu.wyn.spring;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy
@Configuration
@ComponentScan(value = "edu.wyn.spring")
public class AppConfig {
}
自定义切面注解
package edu.wyn.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//指定在方法上
@Target(ElementType.METHOD)
//运行时
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
自定义切面类
package edu.wyn.spring;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class MyLogAspect {
@Pointcut("@annotation(edu.wyn.spring.MyLog)")
public void doLog(){}
@Before("doLog()")
public void before(JoinPoint joinPoint) {
System.out.println("MyLogAspect before:" + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(value = "doLog()", returning = "returnObj")
public void doReturn(JoinPoint joinPoint, Object returnObj) {
System.out.println("MyLogAspect return:" + returnObj.toString());
}
}
使用的bean
package edu.wyn.spring;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public String getOrderId(String name) {
return name+System.currentTimeMillis();
}
}
package edu.wyn.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private OrderService orderService;
@MyLog
public String getOrderInfo(String userName) {
return orderService.getOrderId(userName);
}
}
测试类
package edu.wyn.spring;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService)context.getBean("userService");
userService.getOrderInfo("test");
}
}
测试结果
image.pngAOP的原理
image.png由上图可见,我们获取到的UserService对象其实是通过CGLIB生成的代理对象,也就是AOP是通过代理来实现的,而AOP在何时生成代理对象可见spring的三级缓存
如何实现代理
代理的优势
通过代理可以处理被代理对象的非核心业务,让被代理对象专注自己的业务处理,代理模式可以在不影响被代理对象的前提下增强被代理对象的能力。
静态代理
代理类和目标类都实现同一个接口,并且代理类中包含目标类的对象,代理类在真正执行对应方法的时候调用了目标类的方法。
问题:每一个目标类都需要实现一个代理类,如果目标类过多则会出现代理类的冗余
动态代理
cglib
目标类、代理类的处理类需要编写
引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
目标类实现
package edu.wyn.proxy.cglibproxy;
public class Cat {
public String getName(int no) {
return String.valueOf(no);
}
}
代理类的处理类
package edu.wyn.proxy.cglibproxy;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CatProxy implements MethodInterceptor {
private Object target;
public CatProxy(Object obj) {
this.target = obj;
}
@Override
public Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
System.out.println("代理方法执行前:" + method.getName() + ",参数:" + String.valueOf(params));
//调用目标类
//Object result = methodProxy.invoke(o, params);
//调用子类
Object result = methodProxy.invokeSuper(o, params);
System.out.println("代理方法执行后:" + method.getName() + ",参数:" + String.valueOf(params));
return result;
}
}
测试代码
package edu.wyn.proxy.cglibproxy;
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyDemo{
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//设置需要代理的目标类
enhancer.setSuperclass(Cat.class);
//拦截对象,回调的实现类
enhancer.setCallback(new CatProxy(new Cat()));
Cat cat = (Cat) enhancer.create();
System.out.println(cat.getName(12));
}
}
总结
1、通过ASM第三方框架生成3个代理类,其中两个是为了能快速定位到具体哪个方法,一个是invokesuper使用,一个是invoke使用
2、无需实现接口,使用的是继承方式,因此无法代理final修饰的类和方法
3、调用目标类是子类调用父类的形式
4、通过invokesuper调用目标类时,在目标类调用本类方法会再一次代理
jdk
接口、目标类、代理类的处理类需要开发编写,代理类则动态生成
接口实现
package edu.wyn.proxy.jdkproxy;
public interface IAnimal {
String getAge(String name);
}
目标类实现
package edu.wyn.proxy.jdkproxy;
public class UserInfo implements IAnimal {
@Override
public String getAge(String name) {
return "25";
}
}
代理类的处理类
package edu.wyn.proxy.jdkproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class AnimalProxy implements InvocationHandler {
private Object obj;
public AnimalProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理方法执行前:" + method.getName() + ",参数:" + String.valueOf(args));
Object result = method.invoke(obj, args);
System.out.println("代理方法执行后:" + method.getName() + ",参数:" + String.valueOf(args));
return result;
}
}
测试代码
package edu.wyn.proxy.jdkproxy;
import java.lang.reflect.Proxy;
public class JdkProxyDemo {
public static void main(String[] args) {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
System.out.println("start...");
IAnimal proxy = (IAnimal)Proxy.newProxyInstance(
IAnimal.class.getClassLoader(),
new Class[]{IAnimal.class},
new AnimalProxy(new UserInfo()));
proxy.getAge("abs");
}
}
总结
1、动态生成一个代理类
2、通过实现接口生成代理类,因此目标类必须实现接口
3、调用目标类的时候通过反射调用,所以也是jdk的反射找到了具体哪个方法(1.8jdk优化后效率提升了)
4、目标类调用本类方法只会代理一次
为什么jdk一定要用接口
代理类是实现了接口的方式来进行对象代理,之后通过反射调用目标类,如果不实现对应的接口则无法进行透明调用
为什么@Transactional不能在同一个类中调用被代理的方法
通过jdk进行动态代理的时候,在类中调用本类其他方法时调用的其实是原始类的方法,而不是代理类的方法,而原始方法没有包含事务处理,事务自然就失效了。
通过cglib进行动态代理的时候,如果用invokeSuper因为cglib在实现代理类的时候是继承了父类,调用代理类方法的时候子类进行增强然后super调用父类,因此子类调用子类自己的方法时也能进行增强,进而可以代理
网友评论