代理模式
给一个对象提供一种代理对象以控制对该对象的访问。代理对象在不改变原对象代码的基础上对原对象的功能进行扩展。
使用代理降低了系统的耦合度,扩展性也会更好,还可以起到保护目标对象的作用。代理模式在 Java 开发中是广泛应用的,特别是在框架中底层原理经常涉及到代理模式(尤其是动态代理)。
静态代理和动态代理,实际使用时还是动态代理使用的比较多。原因就是静态代理需要自行手写代码,维护、修改非常繁琐,会额外引入很多工作量。也不能很好的使用配置完成逻辑的指定,所以使用较少。
- 静态代理维护复杂,一旦接口或父类发生改变,所有相关的类或接口就都得进行维护。
- 动态代理无需手写代理类,也不会存在代码编译的过程。运用在内存中生产代理类的技术在 JVM 的运行区造一个代理对象,只需对需要修改的部分进行编辑。
基于 JDK 和基于 CGLIB,实际使用时两个都会用到。
在 Spring 中,默认情况下它就支持了两种动态代理方式。如果你指定的目标类实现了接口,Spring 就会自动选择 JDK 的动态代理。而如果目标类没有实现接口,则 Spring 会使用 CGLIB。而在 SpringBoot 中,默认使用的是 CGLIB。
我们在开发时,由于基于 JDK 的动态代理要求比较多,更不容易实现,所以很多人习惯于统一配置为使用 CGLIB 进行代理。也就是 CGLIB 更通用。
【静态代理】基于接口实现
让目标对象和代理对象都实现一个共同接口。那么这两个类就有了公共的方法,就可以在代理对象中实现对目标对象功能的扩展。
public class StaticProxyInterfaceDemo {
/**
* 公共接口
*/
public interface Dao {
void save();
}
/**
* 原始对象
*/
static class UserDao implements Dao {
@Override
public void save() {
System.out.println("保存数据");
}
}
/**
* 代理对象
*/
static class UserDaoProxy implements Dao {
private UserDao userDao;
@Override
public void save() {
System.out.println("开启事务");
userDao.save();
System.out.println("提交事务");
}
}
}
【静态代理】基于继承实现
让代理对象继承目标对象,那么代理对象就拥有目标对象的方法,同时又可以对其功能进行扩展。
public class StaticProxyExtendsDemo {
/**
* 原始对象
*/
static class UserDao {
public void save() {
System.out.println("保存数据");
}
}
/**
* 代理对象
*/
static class UserDaoProxy extends UserDao {
@Override
public void save() {
System.out.println("开启事务");
super.save();
System.out.println("提交事务");
}
}
}
【动态代理】基于接口的 JDK 动态代理
JDK 动态代理基于接口实现,要求原始对象必须实现接口。
public class JdkProxyDemo {
public static void main(String[] args) {
// 创建目标对象(如果是 JDK 8 之前的版本,匿名内部类访问外面的变量需要声明 final)
UserService userService = new UserServiceImpl();
Class<? extends UserService> userServiceClass = userService.getClass();
// 使用 JDK 动态代理生成代理对象
UserService proxyObj = (UserService) Proxy.newProxyInstance(userServiceClass.getClassLoader(), userServiceClass.getInterfaces(), (proxy, method, args1) -> {
// 在此处编写前置操作
System.out.println("Before");
Object result = null;
try {
result = method.invoke(userService, args1);
} catch (Exception e) {
// 在此处编写异常操作
System.out.println("Exception");
}
// 在此处编写后置操作
System.out.println("After");
// 可以在此处编写新的返回值并返回
return result;
});
proxyObj.login();
}
}
【动态代理】基于继承的 CGLIB 动态代理
CGLIB 动态代理基于继承原始类实现,不需要原始类实现接口,但是原始类不能是 final。
public class CglibProxyDemo {
public static void main(String[] args) {
// 创建目标对象
UserService userService = new UserServiceImpl();
// 使用 CGlib 动态创建代理对象
UserService proxyObj = (UserService) Enhancer.create(userService.getClass(), (InvocationHandler) (o, method, objects) -> {
// 在此处编写前置操作
System.out.println("Before");
Object result = null;
try {
result = method.invoke(userService, args);
} catch (Exception e) {
// 在此处编写异常操作
System.out.println("Exception");
}
// 在此处编写后置操作
System.out.println("After");
// 可以在此处编写新的返回值并返回
return result;
});
proxyObj.login();
}
}
Spring AOP
Spring 的 AOP(面向切面编程)是 Spring 框架中的一个核心特性,它允许开发者在不修改原有代码的情况下,通过添加额外的逻辑来实现横切关注点(cross-cutting concerns)的功能。
Spring 的 AOP 基于代理模式实现。它通过动态代理或者字节码生成技术,在运行时为目标对象生成一个代理对象,并将切面逻辑织入到代理对象的方法调用中。当应用程序调用代理对象的方法时,切面逻辑会在目标方法执行前、执行后或抛出异常时被触发执行。
在 Spring 中,默认情况下它就支持了两种动态代理方式。如果你指定的目标类实现了接口,Spring 就会自动选择 JDK 的动态代理。而如果目标类没有实现接口,则 Spring 会使用 CGLIB。而在 SpringBoot 中,默认使用的是 CGLIB。
关于 Spring AOP 的详细介绍,可以查看这篇文章:《Spring AOP:解锁切面编程的威力与实践》。
我们已经了解到,Spring 的 AOP 本质上就是动态代理,那么 Spring 是通过什么来应用这个动态代理技术的呢?回想之前学习的内容,我们知道 BeanPostProcesser 可以对创建的 Bean 进行加工,而 Spring 的 AOP 正好就是用 BeanPostProcesser 来实现的,它就是 AnnotationAwareAspectJAutoProxyCreator。
Spring 基于注解的 AOP 编程,我们需要在配置类中标记
@EnableAspectJAutoProxy
注解,在 SpringBoot 中我们不需要手动指定,因为 SpringBoot 底层已经进行了封装。如果我们要强制 Spring 每次都使用 CGLIB,那么需要在这个注解添加属性proxyTargetClass = true
。为什么我们指定了 @EnableAspectJAutoProxy 就可以了?我们知道 AOP 是通过 AnnotationAwareAspectJAutoProxyCreator 这个 BeanPostProcesser 来实现的,那么 @EnableAspectJAutoProxy 底层肯定也是通过 @Import 技术引入了这个类。这样就不需要我们自己引入,屏蔽了 Spring 的 AOP 实现细节,语义化的处理也更加的通俗。
@EnableAspectJAutoProxy 还有一个属性 exposeProxy,默认为 false,此参数控制是否将代理对象放入 ThreadLocal,以方便后续获取,解决代理对象的方法中调用本来其他方法代理失效的问题。
我们需要在代理类中配置:
@EnableAspectJAutoProxy(exposeProxy=true)
,随后在代理的原始方法调用其他方法的时候,通过AopContext.currentProxy()
获取本类的代理对象,然后调用方法。@Configuration @EnableAspectJAutoProxy(exposeProxy = true) @ComponentScan("world.xuewei.aopproxy") public class AopProxyConfig { }
@Service public class UserService implements IService { @Override public void m1() { System.out.println("UserService.m1"); // 注意这里,实现接口默认是 JDK 动态代理,这里获取的代理对象的类型用接口类型接收 IService service = (IService) AopContext.currentProxy(); // 如果是 CGLIB 动态代理,则可以使用原始对象类型接收,同时需要指定 proxyTargetClass = true // UserService service = (UserService) AopContext.currentProxy(); service.m2(); } @Override public void m2() { System.out.println("UserService.m2"); } }
手写 Spring AOP
基于前面的了解,我们手写一个简单的实现原理,其实就是创建一个 BeanPostProcesser。这个 BeanPostProcesser 通常是在初始化完成后去执行的。当然也不一定,也有可能在处理循环引用的时候提前就执行了。
定义接口和原始类
public interface IStudentService {
void test1();
}
@Service
public class StudentServiceImpl implements IStudentService {
@Override
public void test1() {
System.out.println("StudentServiceImpl.test1");
}
}
定义代理 BeanPostProcessor
public class MyProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 基于 CGLIB 创建代理对象
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(this.getClass().getClassLoader());
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback((InvocationHandler) (o, method, objects) -> {
// 在此处编写前置操作
System.out.println("Before");
Object result = null;
try {
result = method.invoke(bean, objects);
} catch (Exception e) {
// 在此处编写异常操作
System.out.println("Exception");
}
// 在此处编写后置操作
System.out.println("After");
// 可以在此处编写新的返回值并返回
return result;
});
return enhancer.create();
}
}
定义 AOP 开启注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyProxyBeanPostProcessor.class)
public @interface EnableMyProxy {
}
定义配置类
@EnableMyProxy
@Configuration
@ComponentScan("world.xuewei.proxy")
public class ProxyAppConfig {
}
定义测试类
public class ProxyTest {
private ApplicationContext context;
@Before
public void before() {
// 指定配置类,创建 Spring 工厂
context = new AnnotationConfigApplicationContext(ProxyAppConfig.class);
}
/**
* 测试 AOP
*/
@Test
public void test1() {
StudentServiceImpl service = context.getBean("studentServiceImpl", StudentServiceImpl.class);
service.test1();
}
}
[外链图片转存中…(img-hLfwxitB-1721658404553)]