今天记录一下春天(Spring)的AOP,从以下几个部分介绍:
- 代理模式
- Java注解(Annotation)
- AOP中的Java动态代理两种方式
- AOP中的术语与应用
AOP为Aspect Oriented Programming,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。降低程序业务之间的耦合程度,提高可维护性与开发效率。里面的基础知识就是Java代理与注解。最后就是AOP相关的术语与应用。
代理模式
之前在Android源码中也经常遇到代理模式,比如Binder进程间通信,使用AIDL过程中,当前线程传递数据会判断是否需要跨进程,如果需要跨进程,此时封装数据为Parcelable对象,然后再执行transact函数,而此时AIDL中封装的代理完成了这些操作。类似下面例子:
//吃东西的接口
public interface Eat {
void eat();
}
//吃苹果类,实现吃的接口
public class EatApple implements Eat {
@Override
public void eat() {
System.out.println(">>>>吃掉苹果");
}
}
//洗苹果工具类
public class WashApple {
public static void washAnApple() {
System.out.println("先洗洗苹果");
}
}
//代理类,洗完了再安全的吃。
public class EatAppleProxy {
EatApple eatApple;
public EatAppleProxy() {
eatApple = new EatApple();
}
//健康的吃苹果
public void eatApple() {
//先把苹果洗干净
WashApple.washAnApple();
//吃
eatApple.eat();
}
}
我们只需要调用代理类,EatAppleProxy就可以了,而不需要自己再去写其中的过程。
Java注解(Annotation)
注解(Annotation) 为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后某个时刻方便地使用这些数据,Annotation(注解)是JDK1.5及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。简单点讲就是给一些代码添加标记,便于后续对添加统一标记的代码做处理。
学习注解,最好学会自定义注解,这样能更好理解注解原理。
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
- 1@Target,
- 2@Retention,
- 3@Documented,
- 4@Inherited
@Target
说明了Annotation所修饰的对象范围,取值(ElementType)有:
- CONSTRUCTOR:用于描述构造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部变量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述参数
- TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Retention
定义了该Annotation被保留的时间长短
- 在源文件中有效(即源文件保留)
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在运行时有效(即运行时保留)
@Documented
用于生成Java的Doc。
@Inherited
元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
如何自定义注解:
/*
* 定义注解 Test
* 为方便测试:注解目标为类 方法,属性及构造方法
* 注解中含有三个元素 id ,name和 gid;
* id 元素 有默认值 0
*/
@Target({TYPE,METHOD,FIELD,CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestA {
String name();
int id() default 0;
Class<Long> gid();
}
使用自定义注解
/**
* 这个类专门用来测试注解使用
* @author tmser
*/
@TestA(name="type",gid=Long.class) //类成员注解
public class UserAnnotation {
@TestA(name="param",id=1,gid=Long.class) //类成员注解
private Integer age;
@TestA (name="construct",id=2,gid=Long.class)//构造方法注解
public UserAnnotation(){
}
@TestA(name="public method",id=3,gid=Long.class) //类方法注解
public void a(){
Map<String,String> m = new HashMap<String,String>(0);
}
@TestA(name="protected method",id=4,gid=Long.class) //类方法注解
protected void b(){
Map<String,String> m = new HashMap<String,String>(0);
}
@TestA(name="private method",id=5,gid=Long.class) //类方法注解
private void c(){
Map<String,String> m = new HashMap<String,String>(0);
}
public void b(Integer a){
}
}
** 最重要的,获取注解上的内容**:java1.5中增加注解,也同样增加了读取注解的方法,在java.lang.reflect包中新增了AnnotatedElement接口,JDK源码如下:
public interface AnnotatedElement {
//判断是否标注了指定注解
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
//获取指定注解,没有则返回null
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
//获取所有注解,包括继承自基类的,没有则返回长度为0的数组
Annotation[] getAnnotations();
//获取自身显式标明的所有注解,没有则返回长度为0的数组
Annotation[] getDeclaredAnnotations();
}
请看测试例子:
public class ParseAnnotation {
/**
* 简单打印出UserAnnotation 类中所使用到的类注解
* 该方法只打印了 Type 类型的注解
* @throws ClassNotFoundException
*/
public static void parseTypeAnnotation() throws ClassNotFoundException {
Class clazz = Class.forName("com.tmser.annotation.UserAnnotation");
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
TestA testA = (TestA)annotation;
System.out.println("id= \""+testA.id()+"\"; name= \""+testA.name()+"\"; gid = "+testA.gid());
}
}
/**
* 简单打印出UserAnnotation 类中所使用到的方法注解
* 该方法只打印了 Method 类型的注解
* @throws ClassNotFoundException
*/
public static void parseMethodAnnotation(){
Method[] methods = UserAnnotation.class.getDeclaredMethods();
for (Method method : methods) {
/*
* 判断方法中是否有指定注解类型的注解
*/
boolean hasAnnotation = method.isAnnotationPresent(TestA.class);
if (hasAnnotation) {
/*
* 根据注解类型返回方法的指定类型注解
*/
TestA annotation = method.getAnnotation(TestA.class);
System.out.println("method = " + method.getName()
+ " ; id = " + annotation.id() + " ; description = "
+ annotation.name() + "; gid= "+annotation.gid());
}
}
}
/**
* 简单打印出UserAnnotation 类中所使用到的方法注解
* 该方法只打印了 Method 类型的注解
* @throws ClassNotFoundException
*/
public static void parseConstructAnnotation(){
Constructor[] constructors = UserAnnotation.class.getConstructors();
for (Constructor constructor : constructors) {
/*
* 判断构造方法中是否有指定注解类型的注解
*/
boolean hasAnnotation = constructor.isAnnotationPresent(TestA.class);
if (hasAnnotation) {
/*
* 根据注解类型返回方法的指定类型注解
*/
TestA annotation =(TestA) constructor.getAnnotation(TestA.class);
System.out.println("constructor = " + constructor.getName()
+ " ; id = " + annotation.id() + " ; description = "
+ annotation.name() + "; gid= "+annotation.gid());
}
}
}
public static void main(String[] args) throws ClassNotFoundException {
parseTypeAnnotation();
parseMethodAnnotation();
parseConstructAnnotation();
}
}
Java动态代理
Java动态代理分为JDK动态代理和CGLib动态代理。Java作为一门静态语言,不像JavaScript、php、python等具有标准的动态特性。但Java逐渐加入了一些特性来支持动态特性,主要包括:反射机制、动态编译、动态字节码操作、动态类型转换 等。
JDK动态代理
基于JDK的反射(reflect)机制;在JDK中,提供了InvocationHandler这个接口。源码中解释如下:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
该接口由被代理对象的handler所实现;当调用代理对象的方法时,该方法调用将被编码,然后交给代理对象的invoke方法去执行。
具体使用:
public class ProxyFactory implements InvocationHandler {
private Object delegate;
public Object bind(Object delegate){
this.delegate= delegate;
return Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
delegate.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前期处理
WashApple.washAnApple();
Object result=null;
try {
//调用原类中的方法
result=method.invoke(delegate, args);
} catch (Exception e) {
// TODO: handle exceptions
}
return result;
}
}
CGLib代理方式
一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。可见CGLib是从字节码层改变Java执行的。具体使用例子如下:
public class CglibProxyFactory implements MethodInterceptor{
private Object delegate;
public Object bind(Object delegate){
this.delegate=delegate;
Enhancer enhancer= new Enhancer();
enhancer.setSuperclass(delegate.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//前期处理
WashApple.washAnApple();
Object o =proxy.invoke(this.delegate, args);
PerformanceUtil.endPerformance();
return o;
}
}
很明显,这里的MethodInterceptor类似于JDK动态代理中的InvocationHandler。
AOP中的术语与应用
- 切面(Aspect):切面就是一个关注点的模块化,如事务管理、日志管理、权限管理等;
- 连接点(Joinpoint):程序执行时的某个特定的点,在Spring中就是一个方法的执行;
- 通知(Advice):通知就是在切面的某个连接点上执行的操作,也就是事务管理、日志管理等;
- 切入点(Pointcut):切入点就是描述某一类选定的连接点,也就是用来指定某一类要织入通知的方法的描述;
- 目标对象(Target):就是被AOP动态代理的目标对象;
上图表示了以上术语之间的关系。
通知类型:
- 前置通知(Before advice):在某连接点之前执行的通知,但这个通知*
不能阻止连接点之前的执行流程(除非它抛出一个异常)。 - 后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
- 异常通知(After throwing advice):在方法抛出异常退出时执行的通知。
- 最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- 环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
Spring的AOP文档里提到:
Spring目前仅支持使用方法调用作为连接点(join point)(在Spring bean上通知方法的执行)。虽然可以在不影响到Spring AOP核心API的情况下加入对成员变量拦截器支持,但Spring并没有实现成员变量拦截器。如果你需要把对成员变量的访问和更新也作为通知的连接点,可以考虑其它的语言,如AspectJ。
注意Spring的AOP和AspectJ不是一回事。Spring侧重于提供一种AOP实现和Spring IoC容器之间的整合,用于帮助解决在企业级开发中的常见问题。
Spring中使用AOP可以采用Spring特有的xml配置,或者使用AspectJ实现AOP。
Spring的xml配置
//目标类
<bean id="userServiceId" class="cn.leyue.a_aop.c_spring.UserServiceImpl"></bean>
//切面类(通知)
<bean id="myAspectId" class="cn.leyue.a_aop.c_spring.MyAspect"></bean>
<!--
创建代理类
* 使用工厂bean FactoryBean ,底层调用getObject()返回特殊bean
* ProxyFactoryBean 用于创建代理工程bean,生成特殊代理对象,
interfaces:确定接口们
通过<array>可以设置多个值
只有一个值时,value=""
target 确定目标类
interceptorNames:通知切面类的名称,类型String[],如果设置一个值 value=""
optimize:强制使用cglib
<property name="optimize" value="true"></property>
* 底层机制
如果目标类接口,采用jdk动态代理
如果没有接口,采用cglib 字节码增强
如果生命optimize=true 无论是否有接口,都采用cglib
-->
<bean id="proxyServiceId" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="cn.leyue.a_aop.c_spring.UserService"></property>
<property name="target" ref="userServiceId"></property>
<property name="interceptorNames" value="myAspectId"></property>
<property name="optimize" value="true"></property>
</bean>
目标类
public interface UserService {
void addUser();
void updateUser();
void deleteUser();
}
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("b_cglib------addUser");
}
@Override
public void updateUser() {
System.out.println("b_cglib------updateUser");
}
@Override
public void deleteUser() {
System.out.println("b_cglib------deleteUser");
}
}
切面类
/**
* 切面类中确定通知,需要实现不同接口,接口就是规范,从而确定方法名称.
* 采用"环绕通知"MethodInterceptor
*/
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("c_spring_方法之前");
//手动执行目标方法
Object obj = methodInvocation.proceed();
System.out.println("c_spring_方法之后");
return obj;
}
}
测试类
public class SpringTest {
@Test
public void spring() {
String path = "cn/leyue/a_aop/c_spring/beans.xml";
ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext(path);
UserService service = application.getBean("proxyServiceId", UserService.class);
service.addUser();
service.updateUser();
service.deleteUser();
}
}
AspectJ
这部分语法比较多,建议使用查文档即可,当然记住最好啦,传送门,AspectJ使用
This is the end, that's all, Thank U.
网友评论