持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情
简介
- 代理是常见的设计模式,简单的说他就是控制对一个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理 。 spring中的aop特性就是通过代理类实现的。
静态代理
-
所谓的静态代理就是说在编译前就产生代理类。实际上就是我们手动提供一个实现类。实现类中转发到真实对象中。下面我们简单实现下静态代理
-
逻辑分析: 我们实现逻辑很简单。真实实现类有两个方法一个获取用户名,一个注册用户。因为这里是为了模拟所以直接输出注册用户的日志了。我们的代理实现类构造需要一个真实的实现类。方便代理他所以需要传一个真实的实现类。然后在转发到真实方法前或者后我们自定义了一些自己的行为。getUserName中我们打印了一句获取用户名的日志。registerUser直接拦截了。不进行转发。
-
Test类
public static void main(String[] args) {
UserInterface userInterface = new UserInterfaceProxy(new UserInterfaceImpl());
userInterface.getUserName();
userInterface.registerUser(null);
}
- 日志
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中登录拦截到未登录并不是抛出异常而是进行自动登录。
总结
\