什么是AOP编程
- Aspect Oriented Programming 面向切面编程
- 主要应用场景是日志记录,权限控制,性能监控,事务处理,异常处理等
- 主要意图是将日志记录,权限控制,性能监控,事务处理,异常处理等代码从业务逻辑代码中抽取出来,降低耦合性并解决代码复用的问题
- 底层实现原理是代理设计模式,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能.假设把应用程序想成一个立体结构的话,OOP的利刃是纵向切入系统,把系统划分为很多个模块(如: 用户模块,文章模块等等),而AOP的利刃是横向切入系统,提取各个模块可能都要重复操作的部分(如: 权限检查,日志记录等等).由此可见,AOP是OOP的一个有效补充
名词解释
- 关注点: 重复代码(如打印日志的代码)
- 切面/切面类: 关注点形成的类,将重复代码抽取出来,在运行的时候在业务方法上动态植入
- 切入点: 执行目标对象方法,动态植入切面代码(可以通过切入点表达式,指定拦截哪些类的哪些方法;给指定的类在运行的时候植入切面类代码)
AOP底层实现原理
代理的分类:
静态代理和动态代理
静态代理和动态代理的区别:
静态代理是静态定义代理类(需要手写代理类);动态代理是动态生成代理类(虚拟生成代理类)
动态代理分为:
jdk动态代理和cglib动态代理
jdk动态代理和cglib的区别:
jdk是基于反射机制实现的;
cglib是基于ASM字节码包装的一个类库(性能比反射要高一些)(ASM是一个java字节码操控框架,它能被用来动态生成类或者增强既有类的功能.ASM可以直接产生二进制class文件,也可以在类被加载进java虚拟机之前动态改变类的行为.)
AOP实现原理: 静态代理和动态代理
-
什么是静态代理: 由程序员创建或工具生成代理类的源码,再编译代理类.所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了.
//接口 public interface IUserDao { void save(); }
//被代理类 public class UserDaoImpl implements IUserDao { @Override public void save() { System.out.println("假装把数据入库了..."); } }
//代理类 public class UserDaoProxy implements IUserDao { private IUserDao target; public UserDaoProxy(IUserDao userDao) { this.target = userDao; } @Override public void save() { System.out.println("开启事务..."); target.save(); System.out.println("提交事务..."); } }
什么是动态代理: 代理类不需要实现接口,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
-
jdk动态代理(原理是根据类加载器和接口创建代理类,此代理类是接口的实现类,所以必须使用接口,面向接口生成代理,接口位于java.lang.reflect包下)
public class InvocationHandlerImpl implements InvocationHandler{ // 这其实是业务实现类对象,用来调用具体的业务方法 private Object target; // 通过构造函数传入目标对象 public InvocationHandlerImpl(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; System.out.println("调用开始处理---"); result = method.invoke(target, args); System.out.println("调用结束处理---"); return result; } public static void main(String[] args){ // 被代理对象 IUserDao userDao = new UserDaoImpl(); InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(userDao); ClassLoader loader = userDao.getClass().getClassLoader(); Class<?>[] interfaces = userDao.getClass().getInterfaces(); // 主要装载器、一组接口及调用处理动态代理实例 IUserDao newProxyInstance = (IUserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandlerImpl); newProxyInstance.save(); } }
-
cglib动态代理(原理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)
public class CglibProxy implements MethodInterceptor { private Object targetObject; // 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理 public Object getInstance(Object target) { // 设置需要创建子类的类 this.targetObject = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("开启事务.."); Object result = proxy.invoke(targetObject, args); System.out.println("关闭事务.."); // 返回代理对象 return result; } public static void main(String[] args) { CglibProxy cglibProxy = new CglibProxy(); UserDaoImpl userDao = (UserDaoImpl) cglibProxy.getInstance(new UserDaoImpl()); userDao.save(); } }
-
jdk动态代理和cglib动态代理的区别
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
Spring中:
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP ;
如果目标对象实现了接口,可以强制使用CGLIB实现AOP ;
如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换 ;
JDK动态代理只能对实现了接口的类生成代理,而不能针对类 ;
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ;
因为是继承,所以该类或方法最好不要声明成final ,final可以阻止继承和多态。
AOP编程使用
-
注解版本实现AOP
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> 开启事物注解权限
@Component //交给Spring来处理 @Aspect //指定一个类为切面类 public class AopLog { // 前置通知 @Before("execution(* com.pliu.service.UserService.add(..))") public void begin() { System.out.println("前置通知"); } // 后置通知 @After("execution(* com.pliu.service.UserService.add(..))") public void commit() { System.out.println("后置通知"); } // 运行通知 @AfterReturning("execution(* com.pliu.service.UserService.add(..))") public void returning() { System.out.println("运行通知"); } // 异常通知 @AfterThrowing("execution(* com.pliu.service.UserService.add(..))") public void afterThrowing() { System.out.println("异常通知"); } // 环绕通知 @Around("execution(* com.pliu.service.UserService.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知开始"); proceedingJoinPoint.proceed(); System.out.println("环绕通知结束"); } }
- xml版本实现AOP(不好意思,感觉麻烦懒得写,如有必要还请百度吧)
Spring事务使用
-
事务基本特性
⑴ 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。⑵ 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。⑶ 隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。⑷ 持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
- 事务控制分类
编程式事务: 手动事务(自己去begin,commit,rollback),灵活但繁琐
声明式事务: 自动事务,原理是使用编程式事务+反射机制进行包装.分注解版本和xml版本