Spring Aop
现在我们提起Aop编程并不陌生了,它就是面向方面(也叫面向切面)编程。
什么是面向方面编程?我们举列如果你的模块中都用到安全,事物,和日记记录等操作。如果我们按照原来的代码实现必须在每个类的每个方法中加一个调用公共模块的方法来完成对应的操作。这里不仅仅破坏了模型的完整性,给维护工作也带来困难,列如我们以后还要加一些操作因该怎么办?继续在原有的程序上加?这里循环下去整个系统就会变的很糟糕。
现在有了面向方面编程的思想,我们可以用代理类来把一些公共信息模块化,让代理类来实现来对他们的调用(这个不明白在我们以下的讲解中会说清楚)。
首先要学习Aop必须明白代理类的原理。我们下面以JDK中的Proxy来讲解下代理的原理
Jdk创建代理类的方法有2种,别个是
InvocationHandler handler = new MyInvocationHandler(...);
Class proxyClass = Proxy.getProxyClass(
Foo.class.getClassLoader(), new Class[] { Foo.class });
Foo f = (Foo) proxyClass.
getConstructor(new Class[] { InvocationHandler.class }).
newInstance(new Object[] { handler });
or more simply:
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[] { Foo.class },
handler);
2中方法都是一样的,我们以第2中方法为列进行讲解,其实代理的内部原理是通过你传入的接口给你创建出一个特殊的类,这个类实现了这个接口,但是他的每个方法都是固定的调用InvocationHandler的invoke方法。这样创建出来的对象是个代理对象,他和你实际的类没什么区别,但是在调用他的时候我们就可以通过invoke方法对他进行控制了。其实说的明白点就是代理类是对你目标类的一个复制品。
Proxy
|
代理类
|
Target
|
通过
Proxy
返回一个代理类
|
当调用代理类的时候它会调用目标对象的方法()
|
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
的第1个参数传入一个类加载器,一般都是用加载本类的类加再器,第2个参数就是这个代理类要实现的接口,也就是要用这些接口创建出一个复制对象,第3个参数是一个实现了InvocationHandler 接口的类,当代理类调用方法的时候他就会调用实现了InvocationHandler类的 invoke
(Object proxy, Method method, Object[] args)
在这里我们就可以加入一些代码了,因该代理类在执行每个方法的时候都会来执行这个方法。
public class MyInvocationHandler implements InvocationHandler
{
Student stu = new Student();
//传入目标对象
public MyInvocationHandler(Student stu)
{
this.stu = stu;
}
public synchronized Object invoke(Object proxy, Method method, Object[] args) throws Exception
{
System.out.println("Proxy haha!");
System.out.println(proxy.getClass().getName());
//调用目标对象对应的方法
return method.invoke(stu,args);
}
}
这样我们就实现了一个代理类,代理类外表和目标类一样,因为他们都实现了同一的接口,上面代码代理类的方法别调用的时候实际会去打印System.out.println("Proxy haha!");
然后调用目标对象对应的方法。
注意:上面指的目标对象也是实现了接口的, JDK的代理类只能通过接口代理,如果你的类没实现任何接口,那么必须通过一个CGLIB的框架。它的实现代理是创建目标对象的子类。如果要了解CGLIB请网上查看。
通过上面的讲解我们知道了代理类的实现内幕。现在让我们来看看Spring里的ProxyFactoryBean。要了解ProxyFactoryBean首先了了解几个概念。
切面:
切面是你要实现的交叉功能。他是应用系统模块化的一个切面或领域。切面的最常见列子就是日志记录,日志记录在系统中到处需要要用到,利用继承来重用日志模块不合适。然而,你可以创建一个日志记录的切面,并且使用APP在系统中应用。
连接点:
连接点是应用系统执行过程中插入切面的地方。这个地方可以是方法调用,异常抛出,或者甚至是要修改的字段。切面代码在这些地方插入到你的应用流程中,添加新的行为。
通知:
通知切面的实际实现。它通知应用系统新的行为。在日志列子中,日志通知包含实现实际日志功能代码,如向日志文件写日志。通知在连接点插入到应用系统中。
切入点
切入点定义了通知因该在那些连接点。通知可以应用到AOP框架支持的任何连接点。当然,你并不希望把所有切面应用到所有可能的连接点上。切入点让你指定通知应用到那些地方。通常通过指定类名和方法名,或者匹配类名和方法名式样的正则表达式来指定切入点。
<
bean
id
=
"proxy"
class
=
"org.springframework.aop.framework.ProxyFactoryBean"
>
<!--
代理类实现的接口
à
<
property
name
=
"proxyInterfaces"
>
<
list
>
<
value
>
com.maomao.spring.model.Person
</
value
>
<
value
>
com.maomao.spring.model.LastModitify
</
value
>
</
list
>
</
property
>
<!--
设置通知
à
<
property
name
=
"interceptorNames"
>
<
list
>
<!-- value>Salute</value>
<value>bye</value-->
<!-- value>mymethod</value-->
<!-- value>advice</value-->
<
value
>
lastdate1
</
value
>
</
list
>
</
property
>
<!--
设置目标对象
à
<
property
name
=
"target"
>
<
ref
bean
=
"person"
/>
</
property
>
<
property
name
=
"singleton"
>
<
value
>
false
</
value
>
</
property
>
</bean>
其中根据切入点可以把通知分为BeforeAdvice,AfterReturningAdvice,ThrowsAdvice,MethodInterceptor。它们分别是在调用方法前切入,在调用方法后切入,在抛出异常时候切入,在方法周围切入。这4个都是接口,需要自己自己实现这些接口。
举列
public class My implements MethodInterceptor {
public Object invoke(MethodInvocation method) throws Throwable {
// TODO Auto-generated method stub
System.out.println("start!");
Object obj = method.proceed();
System.out.println("end!");
return obj;
}
}
这样虽然实现了代理,但是我们并不希望所有的方法都被通知,这样我们就必须定义切入点。
Spring中切入点是PointCut 这个接口的方法我们不做具体讲解了(时间有限制,有需求请留言)。
Advisor就是切入点和通知的组合。这个也是个接口,Spring为我们提供了实现这个接口的一系列类,其中最静态切入点常用的有NameMatchMethodPointcutAdvisor,RegexpMethodPointcutAdvisor(这里需要注意的一点是正则表达式匹配的时候要匹配类名)
<bean id="namematchmethodpointcut" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<!-- 可以用通配符匹配方法名字 à
<property name="mappedName"><value>getTest</value></property>
<!-- 设置通知 -- >
<property name="advice"><ref bean="bye" /></property>
</bean>
除了静态切入点还有动态切入点,但是比静态切入点的效率低很多。因为静态切入点只在代理创建的时候执行一次而不是在运行期间每次调用方法的时候执行。
Spring AOP还有自动代理。请自己参考org.springframework.aop.framework.autoproxy. BeanNameAutoProxyCreator和他包下的一些类,如果以上的代理原理明白自动代理很简单了
最后一个小知识点就是把几个spring.xml文件一起加载可以用
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(String [] path);