AOP(面向切面编程)
1、什么是AOP?
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,通过不修改源代码的方式,来进行功能的扩展(开闭原则的体现)
2、AOP的好处?
利用AOP可以对业务逻辑各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。
3、AOP的实现分类
按照 AOP 框架修改源代码的时机,可以将其分为两类:
- 静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
- 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。
4、AOP和OOP
- OOP:纵向抽取
- 提高重用性
- 提高可扩展性
- 多态
- 基于类级别
- AOP:横向抽取
- 提高可重用性
- 提高可扩展和可维护性
- 基于方法级别
4、AOP底层原理(重要)
代理模式(重点)
-
什么是代理
代理,就是你委托别人帮你办事,所以代理模式也有人称作委托模式的。
-
代理模式的目的
在不修改原有代码的条件下,对目标方法进行功能增强。
-
代理模式的分类
静态代理和动态代理
静态代理:代理类和被代理的类实现了同样的接口,代理类同时持有被代理类的引用,这样,当我们需要调用被代理类的方法时,可以通过调用代理类的方法来做到。
动态代理:动态代理实现的目的和静态代理一样,都是对目标方法进行增强,而且让增强的动作和目标动作分开,达到解耦的目的。
-
静态代理的实现示例
1、定义接口,让被代理类实现接口
public interface PersonService { void savePerson(); } public class PersonServiceImpl implements PersonService { @Override public void savePerson() { System.out.println("添加人"); } }
2、定义代理类,添加增强功能,并实现和被代理类相同的接口
public class MyTransaction { public void beginTransaction(){ System.out.println("开启事务 "); } } public class PersonServiceProxy implements PersonService { //目标类 private PersonService personService; //增强类 private MyTransaction transaction; public PersonServiceProxy(PersonService personService, MyTransaction transaction) { this.personService = personService; this.transaction = transaction; } @Override public void savePerson() { transaction.beginTransaction(); personService.savePerson(); } }
-
静态代理的特点
- 由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
- 代理类和被代理的类实现了同样的接口,代理类同时持有被代理类的引用。
-
静态代理的缺点
1、假设一个系统中有100个Service,则需要创建100个代理对象
2、如果一个Service中有很多方法需要事务(增强动作),发现代理对象的方法中还是有很多重复的代码
3、由第一点和第二点可以得出:静态代理的重用性不强
-
动态代理
-
1)AOP 底层使用了动态代理
有两种情况下的动态代理
第一种,有接口情况 ————> JDK动态代理 :创建接口实现类的代理对象,然后增强类的方法
第二种,没接口情况 ————> CGLIB动态代理:创建当前类子类的代理对象,然后增强类的方法
- 2)JDK动态代理
- 1、**使用JDK动态代理,使用Proxy类里面的newProxyInstance静态方法创建代理对象 **
- 2、Proxy类中newProxyInstance方法,该方法有三个参数,第一个参数是:类加载器,第二个参数是:增强方法所在的类,这个实现的接口,支持多个接口,第三个参数是:实现InvocationHandler接口的类。创建代理对象,写增强方法
- 3)CGLIB动态代理:需要代理类实现MethodInterceptor接口
JDK动态代理示例
UserDaoImpl对象(被代理类对象)
package all.spring3.demo;
import all.spring3.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
System.out.println("add 执行...");
return a + b;
}
@Override
public void update(String id) {
System.out.println("update 执行....");
System.out.println(id);
}
}
UserDaoProxy对象(代理类对象)
package all.spring3.demo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
//创建代理对象代码
public class UserDaoProxy implements InvocationHandler {
private Object obj;
//创建的是谁的代理对象,就把谁传过来
public UserDaoProxy(Object userDao) {
this.obj = userDao;
}
//增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前
System.out.println("方法之前:" + method.getName() + "传递的参数" + Arrays.toString(args));
Object res = null;
if (method.getName().equals("add")) {
//被增强的方法的执行
res = method.invoke(obj, args);
//方法之后
System.out.println("方法之后:" + obj);
}
if (method.getName().equals("update")) {
//被增强的方法的执行
res = method.invoke(obj, args);
//方法之后
System.out.println("方法之后:" + obj);
}
return res;
}
}
CGLIB动态代理示例
-
导入依赖
cglib cglib-nodep 3.3.0 -
定义一个被代理类
public class TeacherService { public void addTeacher(){ System.out.println("添加老师"); } }
-
定义增强类
public class MyTransaction { public void beginTransaction(){ System.out.println("开启事务 "); } }
-
定义一个代理类,实现MethodInterceptor接口
public class MyMethodInterceptor implements MethodInterceptor { // 增强类 private MyTransaction transaction; public MyMethodInterceptor(MyTransaction transaction) { this.transaction = transaction; } /** * * @param o cglib生成的代理对象 * @param method 被代理对象方法 * @param objects 方法入参 * @param methodProxy 代理方法 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { transaction.beginTransaction(); Object object = methodProxy.invokeSuper(o, objects); return object; } }
-
定义生成代理类的工具类
public class CGProxy { public <T> T createProxy(T t,MyTransaction obj){ // 通过CGLIB动态代理获取代理对象的过程 Enhancer enhancer = new Enhancer(); // 设置enhancer对象的父类 enhancer.setSuperclass(t.getClass()); // 设置enhancer的回调对象 enhancer.setCallback(new MyMethodInterceptor(obj)); // 创建代理对象 T proxy= (T)enhancer.create(); return proxy; } }
-
测试
@Test public void test3(){ CGProxy proxy=new CGProxy(); // 代理类 TeacherService teacherService=new TeacherService(); // 增强类 MyTransaction myTransaction = new MyTransaction(); TeacherService t= proxy.createProxy(teacherService,myTransaction); t.addTeacher(); }
JDK动态代理和CGLib的区别
1)JDK和CGLib的区别
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
CGLib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)
2)Spring在选择用JDK还是CGLib的依据
当Bean实现接口时,Spring就会用JDK的动态代理
当Bean没有实现接口时,Spring使用CGLib来实现
可以强制使用CGLib(在Spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)
3)JDK和CGLib的性能对比
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理