在实际开发中我们需要添加一些非业务的代码,例如提交事务,记录日志等等,此时你可能会这些编写程序:
定义一个记录日志的工具类:
public class MyLog {
/**
* 记录日志
* @param clazz
*/
public static void doLog(Class clazz){
System.out.println("记录日志:" + clazz.getName());
}
}
定义一个提交事务的工具类:
public class MyTransaction {
public static void doTransaction(Class clazz){
System.out.println("提交事务:" + clazz.getName());
}
}
在dao中使用这两个工具类:
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
//记录日志
MyLog.doLog(this.getClass());
//业务逻辑
System.out.println("添加用户数据");
//提交事务
MyTransaction.doTransaction(this.getClass());
}
public void doOther(){
System.out.println("做一些其他的事情");
}
}
上面这段程序还是有些问题的,在增删改查的每个方法中都需要使用上面两个工具类,这样会导致这两个工具类侵入到了业务逻辑的代码中,影响了业务逻辑代码的可读性,降低了代码的可维护性,同时也增加了开发难度。
使用动态代理动态代理可以在不修改主业务逻辑的前提下,扩展和增强其功能,这里我们使用jdk自带的动态代理来解决上面问题。
创建动态代理类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//记录日志
MyLog.doLog(target.getClass());
//业务逻辑
Object invoke = method.invoke(target, args);
//提交事务
MyTransaction.doTransaction(target.getClass());
return invoke;
}
}
修改之前的UserDaoImpl类:
@Override
public void addUser() {
System.out.println("添加用户数据");
}
创建测试方法:
import com.monkey1024.dao.UserDao;
import com.monkey1024.dao.impl.UserDaoImpl;
import com.monkey1024.util.MyInvocationHandler;
import java.lang.reflect.Proxy;
public class Test02 {
public static void main(String[] args) {
//创建目标类对象
UserDao userDao = new UserDaoImpl();
//创建代理
UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), new MyInvocationHandler(userDao));
userDaoProxy.addUser();
}
}
通过动态代理的方式,可以看到在UserDaoImpl类中只有业务逻辑代码,非常整洁。
AOP简介AOP(Aspect Orient Programming),面向切面编程,是面向对象编程 OOP 的一种补充。在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。例如转账功能,在转账代码的前后需要一些非业务方面的处理,权限控制,记录日志,事务的开启与结束,这些代码就可以使用AOP将其切入到转账代码的前后,这样就可以很好地分离业务代码和非业务代码。 AOP的优点就是降低代码之间的耦合,提高代码的复用性。
spring底层就是采用动态代理模式实现AOP的。采用了两种代理:
- JDK 的动态代理,如果被代理了实现了接口,会默认使用jdk的动态代理。
- CGLIB的动态代理,如果类没有实现接口,会使用CGLIB动态代理。
spring之所以会引入这两种方式是因为其各有优缺点, 从性能上讲,使用字节码处理的CGLIB要比使用反射的JDK动态代理好。 从耦合度上讲,jdk要好于额外需要依赖字节码处理框架ASM的CGLIB。
(1)目标对象(Target) 目标对象指 将要被增强的对象。即包含主业务逻辑的类的对象。上例中的UserDaoImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。
(2)切面(Aspect) 切面泛指非业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面有通知,实际就是对业务逻辑的一种增强。
(3)连接点(JoinPoint) 连接点指可以被切面织入的方法。通常业务接口中的方法均为连接点。
(4)切入点(Pointcut) 切入点指切面具体织入的方法。在 UserDaoImpl 类中,若 addUser()被增强,而doOther()不被增强,则 addUser()为切入点,而 doOther()仅为连接点。 被标记为 final 的方法是不能作为连接点与切入点的,因为是不能被修改的,不能被增强的。
(5)通知(Advice) 通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。上例中的MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。Advice有下面几种,这里使用常用的AspectJ方式:
- 前置通知(Before advice):在连接点之前执行,即目标方法执行之前执行。
- 后置通知(After returning advice):在连接点正常结束之后执行,如果连接点抛出异常,则不执行。
- 异常通知(After throwing advice):在连接点抛出异常后执行
- 最终通知(After (finally) advice):在连接点结束之后执行,无论是否抛出异常,都会执行。
- 环绕通知(Around advice):在连接点之前和之后均执行。
(6)织入(Weaving) 织入是指将切面代码插入到目标对象的过程。上例中 MyInvocationHandler 类中的 invoke() 方法完成的工作,就可以称为织入。
(7)aop代理(AOP proxy) spring中的aop代理有两种:jdk自带的动态代理和CGLIB代理。