一、AOP
1.1、AOP介绍
1.1.1、什么是AOP?
- 在软件业,AOP为Aspect Oriented Programming的缩写,意为:
面向切面编程
,通过预编译方式
和运行期动态代理
实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程
的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离
,从而使得业务逻辑各部分之间的耦合度降低
,提高程序的可重用性
,同时提高了开发的效率。 - AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码。如下图所示:
- 经典应用:事务管理、性能监视、安全检查、缓存 、日志等。
- Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过
代理方式
向目标类织入增强代码。 - AspectJ是一个基于Java语言的AOP框架,从Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入。
1.1.2、AOP实现原理
- aop底层将采用
代理机制
进行实现。 - 接口 + 实现类时 :spring采用 jdk 的
动态代理
Proxy。 - 只有实现类时:spring 采用
cglib 字节码增强
。
1.1.3、AOP术语【掌握】
- Target :目标类,需要被代理的类。本例中如:UserService
- Joinpoint(连接点) :所谓连接点是指那些可能被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。本例中如:UserService的所有的方法
- PointCut 切入点 :所谓切入点是指我们要对哪些Joinpoint进行拦截,即已经被增强的连接点。例如:addUser()
- Advice :通知/增强,增强的代码。例如:after()、before()
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知、后置通知、异常通知、最终通知、环绕通知(即切面要完成的功能)。 - Weaving(织入) :是指把通知/增强advice应用到目标对象target来创建新的代理对象proxy的过程。
spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入。 - Proxy :代理类,一个类被AOP织入增强后,就产生一个结果代理类。
- Aspect(切面) : 是切入点Pointcut和通知Advice(引介)的结合。
- Introduction(引介) :引介是一种
特殊的通知
,在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或Field。
- 小结:
一个线是一个特殊的面。
一个切入点和一个通知,组成成一个特殊的面。
详解如图01:
详解如图02:
1.2、手动方式
1.2.1、JDK动态代理
- JDK动态代理:是对“装饰者”设计模式的简化。JDK动态代理使用前提:必须有接口。
- 目标类:接口 + 实现类
- 切面类:用于存放通知,名称叫:MyAspect.java
- 工厂类:编写工厂生成代理
- 测试类
1.2.1.1、目标类
UserService.java
// 目标接口
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}
UserServiceImpl.java
// 目标实现类,有接口
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("a_proxy.a_jdk addUser");
}
@Override
public void updateUser() {
System.out.println("a_proxy.a_jdk updateUser");
}
@Override
public void deleteUser() {
System.out.println("a_proxy.a_jdk deleteUser");
}
}
1.2.1.2、切面类
MyAspect.java
// 切面类
public class MyAspect {
public void before() {
System.out.println("前方法");
}
public void after() {
System.out.println("后方法");
}
}
1.2.1.3、工厂类(自定义的)
MyBeanFactory.java
// 工厂类
public class MyBeanFactory {
public static UserService createService() {
// 1、先有目标类对象
final UserService userService = new UserServiceImpl();
// 2、再有切面类
final MyAspect myAspect = new MyAspect();
/* 3、最后有代理类,将目标类(切入点)和切面类(通知)进行结合 =》 切面
* Proxy.newProxyInstance
* 参数1:ClassLoader loader 类加载器,我们知道,动态代理类在运行时创建的,任何类都需要类加载器将其加载到内存。
* 类加载器该如何写呢?
* 答:一般情况下:当前类.class.getClassLoader()
* 或者 目标类的实例.getClass().getClassLoader()
*
* 参数2:Class[] interfaces 代理类需要实现的所有接口
* 方式1:目标类的实例.getClass().getInterfaces() 注意:该方式只能获得自己接口,不能获得父元素接口
* 方式2:new Class[]{UserService.class}
* 例如:jdbc 驱动 => DriverManager => 获得接口 Connection
*
* 参数3:InvocationHandler h 处理类,是一个接口,必须进行实现类,一般情况下采用:匿名内部类
* 该接口提供了一个 invoke 方法,代理类的每一个方法执行时,都将调用一次invoke 方法
* 参数31:Object proxy 代理对象
* 参数32:Method method 代理对象当前执行的方法的描述对象(反射)
* 执行的方法名:method.getName()
* 执行的方法:method.invoke(对象, 实际参数)
* 参数33:Object[] args 方法的实际参数
*/
UserService proxyService = (UserService) Proxy.newProxyInstance(
MyAspect.class.getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前执行
myAspect.before();
// 执行目标类的方法
Object obj = method.invoke(userService, args);
// 后执行
myAspect.after();
return obj;
}
});
return proxyService;
}
}
1.2.1.4、测试类
TestJDK.java
// 测试类
public class TestJDK {
@Test
public void demo01() {
UserService userService = MyBeanFactory.createService();
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
程度运行结果为:
前方法
a_proxy.a_jdk addUser
后方法
前方法
a_proxy.a_jdk updateUser
后方法
前方法
a_proxy.a_jdk deleteUser
后方法
- debug调试的结果:
- JDK动态代理返回的是:
$Proxy (id=34)
1.2.2、CGLIB字节码增强
- 没有接口,只有实现类。
- 采用字节码增强框架 cglib,运行原理:在运行时,创建目标类的子类,从而对目标类进行增强。
- 导入jar包:
自己导jar包(了解):
- 核心包:hibernate-distribution-3.6.10.Final\lib\bytecode\cglib\cglib-2.2.jar
- 依赖包:struts-2.3.15.3\apps\struts2-blank\WEB-INF\lib\asm-3.3.jar
- spring-core-3.2.0.RELEASE.jar 已经整合以上两个内容,所以我们只需要导入这个包就可以了,如下图所示:
项目中的位置:
1.2.2.1、目标类
UserServiceImpl.java
// 目标实现类,没接口
public class UserServiceImpl {
public void addUser() {
System.out.println("a_proxy.b_cglib addUser");
}
public void updateUser() {
System.out.println("a_proxy.b_cglib updateUser");
}
public void deleteUser() {
System.out.println("a_proxy.b_cglib deleteUser");
}
}
1.2.2.2、切面类
MyAspect.java的代码同上 1.2.1.2、切面类
代码,这里不再赘述!
1.2.2.3、工厂类(自定义的)
MyBeanFactory.java
// 工厂类
public class MyBeanFactory {
public static UserServiceImpl createService() {
// 1、先有目标类对象
final UserServiceImpl userServiceImpl = new UserServiceImpl();
// 2、再有切面类对象
final MyAspect myAspect = new MyAspect();
// 3、最后有代理类,采用cglib,底层创建目标类的子类
// 3.1、核心类
Enhancer enhancer = new Enhancer();
// 3.2 、先确定父类
enhancer.setSuperclass(userServiceImpl.getClass());
/* 3.3、 设置回调函数 ,MethodInterceptor接口 等效 jdk中的 InvocationHandler接口
* intercept() 等效 jdk中的 invoke()
* 参数1、参数2、参数3:和以invoke()方法的参数一样
* 参数4:methodProxy 方法的代理
*/
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 前执行
myAspect.before();
// 执行目标类的方法
Object obj = method.invoke(userServiceImpl, args);
// 执行代理类的父类,就是执行目标类(目标类和代理类是斧子关系),相当于inwoke调用了2次
methodProxy.invokeSuper(proxy, args);
// 后执行
myAspect.after();
return obj;
}});
// 4、创建代理
UserServiceImpl proxyService = (UserServiceImpl) enhancer.create();
return proxyService;
}
}
1.2.2.4、测试类
TestJDK.java的代码同上 1.2.1.4、切面类
代码,这里不再赘述!
程度运行结果为:
前方法
a_proxy.b_cglib addUser
a_proxy.b_cglib addUser
后方法
前方法
a_proxy.b_cglib updateUser
a_proxy.b_cglib updateUser
后方法
前方法
a_proxy.b_cglib deleteUser
a_proxy.b_cglib deleteUser
后方法
- debug调试的结果:
- CGLIB字节码增强返回的是:
UserServiceImpl$$EnhancerByCGLIB$$157a2b67 (id=34)
1.2.3、代理知识总结
- Spring在运行期,生成动态代理对象,不需要特殊的编译器。
- Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术为目标Bean执行横向织入的。
- 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
- 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
- 程序中应优先对接口创建代理,便于程序解耦维护。
- 标记为final的方法,不能被代理,因为无法进行覆盖。
- JDK动态代理,是针对接口生成子类,接口中的方法不能使用final修饰。
- CGLib动态代理,是针对目标类生产子类,因此目标类和目标类的方法是不能使用final修饰。
- Spring只支持方法连接点,不提供属性连接。
1.3、AOP联盟增强(通知)类型
- AOP联盟为通知Advice定义了org.aopalliance.aop.Advice
- Spring按照通知Advice在目标类方法的连接点位置,可以分为5类:
- 1、前置通知:org.springframework.aop.MethodBeforeAdvice
- 在目标方法执行前实施增强
- 2、后置通知:org.springframework.aop.AfterReturningAdvice
- 在目标方法执行后实施增强
- 3、环绕通知:org.aopalliance.intercept.MethodInterceptor
- 在目标方法执行前后实施增强
- 4、异常抛出通知:org.springframework.aop.ThrowsAdvice
- 在方法抛出异常后实施增强
- 5、引介通知:org.springframework.aop.IntroductionInterceptor
- 在目标类中添加一些新的方法和属性
- 1、前置通知:org.springframework.aop.MethodBeforeAdvice
模拟环绕通知:
环绕通知:`必须手动执行目标方法`
try {
// 前置通知
// 执行目标方法
// 后置通知
} catch() {
// 异常抛出通知
}
1.4、spring 编写代理:半自动
- 让spring 给我们创建代理对象,我们从spring容器中手动的获取代理对象。
- 导入jar包:
- 核心jar包:4 + 1
- AOP的jar包:AOP联盟(规范/接口)、spring-aop(实现)
1.4.1、目标类
UserService.java
// 目标接口
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}
UserServiceImpl.java
// 目标实现类,有接口
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("b_factory_bean addUser");
}
@Override
public void updateUser() {
System.out.println("b_factory_bean updateUser");
}
@Override
public void deleteUser() {
System.out.println("b_factory_bean deleteUser");
}
}
1.4.2、切面类
// 切面类
/**
* 切面类中需要删除之前自己写的通知,添加上AOP联盟提供的通知(即要增强的东西),需要实现不同接口,接口就是规范,从而就确定方法名称。
* 采用“环绕通知” MethodInterceptor
*
*/
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("我们的前代码");
// 使用AOP联盟的环绕通知:必须手动执行目标方法
Object obj = mi.proceed();
System.out.println("我们的后代码");
return obj;
}
}
1.4.3、spring配置
bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans