AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
[编辑](javascript:?
Aspect Oriented Programming(AOP)是较为热门的一个话题。AOP,国内大致译作“面向切面编程”。
“面向切面编程”,这样的名字并不是非常容易理解,且容易产生一些误导。有些人认为“OOP/OOD11即将落伍,AOP是新一代软件开发方式”。显然,发言者并没有理解AOP的含义。Aspect,的确是“方面”的意思。不过,汉语传统语义中的“方面”,大多数情况下指的是一件事情的不同维度、或者说不同角度上的特性,比如我们常说:“这件事情要从几个方面来看待”,往往意思是:需要从不同的角度来看待同一个事物。这里的“方面”,指的是事物的外在特性在不同观察角度下的体现。而在AOP中,Aspect的含义,可能更多的理解为“切面”比较合适。
可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。
在Spring中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
日志记录,性能统计,安全控制,事务处理,异常处理等等。
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
上面的陈述可能过于理论化,举个简单的例子,对于“雇员”这样一个业务实体进行封装,自然是OOP/OOD的任务,我们可以为其建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用AOP设计思想对“雇员”进行封装将无从谈起。
同样,对于“权限检查”这一动作片断进行划分,则是AOP的目标领域。而通过OOD/OOP对一个动作进行封装,则有点不伦不类。
换而言之,OOD/OOP面向名词领域,AOP面向动词领域。
很多人在初次接触 AOP 的时候可能会说,AOP 能做到的,一个定义良好的 OOP 的接口也一样能够做到,我想这个观点是值得商榷的。AOP和定义良好的 OOP 的接口可以说都是用来解决并且实现需求中的横切问题的方法。但是对于 OOP 中的接口来说,它仍然需要我们在相应的模块中去调用该接口中相关的方法,这是 OOP 所无法避免的,并且一旦接口不得不进行修改的时候,所有事情会变得一团糟;AOP 则不会这样,你只需要修改相应的 Aspect,再重新编织(weave)即可。 当然,AOP 也绝对不会代替 OOP。核心的需求仍然会由 OOP 来加以实现,而 AOP 将会和 OOP 整合起来,以此之长,补彼之短。
(https://upload-images.jianshu.io/upload_images/15384450-d0ef6c94dc19f331.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)
##动态代理+反射
代理的方式:1.静态代理 2.JDK动态代理 3.Cglib Spring AOP所使用的动态代理方式
CGLIB中的动态代理是JDK proxy的一个很好的补充,在JDK中实现代理时,要求代理类必须是继承接口的类,因为JDK最后生成的proxy class其实就是实现了被代理类所继承的接口并且继承了java中的Proxy类,通过反射找到接口的方法,调用InvocationHandler的invoke 方法实现拦截。CGLIb中最后生成的proxy class是一个继承被代理类的class,通过重写被代理类中的非final的方法实现代理。总结为:
JDK proxy:代理类必须实现接口
CGLIB: 代理类不能是final,代理的方法也不能是final(继承限制)
1、静态代理
静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
2、静态代理简单实现
根据上面代理模式的类图,来写一个简单的静态代理的例子。我这儿举一个比较粗糙的例子,假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,
班长就是学生的代理。
首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。
/**
* 创建Person接口
* @author Gonjan
*/
public interface Person {
//上交班费
void giveMoney();
}
Student类实现Person接口。Student可以具体实施上交班费的动作。
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}
StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。
/**
* 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为
* @author Gonjan
*
*/
public class StudentsProxy implements Person{
//被代理的学生
Student stu;
public StudentsProxy(Person stu) {
// 只代理学生对象
if(stu.getClass() == Student.class) {
this.stu = (Student)stu;
}
}
//代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
stu.giveMoney();
}
}
下面测试一下,看如何使用代理模式:
public class StaticProxyTest {
public static void main(String[] args) {
//被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
Person zhangsan = new Student("张三");
//生成代理对象,并将张三传给代理对象
Person monitor = new StudentsProxy(zhangsan);
//班长代理上交班费
monitor.giveMoney();
}
}
运行结果:
这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。
代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到
可以看到,只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。
java.lang.reflect
接口 InvocationHandler
1.新建一个Dog.java
package com.foreknow.proxy;
public interface Dog {
public void info();
public void run();
}
2.DogImpl.java 实现类
package com.foreknow.proxy;
/**
* 目标对象
* @author Administrator
*
*/
public class DogImpl implements Dog{
@Override
public void info() {
System.out.println("info....");
}
@Override
public void run() {
System.out.println("run....");
}
}
3.DogInterceptor.java 常见一个拦截器 在目标对象前后添加
package com.foreknow.proxy;
/**
* 拦截器
* 拦截某一个方法之前或者之后的操作方法
* @author Administrator
*
*/
public class DogInterceptor {
public void method1(){
System.out.println("method1");
}
public void method2(){
System.out.println("method2");
}
}
package com.foreknow.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyHandler implements InvocationHandler{
//目标对象 相当于切入点
Object target;
DogInterceptor di=new DogInterceptor();
public void setTarget(Object target) {
this.target = target;
}
@Override
//Method method 参数 我想拦截这个方法 首先我的获取这个方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//首先得获得切入点 然后在这个切入点前 后 操作
//第一 获取到的目标对象 第二个 是参数
if(method.getName().equals("info")){
di.method1();
method.invoke("target", args);//调用info方法
di.method2();
}else{
method.invoke("target", args);//否则调用run方法
}
return null;
}
}
5.创建一个代理工厂MyproxyFacotory.java
package com.foreknow.proxy;
import java.lang.reflect.Proxy;
/**
* 创建一个工厂类
* Proxy是动态在程序运行是生成的类 当然 Proxy不会代替你去做实际的工作,是由动态代理ProxyHandler完成实际的工作
* @author Administrator
*
*/
public class MyproxyFacotory {
public static Object getProxy(Object obj){
ProxyHandler h= new ProxyHandler();
//设置目标对象
h.setTarget(obj);
//一个 是代理类的对象 也就是dogimpl的对象getClassLoader()类的加载器
//第二个参数 获取代理类的接口
//获取核心代理类的对象 new 出来的H
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), h);
}
}
6.测试类Test.java
package com.foreknow.proxy;
public class Test {
public static void main(String[] args) {
//创建代理的对象
Dog dogTarger=new DogImpl();
Object proxy=MyproxyFacotory.getProxy(dogTarger);
Dog dog=null;
//instanceof 看看是否属于Dog这个接口
if(proxy instanceof Dog){
dog =(Dog) proxy;
}
dog.info();
dog.run();
}
}
输出结果
method1
info....
method2
run....