- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
动态代理
- 因为静态代理的麻烦,静态代理类只能为一个接口服务,所以我们的动态代理就产生了。动态代理是通过反射机制进行方法的代理。这大大方便我们代码的编写。下面我们简单通过两种方式实现动态代理。
jdk动态代理
-
jdk动态代理是Java提供的一种方式,最大的特点是保留了静态代理的风格。jdk动态代理只能代理接口。这也是他和cglib的区别,也是他的缺点。jdk动态代理通过Proxy进行创建代理对象的。通过java.lang.reflect.InvocationHandler接口控制方法代理拦截的。
-
接口类
public interface Developer {
/**
* 编码
*/
void code();
/**
* 解决问题
*/
void debug();
}
- 真实类
public class JavaDeveloper implements Developer {
@Override
public void code() {
System.out.println("java code");
}
@Override
public void debug() {
System.out.println("java debug");
}
}
- 代理类
public class JavaDynamicProxy {
public static void main(String[] args) {
JavaDeveloper jDeveloper = new JavaDeveloper();
Developer developer = (Developer) Proxy.newProxyInstance(jDeveloper.getClass().getClassLoader(), jDeveloper.getClass().getInterfaces(), (proxy, method, params) -> {
if (method.getName().equals("code")) {
System.out.println("我是一个特殊的人,code之前先分析问题");
return method.invoke(jDeveloper, params);
}
if (method.getName().equals("debug")) {
System.out.println("我没有bug");
}
return null;
});
developer.code();
developer.debug();
}
}
- 效果
- 解析
- 首先定义一个接口包含code、bug方法。
- 然后我们定义一个实现类是Java程序员的工作,对应的功能是java code 、 java bug
- 然后我们不需要提供代理类,这里通过Proxy进行创建代理对象。但是我们需要一个方法拦截器。这个拦截器在Proxy.newProxyInstance方法中传入。这个方法有三个参数:
- classLoader : 类加载器(通过接口获取)
- interface : 接口
- 拦截器 : 需要我们继承InvocationHandler , 上述代码直接通过lombad表达式书写的。
- 我们可以看到在拦截器里我们就可以控制方法转发前后的节点了。
cglib动态代理
-
有了jdk动态代理我们开发代理就很方便了。这个时候还有一个问题。我们平时开发中不可能每个类都有继承接口的。这个时候我们的普通类如果想实现代理改如何实现呢。我们看看spring中aop的类也是没有实现接口的。这个是如何实现的呢。下面我们用cglib告诉你
-
接口
public class HelloService {
public HelloService() {
System.out.println("HelloService构造");
}
final public String sayHello(String name) {
System.out.println("HelloService:sayOthers>>"+name);
return null;
}
public void sayHello() {
System.out.println("HelloService:sayHello");
}
}
- 拦截器
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(o, objects);
System.out.println("======插入后者通知======");
return object;
}
}
- Test
public static void main(String[] args) {
//代理类class文件存入本地磁盘方便我们反编译查看源代码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/root/code");
//通过CGLIB动态代理获取代理对象过程
Enhancer enhancer = new Enhancer();
//设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
//创建代理对象
HelloService helloService = (HelloService) enhancer.create();
//通过代理对象调用目标方法
helloService.sayHello();
}
- cglib可以代理任何类。这是他的优点。他的创键也很简单。通过Enhancer将类与拦截器进行关联,我们的拦截器是实现
net.sf.cglib.proxy.MethodInterceptor的。在intercept方法中我们可以通过methodProxy.invokeSuper进行转发方法。在转发前我们可以进行节点的控制。- object : 表示增强的对象,具体的实现类
- method : 表示被拦截的方法
- args : 参数
- methodProxy : 父类的方法对象。 这里值得注意的是我们要通过methodProxy.invokeSuper.如果换成method调用将会一直递归拦截造成内存溢出。
使用场景运用
- 上述我们简单介绍了静态代理,两种动态代理的方式。下面我们结合具体案例进行实现代理。
- 我们平时开发有些方法需要进行验证是否有权限调用。最简单的就是需要登录。如果我们不用spring。那么将会在每个方法中进行逻辑判断是否已经登录了。这样会造成代码的冗余。今天我们通过动态代理的方式对类里的方法进行登录拦截。
- 为了方便阅读,在创建代理的地方及类就不展示了。我们值展示两种代理方式的拦截器部分就行了。
jdk动态代理运用
public class BlogHolder extends AbstractBlogHolder {
private static Logger logger = LogManager.getLogger(BlogHolder.class);
public BlogHolder(Object blog) {
super(blog);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Blog blog = getBlog();
BlogLogin annotation = method.getAnnotation(BlogLogin.class);
if (annotation!=null&&annotation.needLogin()==true&&!blog.isLogin()) {
logger.info("当前对象未登录");
throw new LoginException("当前对象未登录");
}
return method.invoke(blog, args);
}
}
- 在invoke方法中我们通过获取方法上是否有BlogLogin注解,和needLogin标识来判断当前方法是否需要登录拦截。如果需要拦截且未登录则我们会抛出异常。否则我们进行方法的转发。在实现上有点类似aop的编写。
Cglib动态代理
public class BlogInterceptor extends AbstractBlogInterceptor {
private static Logger logger = LogManager.getLogger(BlogInterceptor.class);
private ResponseBodyAdvice advice = new ResponseBodyHandler();
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
BlogLogin annotation = method.getAnnotation(BlogLogin.class);
if (annotation!=null&&annotation.needLogin()== true) {
//判断是否登录
judgeLogin(annotation,o);
}else if(annotation==null) {
//判断继承的接口中是否有BlogLogin注解
BlogLogin blogLogin = getBlogLoginByParent(method, methodProxy);
if (blogLogin!=null&&blogLogin.needLogin() == true) {
judgeLogin(blogLogin,o);
}
}
Object o1 = null;
try {
o1 = methodProxy.invokeSuper(o, objects);
} catch (Exception e) {
advice.beforeBodyWrite(method,o,e);
}
advice.beforeBodyWrite(method,o,o1);
return o1;
}
private void judgeLogin(BlogLogin blogLogin , Object o) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, LoginException {
Method isLogin = o.getClass().getMethod("isLogin");
Object invoke = isLogin.invoke(o);
if (!"true".equals(invoke.toString())) {
logger.info("当前对象未登录");
if (!advice.autoLogin(blogLogin,o)) {
throw new LoginException("当前对象未登录");
}
}
}
public BlogLogin getBlogLoginByParent(Method method, MethodProxy methodProxy) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException {
Field field = methodProxy.getClass().getDeclaredField("createInfo");
field.setAccessible(true);
Object createInfo = field.get(methodProxy);
if (createInfo == null) {
return null;
}
Field sourceClassName = createInfo.getClass().getDeclaredField("c1");
sourceClassName.setAccessible(true);
Class sourceClass = (Class) sourceClassName.get(createInfo);
Class[] interfaces = sourceClass.getInterfaces();
for (Class anInterface : interfaces) {
Method interfaceMethod = anInterface.getMethod(method.getName(),method.getParameterTypes());
BlogLogin annotation = interfaceMethod.getAnnotation(BlogLogin.class);
if (annotation != null) {
return annotation;
}
}
return null;
}
}
- 和jdk代理逻辑一样,先根据方法上BlogLogin注解判断是否要进行登录拦截。这里因为代理的是对象本身所以这里除了获取类上方法的BlogLogin注解,还会获取接口上BlogLogin注解,接口的注解起到默认作用。类上的注解会起到覆盖默认的作用。因为项目的需求我在cglib中登录拦截到未登录并不是抛出异常而是进行自动登录。
总结
- spring框架中简直大量使用代理实现的,你可以看看哦