根据动态代理手写一个AOP
目录
一、定义切面接口
二、定义ProxyFactory
JDKProxyFactory
CglibProxyFactory
三、ServiceLoader兼顾可插拔性
四、默认使用Cglib创建代理对象
五、代理核心实现
六、ProxyUtil
七、自定义切面类
SpringAop是面向切面编程的实现手段,也是动态代理的表现,我们可以借助SpringAOP在应用里来实现一个切面,可管理应用的日志、安全和事务等功能,只需要用@Aspect 注解标记一个类为切面,用@Pointcut注解定义一组service为一个切点, 然后定义方法的通知类型即可完成一个Aop的定义。
抛开SpringAOP, 使用JDK 动态代理和Cglib 动态代理如何实现对象的动态代理,对外只需要提供目标对象和切面类,实现对目标对象的环绕通知和异常处理。
功能列表
1. 统一切面的通知模式,模式包含前置通知、后置通知和异常处理,SpringAop环绕通知也就是前置通知和后置通知的结合。
2. 能定制切面,可以对切面进行二次开发,如一组功能类似的类使用同一个切面。
3. 默认采用Cglib 生成代理对象,因为JDK动态代理需要目标对象实现额外的接口。
4. 只要提供目标对象和自定义的切面类即可生成代理对象,实现的目标如下:
FixPhone fixPhone = ProxyUtil.Proxy(new FixPhone(), MyAspect.class);
一、定义切面接口
切面接口只需要包含3个方法before、after和throwException, 主要包含3个参数分别是目标类对象的实例、目标方法对象和目标方法的参数。
package com.bing.sh.aop.aspect;
import java.lang.reflect.Method;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/18 0018 23:12
*/
public interface Aspect {
/**
* 目标方法执行前
* @param target
* @param method
* @param args
* @return
*/
boolean before(Object target, Method method, Object[] args);
/**
* 目标方法执行后
* @param target
* @param method
* @param args
* @param returnValue
* @return
*/
boolean after(Object target, Method method, Object[] args, Object returnValue);
/**
* 目标方法调用异常
* @param target
* @param method
* @param args
* @param throwable
* @return
*/
boolean throwException(Object target, Method method, Object[] args, Throwable throwable);
}
下面使用工厂模式去生产2种代理实现,分别是JDKProxyFactory和CglibProxyFactory。
二、定义ProxyFactory
定义抽象类ProxyFactory, 由JDKProxyFactory和CglibProxyFactory实现。
public abstract class ProxyFactory implements Serializable {
private static final long serialVersionUID = 122319479213L;
/**
* 执行代理
*/
public abstract <T> T proxy(T target, Aspect aspect);
}
JDKProxyFactory
我们可以直接使用java.lang.reflect包下的Proxy对象调用newProxyInstance创建代理对象,参数Class<?>[] interfaces为目标对象实现的接口列表,InvocationHandler 接口需要我们自己去实现。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
package com.bing.sh.aop.proxy;
import com.bing.sh.aop.aspect.Aspect;
import com.bing.sh.aop.interceptor.JDKInterceptor;
import java.lang.reflect.Proxy;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/18 0018 23:14
*/
public class JDKProxyFactory extends ProxyFactory {
@Override
public <T> T proxy(T target, Aspect aspect) {
JDKInterceptor interceptor = new JDKInterceptor(target, aspect);
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), interceptor);
}
}
CglibProxyFactory
我们在使用Cglilb生成代理对象时,需要添加cglib的dependency,借助Enhancer创建代理对象 。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
package com.bing.sh.aop.proxy;
import com.bing.sh.aop.aspect.Aspect;
import com.bing.sh.aop.interceptor.CglibInterceptor;
import net.sf.cglib.proxy.Enhancer;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/18 0018 23:14
*/
public class CglibProxyFactory extends ProxyFactory {
@Override
public <T> T proxy(T target, Aspect aspect) {
final Enhancer enhancer = new Enhancer();
// super class 为目标对象的class。
enhancer.setSuperclass(target.getClass());
//设置callback, 就是MethodInterceptor的接口的实现类
enhancer.setCallback(new CglibInterceptor(target, aspect));
return (T) enhancer.create();
}
}
三、ServiceLoader兼顾可插拔性
为了提高应用的可插拔性,JDK提供了一个SPI给我们开发者,我们可以使用JDK里的ServerLoader类去加载META-INF/services目录下文件名定义的类实现。
这也是JDK对开发者提供的一种SPI机制,当我们对一个接口或抽象类有多个实现时,我们在选择实现类的时候,只需要从多个实现中选择一个即可,如果没有该配置,我们也可以使用一个默认的实现,配置的方式全限定名的方式,如下:
一般实现只配置一个就行,比如Spring框架里的spring-web模块,实现了servlet提供的初始化接口javax.servlet.ServletContainerInitializer,实现类为:org.springframework.web.SpringServletContainerInitializer。
我在项目里的com.bing.sh.aop.proxy.ProxyFactory 抽象类有2个实现,分别是CglibProxyFactory和JDKProxyFactory, 那么我们可以直接将CglibProxyFactory和JDKProxyFactory的全限定名配置到该文件里。
ServiceLoader类提供了一个静态方法load 去加载META-INF/services目录下的clazz和对应的实现:
Params:
service – The interface or abstract class representing the service
loader – The class loader to be used to load provider-configuration files and provider classes, or null if the system class loader (or, failing that, the bootstrap class loader) is to be used
Type parameters:
<S> – the class of the service type
Returns:
A new service loader
Throws:
ServiceConfigurationError – if the service type is not accessible to the caller or the caller is in an explicit module and its module descriptor does not declare that it uses service
API Note:
If the class path of the class loader includes remote network URLs then those URLs may be dereferenced in the process of searching for provider-configuration files.
This activity is normal, although it may cause puzzling entries to be created in web-server logs. If a web server is not configured correctly, however, then this activity may cause the provider-loading algorithm to fail spuriously.
A web server should return an HTTP 404 (Not Found) response when a requested resource does not exist. Sometimes, however, web servers are erroneously configured to return an HTTP 200 (OK) response along with a helpful HTML error page in such cases. This will cause a ServiceConfigurationError to be thrown when this class attempts to parse the HTML page as a provider-configuration file. The best solution to this problem is to fix the misconfigured web server to return the correct response code (HTTP 404) along with the HTML error page.
@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader){
return new ServiceLoader<>(Reflection.getCallerClass(), service, loader);
}
我们只需要传入class,使用Iterator去遍历就能拿到service所有的实现实例。
package com.bing.sh.utils;
import com.bing.sh.aop.proxy.ProxyFactory;
import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.function.Supplier;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/19 0019 14:26
*/
public class ServiceLoaderUtil {
public static <T> T loadService(Class<T> clazz, Supplier<? extends T> supplier) {
final Iterator<T> iterator = load(clazz).iterator();
while (iterator.hasNext()) {
try {
return iterator.next();
} catch (ServiceConfigurationError e) {
// ignore
}
}
// 如果没有那么默认注入一个
return supplier.get();
}
public static <T> ServiceLoader<T> load(Class<T> clazz) {
return load(clazz, null);
}
public static <T> ServiceLoader<T> load(Class<T> clazz, ClassLoader classLoader) {
return ServiceLoader.load(clazz, ObjectUtil.defaultIfNull(classLoader, ClassLoaderUtil::getClassLoader));
}
}
四、默认使用Cglib创建代理对象
Cglib动态代理不需要目标对象实现接口即可创建代理对象,相比于JDK动态代理稍微更简洁些。
如果没有配置services,那么使用CglibProxcFactory:
private static ProxyFactory createFactory() {
return ServiceLoaderUtil.loadService(ProxyFactory.class, () -> {
try {
return ClassUtil.loadClass(CglibProxyFactory.class);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
return null;
});
}
五、代理核心实现
我们只需要在Interceptor类里实现代理方法的befor、after、throwException的调用即可。
package com.bing.sh.aop.interceptor;
import com.bing.sh.aop.aspect.Aspect;
import com.bing.sh.utils.ClassUtil;
import com.bing.sh.utils.ReflectUtils;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @Desc: 代理的对象不需要实现额外的接口即可生成一个代理对象,也是默认的代理方式
* @Author: bingbing
* @Date: 2022/4/18 0018 23:32
*/
public class CglibInterceptor implements MethodInterceptor, Serializable {
private static final long serialVersionUID = 1L;
private final Object target;
private final Aspect aspect;
public CglibInterceptor(Object target, Aspect aspect) {
this.target = target;
this.aspect = aspect;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
final Object target = this.target;
Object result = null;
if (aspect.before(target, method, args)) {
try {
ReflectUtils.setAccessible(method);
result = method.invoke(ClassUtil.isStatic(method) ? null : target, args);
} catch (InvocationTargetException e) {
// 捕获业务代码导致的异常
if (aspect.throwException(target, method, args, e.getTargetException())) {
throw e;
}
}
}
if (aspect.after(target, method, args, result)) {
return result;
}
return null;
}
}
六、ProxyUtil
使用Class<? extends Aspect>表示参数的上限类型为Aspect, 可传入Aspect接口的子类。
package com.bing.sh.aop.proxy;
import com.bing.sh.aop.aspect.Aspect;
import com.bing.sh.aop.interceptor.JDKInterceptor;
import java.lang.reflect.Proxy;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/19 0019 19:48
*/
public class ProxyUtil {
public static <T> T Proxy(T target, Class<? extends Aspect> aspect) {
return ProxyFactory.createProxy(target, aspect);
}
public static <T> T newProxyInstance(ClassLoader classLoader, JDKInterceptor interceptor, Class<?>[] interfaces) {
return (T) Proxy.newProxyInstance(classLoader, interfaces, interceptor);
}
}
七、自定义切面类
实现Aspect接口,所有方法均返回true。
package com.bing.sh.aop.aspect;
import java.lang.reflect.Method;
/**
* @Desc: 继承此类并重新该方法就能创建某个类的代理对象
* @Author: bingbing
* @Date: 2022/4/19 0019 10:59
*/
public class SimpleAspect implements Aspect {
@Override
public boolean before(Object target, Method method, Object[] args) {
return true;
}
@Override
public boolean after(Object target, Method method, Object[] args, Object returnValue) {
return true;
}
@Override
public boolean throwException(Object target, Method method, Object[] args, Throwable throwable) {
return true;
}
}
我们只需要继承SimpleAspect类就能创建一个切面类:
public class MyAspect extends SimpleAspect {
@Override
public boolean before(Object target, Method method, Object[] args) {
System.out.println("先开机...");
return true;
}
@Override
public boolean after(Object target, Method method, Object[] args, Object returnValue) {
System.out.println("挂掉...");
return true;
}
}
测试:
package com.bing.sh.aop;
import com.bing.sh.aop.aspect.SimpleAspect;
import com.bing.sh.aop.proxy.ProxyFactory;
import com.bing.sh.aop.proxy.ProxyUtil;
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Method;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/19 0019 9:41
*/
public class AopTests {
// 如果是内部类那么必须为静态内部类//java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
static class FixPhone {
public void call() {
System.out.println("fixphone打电话...");
}
public void sendMessage() {
System.out.println("发送短信...");
}
}
static class MobilePhone implements Phone {
public MobilePhone() {
//java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
}
@Override
public void call() {
System.out.println("mobile打电话...");
}
@Override
public void sendMessage() {
System.out.println("mobile发送短信...");
}
}
interface Phone {
void call();
void sendMessage();
}
public class MyAspect extends SimpleAspect {
@Override
public boolean before(Object target, Method method, Object[] args) {
System.out.println("先开机...");
return true;
}
@Override
public boolean after(Object target, Method method, Object[] args, Object returnValue) {
System.out.println("挂掉...");
return true;
}
}
public class AnimalAspect extends SimpleAspect {
@Override
public boolean before(Object target, Method method, Object[] args) {
System.out.println("先起床...");
return true;
}
@Override
public boolean after(Object target, Method method, Object[] args, Object returnValue) {
System.out.println("后睡觉...");
return true;
}
}
@Test
public void testAopByAutoCgLib() {
FixPhone fixPhone = ProxyUtil.Proxy(new FixPhone(), MyAspect.class);
fixPhone.call();
MobilePhone mobilePhone = ProxyUtil.Proxy(new MobilePhone(), MyAspect.class);
mobilePhone.call();
Dog dog = ProxyUtil.Proxy(new Dog(), AnimalAspect.class);
dog.eat();
}
}
Hutool参考文档
static326: 初始化报错的话 2024-05-21T03:02:21.983203Z 0 [ERROR] --initialize specified but the data directory has files in it. Aborting. 2024-05-21T03:02:21.984368Z 0 [ERROR] Aborting 把my.ini文件放到bin目录下
m0_73554618: 大佬您好,这个解决的方法比较被动,有没有能够及时处理并解决这个问题的方法呢?
令人头秃的bug: 这个能扫码看视频或者文档吗?
七月听雪: 加了啊 你的代码不是有吗
Chaos_Taiji: 大佬,我也发生了你说的这种情况,但是我看了这个INNODB_TRX 表,我的这个表是空的。求教怎么解决?