面向切面编程(Aspect Oriented Programming,AOP)是软件编程思想发展到一定阶段的产物,是对面向对象编程(Object Oriented Programming,OOP)的有益补充, 目前已成为一种比较成熟的编程方式。AOP适用于具有横向逻辑的场所,如访问控制、事务管理、性能监测等。
在传统的业务处理代码中,通常都会进行事务处理、日志记录等操作。虽然OOP可以通过组合或者继承的方式来达到代码的重用,但是比如实现日志记录时,代码还是会分散到不同的方法中。这样就会存在一个问题,如果想要关闭某个功能或者修改时,就必须要修改所有的相关方法。这不单单增加了开发人员的工作量,而且提高了代码的出错率。
为了解决这个问题,AOP思想随之产生。AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,是传统的OOP思想无法办到的,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,却不是OOP的替代品,它只是OOP的延申和补充。
AOP的使用让开发人员在编写业务逻辑时可以专心于核心业务,而不用过多地关注于其他业务逻辑的实现,这不但提高了开发效率,而且增强了代码的可维护性。
在AOP思想中,类于切面的关系如下图所示。我们可以看出,通过Aspect(切面)分别在Class1和Class2的方法中加入了事务、日志、权限和异常等功能。
通过学习我们知道了AOP中的代理就是由AOP框架动态生成的一个对象,该对象可以作为目标对象使用,对于面向切面编程,简单地说,就是在不改变原程序的基础上为代码段增加新的功能,对代码段进行增强处理。它的设计思想来源于代理设计模式,通常情况下调用对象的方法如下图。
在代理模式中可以为该对象设置一个代理对象,代理对象为function()提供一个代理方法,当通过代理对象的function()方法调用原对象的function()方法时,就可以在代理方法中添加新的功能,即增强处理。增强的功能既可以插到原对象的function()前面,也可以插到其后面(如虚线)
JDK动态代理是通过java.lang.reflect.Proxy类来实现的,可以调用Proxy类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring框架会默认使用JDK动态代理来实现AOP。通过一个案例来演示。
package dao;
public interface UserDao {
public void addUserDao();
public void deleteUser();
}
package dao;
public class UserDaoImpl implements UserDao{
@Override
public void addUserDao() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
package aspect;
public class MyAspect {
public void check_permission(){
System.out.println("----模拟检查访问----");
}
public void log(){
System.out.println("----模拟记录日记----");
}
}
package jdk;
import aspect.MyAspect;
import dao.UserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Jdk代理类
*/
public class JdkProxy implements InvocationHandler {
//声明目标类接口
private UserDao userdao;
// 创建代理方法
public Object createProxy(UserDao userdao){
this.userdao=userdao;
//类加载器
ClassLoader classLoader=JdkProxy.class.getClassLoader();
//被代理对象实现的所有接口
Class[] clazz=userdao.getClass().getInterfaces();
//使用代理类进行增强,返回的是代理后的对象
return Proxy.newProxyInstance(classLoader,clazz,this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//声明切面
MyAspect myAspect=new MyAspect();
//前增强
myAspect.check_permission();
//在目标上调用方法,并传入参数
Object obj=method.invoke(userdao,args);
//后增强
myAspect.log();
return obj;
}
}
@Test
public void shouldAnswerWithTrue()
{
JdkProxy jdkProxy=new JdkProxy();
UserDao userDao=new UserDaoImpl();
UserDao userDao1=(UserDao) jdkProxy.createProxy(userDao);
userDao1.addUserDao();
System.out.println("\n-----------------------------分割线------------------------------------\n");
userDao1.deleteUser();
}
JDK 动态代理的使用非常简单,但它具有一定的局限性(使用动态代理的对象必须实现一个或多个接口)如果要对没有实现接口的类进行代理,那么可以使用CGLIB代理。
CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。在Spring框架的核心包中已经集成了CGLIB所需要的包,所以开发中不需要另外导入jar包。
package dao;
public class BookDao {
public void addBook(){
System.out.println("添加书本");
}
public void deleteBook(){
System.out.println("删除书本");
}
}
package jdk;
import aspect.MyAspect;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
//代理方法
public Object createProxy(Object target){
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
MyAspect myAspect=new MyAspect();
myAspect.check_permission();
Object o1=methodProxy.invokeSuper(proxy,args);
myAspect.log();
return o1;
}
}