文章目录
面向切面编程的 AOP
1、AOP(Aspect-OrientedProgramming,面向切面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合,这些公共的行为就好像是一个主线任务,有且仅有这么一条主线。举日志代码为例,日志代码存在所有对象层次中,它与核心功能几乎就有没有关系,就好比是支线任务,可以存在每一个阶段,在OOP设计中,它导致了大量代码的重复,模块间的藕合度高,而不利于各个模块的重用。AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,由点即面的横向执行。
2、横向切面的好处也是显而易见的:AOP 可减少 OOP 中大量的重复性代码,而且还行可以在降低耦合度,使核心模块只关心核心内容。
AOP的实现原理(针对的是业务处理)
AOP 实际上是由目标类的代理类实现的。AOP 代理其实是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但 AOP 代理中的方法与目标对象的方法存在差异,AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法。
AOP 代理的3种模式
-
静态代理(编译时增强):
通过让代理类实现与目标类一样的接口,在代理类中关联一个目标类,对于需要修改的方法进行修改,对于不需要修改的功能,直接使用关联的目标类进行调用。 -
JDK动态代理(运行时增强):
通过JDK提供的反射机制中的Proxy类和InvocationHandler接口,实现在运行时,创建代理类的二进制文件及其实例,运行原理与静态代理类似。 -
CGLIB代理(子类代理,运行时增强)
在运行时,创建目标类的子类实例。
Spring对AOP的支持
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
-
默认使用JDK动态代理来创建AOP代理,这样就可以为任何接口实例创建代理。
-
当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB。
AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:
-
定义普通业务组件
-
定义切入点,一个切入点可能横切多个业务组件
-
定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法 process()。
AOP实现
针对业务处理层方法的增强。
public class ServiceImpl implements Service {
private Dao dao;
/* 设置 Setter 方法,完成 dao 层的注入 */
public void setDao(Dao dao) {
this.dao = dao;
}
/* 无参构造方法 */
public ServiceImpl() {}
/* 带参数的构造方法 */
public ServiceImpl(Dao dao){
this.dao = dao;
}
public void save(String username, String password) {
dao.save(username, password);
throw new RuntimeException("抛出一个异常。对操作save方法执行回滚或者Aop方法。");
}
}
// 对于业务系统来说,业务层的实现类(ServiceImpl 类)就是目标实现类,它的业务方法,如上述代码片段中的 save() 方法的前后或代码会出现异常的地方都是AOP的连接点。
基于xml配置文件方式的实现(基于Schema的配置)
- 设置增强类。为目标实现类添加增强的方法。
1.1、在开发中最为重要的是编写日志文件。
1.2、最初开发的时候我会在一个类中,写一个又一个的 System.out.print(" 检测自己的内容 ");
1.3、之后知道了日志,log4j 等,开始编写 log.debug();
- 最后,使用SpringAOP切面后,能够有效的减少许多重复的日志代码。
public class LoggerAspect {
//任何通知方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint类型
public void before(JoinPoint joinPoint) {
//获取目标对象对应的类名
String className = joinPoint.getTarget().getClass().getName();
//获取目标对象上正在执行的方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("前置通知:" + className + "类的" + methodName + "方法开始了");
}
public void afterReturn() {
System.out.println("后置通知:方法正常结束了");
}
public void after(){
System.out.println("最终通知:不管方法有没有正常执行完成,一定会返回的");
}
public void afterThrowing() {
System.out.println("异常抛出后通知:方法执行时出异常了");
}
//用来做环绕通知的方法可以第一个参数定义为org.aspectj.lang.ProceedingJoinPoint类型
public Object doAround(ProceedingJoinPoint call) throws Throwable {
Object result = null;
this.before(call);
try {
result = call.proceed();
// 调用此类中的后置通知
this.afterReturn();
} catch (Throwable e) {
// 调用此类中的后置异常通知
this.afterThrowing();
throw e;
}finally{
// 调用此类中的最后通知
this.after();
}
return result;
}
}
属于业务服务类,换用AOP的术语来说,它就是一个切面类,它定义了许多通知。Before()、afterReturn()、after()和afterThrowing() 这些方法都是通知。
- ProceedingJoinPoint 和 JoinPoint 的源码
1) JoinPoint
java.lang.Object[] getArgs():获取连接点方法运行时的入参列表;
Signature getSignature() :获取连接点的方法签名对象;
java.lang.Object getTarget() :获取连接点所在的目标对象;
java.lang.Object getThis() :获取代理对象本身;
2) ProceedingJoinPoint
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:
java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法;
java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,可以设置新的参数,简单说,可以通过这个方法进行参数增强。
- 配置 applicationContent-tx.xml ,加载到 Spring 容器中。
<bean id="Service" class="com.RobertChao.ServiceImpl">
<property name="Dao" ref="Dao"/>
</bean>
<!-- 1、定义日志切面类,执行增强的方法 -->
<bean id="LogBean" class="com.RebortChao.LoggerAspect "/>
<!-- 2、AOP的配置 -->
<aop:config>
<!-- 3、配置一个切面 -->
<aop:aspect id="LogBean" ref="LogBean">
<!-- 4、定义切入点,指定切入点表达式 -->
<aop:pointcut id="pointcut"
expression="execution(* com.RobertChao.service.*.*(..))"/>
<!-- 5、应用各种通知,设置通知使用的方法,以及对应的切入点 -->
<aop:before method="before" pointcut-ref="pointcut" />
<aop:after-returning method="afterReturn" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"/>
<aop:around method="doAround" pointcut-ref="pointcut" />
</aop:aspect>
</aop:config>
基于注解的AOP的实现(基于AspectJ的配置)
- 重新配置 applicationContext-tx.xml
<bean id="Service" class="com.RobertChao.ServiceImpl">
<property name="Dao" ref="Dao"/>
</bean>
<!-- 把切面类交由Spring容器来管理 -->
<bean id="logAspectBean" class="com.zxf.aspect.LogAnnotationAspect"/>
<!-- 启用spring对AspectJ注解的支持 -->
<aop:aspectj-autoproxy/>
- 配置切面的类 LoggerAnnotationAspect
@Aspect //定义切面类
public class LogAnnotationAspect {
// 定义切入点,提供一个方法,这个方法的名字就 切入点的id
@Pointcut("execution(* com.RobertChao.service.*.*(..))")
private void pointcut(){}
// 针对指定的切入点表达式选择的切入点应用前置通知
@Before("execution(* com. RobertChao.service.*.*(..))")
public void before(JoinPoint jp) {
String className = jp.getTarget().getClass().getName();
String methodName = jp.getSignature().getName();
System.out.println("前置通知:" + className + "类的"
+ methodName + "方法开始执行……");
}
// 访问切点后的通知
@AfterReturning("pointcut()")
public void afterReturn() {
System.out.println("后置方法:方法正常执行结束后执行");
}
// 执行完毕后通知
@After("pointcut()")
public void after(){
System.out.println("最终通知:不管方法有没有正常执行完成,"
+ "一定会返回的");
}
// 异常抛出后通知
@AfterThrowing("pointcut()")
public void afterThrowing() {
System.out.println("异常抛出后通知:方法执行时出异常,抛出异常后通知");
}
// 环绕通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
Object result = null;
调用前置通知
this.before(pjp);
try {
result = pjp.proceed();
// 返回结果后通知
this.afterReturn();
} catch (Throwable e) {
// 抛出异常后通知
this.afterThrowing();
throw e;
}finally{
// 最后通知
this.after();
}
return result;
}
}
我在课堂上使用的两种联系的源码:
Spring中AOP的两种代理方式(JDK动态代理和CGLIB代理)
JDK动态代理
JDK动态代理主要涉及java.lang.reflect包下边的两个类:Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑贬值在一起。
// 需要被代理的接口
public interface Service {
public void save(int id);
}
// 被代理接口的实现类,包含核心的业务逻辑
public class ServiceImpl implements Service{
@Override
public void save(int id); {
System.out.println("模拟保存记录:"+ id);
try {
// 线程休息 0.02 秒
Thread.currentThread().sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// AOP横切模块
public class HandlerAop implements InvocationHandler{
private Object target;
// 设置有参数的构造器
public HandlerAop (Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.print("开始执行:"+method.getName());
Object object = method.invoke(target, args);
System.out.print("执行结束:"+method.getName());
return object;
}
}
// 测试
public class TestForumService {
public static void main(String[] args) {
Service target = new ServiceImpl();
// 将目标业务类与横切代码编织到一起
PerfermanceHandler handler = new PerfermanceHandler(target);
// 创建代理实例
Service proxy = (Service) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), handler);
proxy.save(1);
}
}
- 执行结果
开始执行:save()
被增强的方法的执行。
开始执行:save()
CGLib 代理
采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的结束拦截所有父类方法的调用,并顺势织入横切逻辑。我们采用CGLib技术可以编写一个可以为任何类创建织入横切逻辑代理对象的代理创建器。
public class CglibProxy implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz){
// 设置创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
// 通过字节码技术动态创建子类实例
return enhancer.create();
}
@Override
public Object intercept(Object target, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.print("开始执行:"+method.getName());
Object object = proxy.invokeSuper(target, args);
System.out.print("结束执行:"+method.getName());
return object;
}
}
//测试
public class TestService {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
ServiceImpl service = (ServiceImpl) cglibProxy.getProxy(ServiceImpl.class);
service.removeForum(10);
service.removeTopic(1012);
}
}
- 结果
开始执行:save()
被增强的方法的执行。
开始执行:save()
EnhancerByCGLIB
大大小小的方法总结了四种:
- 原始的方法(两种)
- 基于 xml 的配置方式
- 基于 注解 的配置方式
- Spring (两种)
- 基于 JDK 动态代理的方式
- 基于 CGLib 代理的方式