[#0x0042] Spring AOP学习(二):动态代理

本文介绍如何使用SpringAOP为多个类统一添加日志功能,通过动态代理方式减轻代码改动负担。展示了如何实现自定义拦截器及利用配置文件进行方法级别的拦截。

  如果系统中有500个类,每个类都要添加日志功能,此时无论是直接修改源代码、继承还是组合,都是十分巨大的工作量。此时AOP可以帮我们解决这个问题。

 

  现在假设记录日志的功能已经单独提出来了,由LogInterceptor来完成:

package com.bjsxt.aop;

public class LogInterceptor {
	public void beforeMethod() {
		System.out.println("logging...");
	}
}

有了Spring AOP,我们就可以用配置文件来说明:“在某个类的每一个方法执行之前,都给我调用一次beforeMethod()方法”(更复杂一点的做法是给beforeMethod()方法添加一个Method参数,这样可以配置可以具体到某个类的某个方法上),如:

<beans>
	<bean id="u" class="com.bjsxt.dao.impl.UserDAOImpl" >
		<!-- 非标准写法,仅作演示 -->
		<Log class="com.bjsxt.aop.LogInterceptor" logMethod="beforeMethod" targetMethod="all" />
	</bean>
</beans>

这样,在配置文件中给500个bean都加上这么一段,就能给这500个类都加上日志功能了。

 

  那么Spring AOP是如何实现的呢?用的是动态代理的方法。

  参考[#0x0022],我们可以把LogInterceptor实现成一个InvocationHandler

package com.bjsxt.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class LogInterceptor implements InvocationHandler {
	private Object target;
	
	public Object getTarget() {
		return target;
	}

	public void setTarget(Object target) {
		this.target = target;
	}

	public void beforeMethod(Method m) {
		System.out.println(m.getName() + ": logging...");
	}

	public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
		beforeMethod(m);
		m.invoke(target, args);
		return null;
	}
}

调用的代码如下: 

public class AOPTest
{
	public static void main(String[] args)
	{
		UserDAO userDAO = new UserDAOImpl();
		LogInterceptor li = new LogInterceptor();
		li.setTarget(userDAO);
		
		UserDAO userDAOProxy = (UserDAO)Proxy.newProxyInstance(userDAO.getClass().getClassLoader(), userDAO.getClass().getInterfaces(), li);
		
		userDAOProxy.save(new User());
	}
}

 

  这里要特殊说明的是:

  (1) userDAOProxy对象是UserDAOImpl类的动态代理,userDAOProxy是代理对象,UserDAOImpl对象是被代理对象

  (2) 若想用Java API来给某个类(如UserDAOImpl)来创建动态代理,那么这个类必须实现一个接口(如UserDAO),没有实现接口的类用Java API是无法创建动态代理的(但cglib可以)。所以在Proxy.newProxyInstance()方法中,需要把被代理类的接口,即userDAO.getClass().getInterfaces()传递进去。

  (3) 代理对象和被代理对象应该在同一个classLoader中,如果在不同的classLoader中,它们就无法互相访问,所以userDAO.getClass().getClassLoader()也作为参数传递给了Proxy.newProxyInstance()。代理对象和被代理对象需要互相访问的原因在(4)

  (4) 我们得到的userDAOProxy对象,其类型应该是一个组合了LogInterceptor的类(li作为参数被传递给了Proxy.newProxyInstance()),而LogInterceptor又组合了userDAOImpl(li.setTarget(userDAO));加上userDAOProxy又实现了UserDAO接口,所以userDAOProxy看上去应该是这样一个类:

// 非标准写法,仅作演示用
public class UserDAOProxy implements UserDAO {
	private InvocationHandler invocationHandler;

	public UserDAOProxy(InvocationHandler invocationHandler) {
		super();
		this.invocationHandler = invocationHandler;
	}

	public InvocationHandler getInvocationHandler() {
		return invocationHandler;
	}

	public void save(User user) {
		this.invocationHandler.invoke(proxy, UserDAO.class.getDeclaredMethod("save", User.class), user);
	}
}

(同[#0x0022],invoke()方法中的proxy对象还是不知道是干嘛用的……视频中说这个proxy对象就是userDAOProxy自身,待研究)

  这样,Proxy.newProxyInstance(userDAO.getClass().getClassLoader(), userDAO.getClass().getInterfaces(), li)也就相当于new UserDAOProxy(li)。userDAOProxy的save()方法的实际过程是:

Spring AOP为系统添加逻辑(业务逻辑,如上文的日志)的动态代理方法大抵就是这样。

  如果UserDAO还有个delete()方法,那么userDAOProxy.delete()也会去调用li.invoke(proxy, delete, user)。这就体现了InvocationHandler是“对被代理对象的任意方法的invocation都handle”这么一个概念。

  当然,更好的实现是把beforeMethod这部分逻辑提出来,让LogInterceptor这个类侧重“Interceptor”而不包含"Log"的逻辑,beforeMethod完全可以用一个Logger类来实现

学生社团系统-学生社团“一站式”运营管理平台-学生社团管理系统-基于SSM的学生社团管理系统-springboot学生社团管理系统.zip-Java学生社团管理系统开发实战-源码 更多学生社团系统: SpringBoot+Vue学生社团“一站式”运营管理平台源码(活动管理+成员考核+经费审批) Java学生社团管理系统开发实战:SSM升级SpringBoot(招新报名+场地预约+数据看板) 基于SpringSecurity的社团管理APP(移动端签到+权限分级+消息推送) 企业级社团数字化平台解决方案(SpringBoot+Redis缓存+Elasticsearch活动搜索) 微信小程序社团服务系统开发(活动直播+社团文化墙+成员互动社区) SpringBoot社团核心源码(多角色支持+工作流引擎+API接口开放) AI赋能社团管理:智能匹配兴趣标签+活动热度预测+成员贡献度分析(附代码) 响应式社团管理平台开发(PC/移动端适配+暗黑模式+无障碍访问) 完整学生社团系统源码下载(SpringBoot3+Vue3+MySQL8+Docker部署) 高校垂直领域社团平台:百团大战系统+社团星级评定+跨校活动联盟 适用对象:本代码学习资料适用于计算机、电子信息工程、数学等专业正在做毕设的学生,需要项目实战练习的学习者,也适用于课程设计、期末大作业。 技术栈:前端是vue,后端是springboot,项目代码都经过严格调试,代码没有任何bug! 核心管理:社团注册、成员管理、权限分级 活动运营:活动发布、报名签到、场地预约 资源服务:经费申请、物资管理、文档共享 数据分析:成员活跃度、活动效果评估、社团影响力排名
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值