面向切面编程
通过横向抽取机制来解决类中的代码重复问题
- 代理目标(target)
- 被代理类实例代理的目标对象
- 代理对象(proxy)
- 代理其他类的对象
AOP中的概念
- 连接点(JoinPoint) :执行点+方位
- 执行点 :任意一个可执行的方法
- 切点(PoinCut)
- 对连接点进行筛选的条件 指定执行点和方位 确定在哪里添加代码
- Advice :在指定的切点所选择的连接点所加入的代码
- 切面(Aspect) :切面( Aspect ) = 切点( Pointcut ) + Advice
- 织入
- 将 Advice 对应的代码 加入到 切点 所选择的 连接点 的过程
- 实现方式:编译时织入 类加载时织入 运行期织入
- 方位
- 方法执行前(before)
/**
* 任意一个 Advice 对应的方法中 , 第一个参数都可以是 org.aspectj.lang.JoinPoint
* 通过 JoinPoint 可以在 Advice 对应的方法内部 访问 连接点 的信息
* 可以通过 JoinPoint 来访问 连接点 中的信息
*/
public void before (JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();//获取 连接点 对应的方法的 签名
System.out.println("signature:"+signature);
int mod = signature.getModifiers();//获取 方法 的修饰符 返回值为整数
System.out.println("Modifier:"+Modifier.toString(mod));
System.out.println("Name:"+signature.getName());
Object[] args = joinPoint.getArgs();//获取被拦截方法在执行时接受的参数
System.out.println("args:"+ Arrays.toString( args ));
System.out.println( "【 " +joinPoint.getSignature().getName()+ "即将执行】" );
}
- 方法执行后(after)
- 方法执行前后(around)
public Object around( ProceedingJoinPoint joinPoint) throws Throwable { //joinPoint 必须是 ProceedingJoinPoint 类型
String name = joinPoint.getSignature().getName();
System.out.println( "开始为" + name + "计时" );
long begin = System.nanoTime();
Object result = joinPoint.proceed() ; // 通过 ProceedingJoinPoint 的 proceed 方法让被拦截的方法继续执行
long end = System.nanoTime();
System.out.println( "为" + name + "计时结束" );
System.out.println( "[ " + name + " ]执行耗时 [ " + ( end - begin ) + "ns, ]" );
return result ; // 返回 由 被拦截的方法执行后 所返回的值
}
- 方法正常返回后(after-return)
public void afterReturning (JoinPoint joinPoint ,Object returnValue){
System.out.println("【 "+ joinPoint.getSignature().getName()+" 】方法执行后返回了【"+returnValue+"】");
}
- 方法抛出异常后(after-throw)
spring配置文件
- AspectJ是语言级的AOP实现,AspectJ扩展了Java语言,能够在编译期提供横切代码的织入
- 添加 aspectj-runtime 和 aspectj-weaver 依赖并引入 aop 命名空间
- <aop:config proxy-target-class="false"> 是默认值 , 如果给定的类适合使用 JDK 动态代理来产生代理类,则采用JDK动态代理。否则采用 cglib 方式来创建
- <aop:config proxy-target-class="true"> 表示一律使用 cglib 方式来创建代理类
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http:"http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="advices" class="com.it.aop.Advices"/>
//Advices 类中包含在指定切点 所选择的连接点 需要添加的方法
<aop:config>
<aop:aspect ref="advices">
//aspect 切面引入 需要添加的代码
<aop:pointcut id="firstpointcut" expression="execution(* com.it.aop.Fish.fly())"/> //aop:pointcut 选择 某个类中的某个方法 作为切点 第一个*表示修饰符和返回类型
<aop:after-returning method="afterReturning" pointcut-ref="firstpointcut" returning="returnValue"/> //在 引入的切点 的指定方位(after-returning)处添加Advices类中afterReturning方法 并指定 返回变量 returnValue
<aop:around method="around" pointcut-ref="firstpointcut"/>
<aop:before method="before" pointcut-ref="firstpointcut"/>
<aop:after method="after" pointcut-ref="firstpointcut"/>
</aop:aspect>
</aop:config>
- introdcation
- 在 aop:aspect </aop:aspect>中通过配置 declare-parents 为 Cat 类引入 HashMap的实现
<aop:declare-parents types-matching="com.it.aop.schema.Cat"
implement-interface="java.util.Map"
default-impl="java.util.HashMap"/>
<bean id="cat" class="com.it.aop.schema.Cat" p:name="Tom"/>
<aop:config proxy-target-class="true">
<aop:aspect>
<aop:declare-parents types-matching="com.it.aop.schema.Cat"
implement-interface="java.util.Map"
default-impl="java.util.HashMap"/>
</aop:aspect>
</aop:config>
</beans>
测试(Test)
- Fish 类实现了 flyable 接口
- <aop:config proxy-target-class="true"> 表示一律使用 cglib 方式来创建代理类
- 测试结果
【proxy.toString()】: Fish{com.it.aop.schema.Fish:230526532}
【proxy.getClass().getCanonicalName()】: com.it.aop.schema.Fish$$EnhancerBySpringCGLIB$$b63a9d5
【proxy.getClass().getSuperclass()】: class com.it.aop.schema.Fish
- <aop:config proxy-target-class="false"> 时测试结果
【proxy.toString()】: Fish{com.it.aop.schema.Fish:2005028997}
【proxy.getClass().getCanonicalName()】: com.sun.proxy.$Proxy6
【proxy.getClass().getSuperclass()】: class java.lang.reflect.Proxy
- Cat 类没有实现接口 proxy-target-class="false" / "true" 测试结果相同
【proxy.toString()】: com.it.aop.schema.Cat@3e7dd664
【proxy.getClass().getCanonicalName()】: com.it.aop.schema.Cat$$EnhancerBySpringCGLIB$$42ce28ee
【proxy.getClass().getSuperclass()】: class com.it.aop.schema.Cat
- 使用 cglib 方式创建了 proxy 类
【proxy.toString()】: com.it.aop.schema.Cat@51acdf2e
【proxy.getClass().getCanonicalName()】: com.it.aop.schema.Cat$$EnhancerBySpringCGLIB$$42ce28ee
【proxy.getClass().getSuperclass()】: class com.it.aop.schema.Cat
- 使用 cglib 方式创建的代理类是Fish类的子类
String config = "classpath*:com/it/aop/schema/aop-schema.xml";
AbstractApplicationContext iocContainer = new ClassPathXmlApplicationContext(config);
Object proxy = iocContainer.getBean("fish");
System.out.println(proxy.toString());
System.out.println(proxy.getClass().getCanonicalName());
if (proxy instanceof flyable){
flyable fish = (flyable) proxy;
fish.fly();
}
iocContainer.close();
基于jdk下的动态proxy代理测试
- 主要涉及 java.lang.reflect 包中的 proxy类 和 InvocationHandler 接口
- 通过实现 InvocationHandler(内部是匿名内部类) 定义横切逻辑,并通过反射机制调用目标类的代码
- 动态地将横切逻辑和业务逻辑编织在一起
public static void main(String[] args) {
Prompter p = new Prompter();
// 代理目标
final Object target = new Airplane();
Class<?> targetClass = target.getClass(); //获取代理目标对应的类
ClassLoader loader = targetClass.getClassLoader(); // 获取 代理目标对应的类 的 "类加载器"
Class<?>[] interfaces = targetClass.getInterfaces(); // 获取 代理目标对应的类 所实现的接口
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null ;
try {
// 方位: 方法执行前
if( "fly".equals( method.getName() ) ) { // 在所有方法执行之前 、对 方法名称进行了筛选
p.before( method.getName() ); // Advice
}
// 方位: 方法执行前***: before
result = method.invoke(target, args); // 执行点 调用 target 所引用的对象的 method 表示的方法
// 方位: 方法正常执行前***: after
// 方位: 正常返回后 ( 绝对没有发生异常 )
} catch ( Exception e ) {
// 方位: 抛出异常后
e.printStackTrace();
}
// 方位: 方法执行后
if( "fly".equals( method.getName() ) ) { // 在所有方法执行之后 、对 方法名称进行了筛选
p.after( method.getName() ); // Advice
}
return result;
}
};
// 代理对象
Object proxy = Proxy.newProxyInstance( loader , interfaces , handler );
String s = proxy.toString(); // handler.invoke
System.out.println( s );
if( proxy instanceof Flyable ){
Flyable f = (Flyable) proxy;
f.fly();
}
}
- 产生的代理类以及代理类的父类测试结果
- 在基于JDK下代理,代理类的父类是 java.lang.reflect 类的子类
【proxy】: com.it.proxy.Factory@77b52d12
【proxy.getClass().getCanonicalName()】: com.sun.proxy.$Proxy0
【proxy.getClass().getSuperclass()】: class java.lang.reflect.Proxy
对于proxy代理类 内部的实现进行 猜想
```java
public interface flyable {
void fly();
}
public class Proxy {
protected InvocationHandler h ;
protected Proxy( InvocationHandler h ) {
this.h = h ;
}
//......
}
// 注意这里不考虑异常处理
public class $Proxy0 extends Proxy implements Nubia {
private static Method m0 ;
private static Method m1 ;
private static Method m2 ;
private static Method m3 ;
static {
Class<?> objectClass = Class.forName( "java.lang.Object" ) ;
m0 = objectClass.getDeclaredMethod( "equals" , Object.class );
m1 = objectClass.getDeclaredMethod( "toString" ) ;
m2 = objectClass.getDeclaredMethod( "hashCode" ) ;
Class<?> flyClass = Class.forName( "包名.flyable" ) ;
m3 = flyClass.getDeclaredMethod( "produce" );
}
public $Proxy0( InvocationHandler handler ){
super( handler );
}
public final boolean equals(Object another) {
Object[] args = { another };
return this.h.invoke( this , m0, args ); // m0 ==> equals
}
public final String toString() {
// 调用 InvocationHandler 实例的 invoke 方法时需要传入 三个参数:
// 第一个参数表示 代理对象 ( 当前对象 this 就是 代理对象 )
// 第二个参数表示 正在执行的方法 ( 就是 与当前的 toString 同名的方法 )
// 第三个参数表示 正在执行的方法 需要传入的参数 ( 如果没有参数,可以传一个 空数组 )
Object[] args = { };
return this.h.invoke( this , m1 , args ); // m1 ==> toString
}
public final int hashCode() {
Object[] args = { };
return this.h.invoke( this , m2, args ); // m2 ==> hashCode
}
public final Phone produce(){
Object[] args = { };
return this.h.invoke( this , m3, args ); // m3 ==> produce
}
}
- 在 ServiceAspect 类中通过 方法和注解 结合使用 定义 切点 , 方位 , Advice
[@Component](https://my.oschina.net/u/3907912)
[@Aspect](https://my.oschina.net/aspect)
public class ServiceAspect {
@Pointcut("execution(* com.it.aop.annotation.service.StudentService.save(..))")
public void servicePointcut(){
}
@Before("servicePointcut()")
public void before(JoinPoint joinPoint){
System.out.println("【"+joinPoint.getSignature().getName()+"】方法即将执行");
}
@After("servicePointcut()")
public void after(JoinPoint joinPoint){
System.out.println("【"+joinPoint.getSignature().getName()+"】方法即将结束");
}
@Around("servicePointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
String name = proceedingJoinPoint.getSignature().getName();
System.out.println( "开始为" + name + "计时" );
long begin = System.nanoTime();
Object result = proceedingJoinPoint.proceed();
long end = System.nanoTime();
System.out.print( "为" + name + "计时结束," );
System.out.println( "[ " + name + " ]执行耗时 [ " + ( end - begin ) + "ns, ]" );
return result;
}
@AfterReturning(pointcut = "servicePointcut()" , returning = "xx")
public void afterReturn (JoinPoint joinPoint , Object xx){
System.out.println(joinPoint.getSignature().getName()+"方法正常返回了"+xx);
}
@AfterThrowing( pointcut = "servicePointcut()" , throwing = "ex")
public void afterThrow( JoinPoint joinPoint , Throwable ex ) {
System.out.println("【 " + joinPoint.getSignature().getName() + " 】方法执行执行时抛出 【 " + ex +" 】");
}
}