首先看一个例子,代码如下:
public class Main {
public static void main(String[] args){
UserService userService = new UserService();
userService.saveUser();
}
}
public class UserService {
public void saveUser(){
System.out.println("saving user...");
}
}
假如我们需要在saveUser前后加一些额外操作,比如记录相关日志,最直接的办法,在业务操作之前先记录下日志,业务操作后再记录下日志:
public class UserService {
public void saveUser(){
logBefore();
System.out.println("saving user...");
logAfter();
}
private void logBefore(){
System.out.println("before saving user.....");
}
private void logAfter(){
System.out.println("before saving user.....");
}
}
功能倒是实现了,但是仔细一想,上面的代码有点问题,logBefore和logAfter跟业务逻辑(saveUser)没有必然的关系,换句话说,saveUser这个业务操作根本不应该再去关心记录日志的事情,职责变得不明确了,日志记录侵入到业务逻辑了!还有就是before和after针对所有类似UserService的类都具有一定的共性,可以抽取出来。那有没有办法,我只需要关注业务逻辑,日志记录这件事交由其他工具自动完成?这时候,动态代理就派上用场了,使用动态代理,可以动态生成saveUser的增强方法,解决日志记录的问题。下面我们一步步实现这个功能:
按照上面的代码,我们首先需要定义一个接口,接口中主要是before和after方法:
public interface AopMethod {
public void before();
public void after();
}
接下来,因为需要使用代理(这里使用jdk提供的动态代理Proxy),我们看下创建代理对象需要哪些参数:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
第一个参数是classloader,这个可以通过UserService类的classloader获取
第二个参数是类实现的接口,这说明使用jdk动态代理的类一定要实现了接口
第三个是InvocationHandler,反射调用方法就是在这个Handler中实现的
基于以上几个参数,首先要定义接口,供UserService实现:
public interface IUserService {
public void saveUser();
}
接着需要定义InvocationHandler的实现类,实现方法调用:
public class AopHandler implements InvocationHandler {
private AopMethod aopMethod;
private Object needProxyObject;
public AopHandler(AopMethod method,Object object){
this.aopMethod = method;
this.needProxyObject = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("saveUser"))
aopMethod.before();
Object object = method.invoke(needProxyObject,args);
if(method.getName().equals("saveUser"))
aopMethod.after();
return object;
}
}
其中,通过构造函数将aopmethod和UserService实例传进来,在调用实际方法的前后执行aopmethod的before和after方法。最后,我们再提供一个获取bean对象的方法类,然后再重新编写测试程序:
public class AopFactory {
public static Object getBean(Object object,AopMethod method){
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),new AopHandler(method,object));
}
}
public class Main {
public static void main(String[] args){
UserService userService = new UserService();
IUserService user = (IUserService) AopFactory.getBean(userService, new AopMethod() {
@Override
public void before() {
System.out.println("before.....");
}
@Override
public void after() {
System.out.println("after.....");
}
});
user.saveUser();
}
}
上面的代码实现了aop的功能,但是还有几个地方值得改进,进一步提高开发效率:
1,获取代理类时,需要将AopMethod传进去,对于程序员来说还要去实现AopMethod接口,有点不人性化
2,反射调用时需要判断是否为saveUser方法,这种硬编码的方式不好
下面结合之前的文章"我所理解的spring ioc",对上面的代码进行改造,首先定义两个注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Before {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface After {
}
接着改造下UserService,在saveUser上打上上面的两个注解
@MyService
public class UserService implements IUserService{
@After
@Before
@Override
public void saveUser(){
System.out.println("saving user...");
}
}
然后定义一个接口,用于处理bean实例化之后的操作:
public interface BeanPostProcessor {
public Object processBeanAfterInstance(Object bean);
}
再定义一个BeanPostProcessor的实现类,用于处理Before和After两个标签,生成代理类:
@MyService
public class AopBeanPostProcessor implements BeanPostProcessor {
@Override
public Object processBeanAfterInstance(Object bean) {
Set<Method> methodList = new HashSet<Method>();
Class cls = bean.getClass();
Class[] interfaces = cls.getInterfaces();
if(null != interfaces){//jdk动态代理一定是实现了接口的
for(Class inter: interfaces){
Method[] methods = inter.getDeclaredMethods();
for(Method method : methods){
try {
Method m = cls.getDeclaredMethod(method.getName());
if(m.isAnnotationPresent(Before.class)){
methodList.add(m);
}
if(m.isAnnotationPresent(After.class)){
methodList.add(m);
}
//others
} catch (NoSuchMethodException e) {
//ignore
}
}
}
}
if(!methodList.isEmpty()){
return createProxy(bean,methodList);
}
return bean;
}
private Object createProxy(Object bean, final Set<Method> methodList) {
class AopMethodHandler implements InvocationHandler {
private Object o;
public AopMethodHandler(Object obj){
this.o = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (null != getMethod(methodList,method,Before.class)) {
//处理before
System.out.println("before.......");
}
Object object = method.invoke(o,args);
if (null != getMethod(methodList,method,After.class)) {
//处理after
System.out.println("after.......");
}
return object;
}
private Object getMethod(Set<Method> methodList, Method method, Class cls) {
for(Method m :methodList){
if(m.getName().equals(method.getName())){
if(m.isAnnotationPresent(cls)){
return m;
}
}
}
return null;
}
}
return Proxy.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(),new AopMethodHandler(bean));
}
}
接着,修改ClassPathXmlApplicationContext,在实例化bean后,添加后处理逻辑:
/**
* beanPostProcessor
*/
for(String key : beanMap.keySet()){
Object object = beanMap.get(key);
for(BeanPostProcessor beanPostProcessor : beanPostProcessors){
object = beanPostProcessor.processBeanAfterInstance(object);
}
beanMap.put(key,object);
}
最后,编写测试程序:
public class Main {
public static void main(String[] args) throws Exception{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserService userService = (IUserService) context.getBean("userService");
userService.saveUser();
}
}
运行程序得到跟预期的结果一致。上面从一个简单的例子出发,对出现的问题进行思考,一步步实现了一个自己的aop,后续可以基于此进行扩展和改造,丰富下aop的功能。
最后,我们看下AOP的概念:AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。在本文中,saveUser就是所谓的一个切面(准确说应该是切点),通过运行期动态代理,将saveUser和日志记录进行了隔离,彼此只需关注自己的业务逻辑,降低了耦合度!
以上代码均已上传:https://github.com/reverence/myaop