AOP(Aspect Oriented Programming)
面向切面编程,主要解决的是从左到右的问题;而面向对象主要解决的是从上到下的问题,在学习AOP之前必须先学习代理模式–>jdk与cglib代理
- jdk动态代理
jdk动态代理只能代理接口
//接口类
public interface Person{
public void eat();
public void sleep();
public void drink(String what);
public int getAge();
}
//实现类
public class Tom implements Person{
public void eat(){
System.out.println("tom is eating");
}
public void sleep(){
System.out.println("tom is sleeping");
}
public void drink(String what){
System.out.println("tom love" + what);
}
public int getAge(){
return 18;
}
}
//工厂类
public class PersonFactory{
public static Person create(){
return new Tom();
}
}
//测试端
public class Test{
public static void main(String[] args){
Person person = PersonFactory.create();
person.eat();
person.sleep();
}
}
//以上代码没有二异性,直接会打印出:tom is eating 、tom is sleeping
//现在想改变tom的生活,在代码中实体类Tom和测试端可以视为many方,不应该发生改变,能变的只能是工厂类
//现在给tom请一个经纪人,在tom做出任何动作之前,先经过经纪人,即经纪人决定着tom做任何事情,但是最终做事的还是tom
//此经纪人必须实现一个接口
public class Middleman implements InvocationHandler{
Person person;
public Middleman(Person person){
this.person = person;
}
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
return null;
}
}
//而此时工厂类里不应该返回tom,而应该返回由Middleman代理的tom,如下:
public class PersonFactory{
public static Person create(){
Person person = new Tom();
Middleman middleman = new Middleman();
Person newPerson = (Person)Proxy.newProxyInstance(middleman.getClass().getClassLoder(),person.getClass().getInterfaces(),middleman)
//上面这行代码运行完后,所产生的效果就是通知JVM的ClassLoder在加载Person接口类时,先加载Middleman类
return newPerson;
}
}
//此时就可以在tom执行动作之前,做些其他的事情了:
public class Middleman implements InvocationHandler{
Person person;
public Middleman(Person person){
this.person = person;
}
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
if(method.getName().equals("eat")){
System.out.println("wash your hands");
}
Ojbect result = method.invoke(person,args);
return result;
}
}
//当请求分发到代理对象Middleman时,会自动执行middleman.invoke(),这里的invoke()方法就是用来做N多事的地方
//上面的代码运行结果是:wash your hands、tom is eating 、tom is sleeping
//此时Tom实现类里的代码未发生任何变化 ,而此时tom的行为已发生了改变
还可以更改入参:如drink()方法里的参数
//在测试端main方法中调用
drink("beer");
//在Middleman的invoke()里更改入参
if(method.getName().equals("drink")){
args[0]= "water";
}
//那么此测试端打印出的就应该会是tom love water
还可以更改方法的返回值:
//在Middleman的invoke()里更改入参
if(method.getName().equals("getAge")){
return 10;
}
//在测试端main方法中打印
System.out.println(person.getAge());
//应该就会打印出10;
代理模式的好处就是不用更改源码,把对方所有行为都可以改变
以上的jdk动态代理,看上去功能是很不错,但是它有以下几点弊端:
- 只能代理接口
- 代理者和被代理者必须要有依赖关系(在Middleman类的构造函数中初始化被代理者Person)
- 需要更改工厂
基于以上几点弊端,Spring提供了cblib代理来完善,先看以下代码:
//实体类不用实现接口,就可以直接被代理
public class Tom{
public void eat(){
System.out.println("tom is eating");
}
public void sleep(){
System.out.println("tom is sleeping");
}
}
//此经纪人必须实现一个接口
public class Middleman implements MethodInterceptor{
public Object invoke(MethodInvocation arg0) throws Throwable{
//在实体类执行之前做些事情
System.out.println("do somthing before tom's action");
if(arg0.getMethod().getName().equals("eat")){
System.out.println("wash your hands please");
return null;
}
Object obj = arg0.proceed();
//在实体类执行之后做些事情
System.out.println("do something after tom's action");
return obj;
}
}
//工厂类
public class TomFactory{
public static Tom create(){
Tom tom = new Tom();
Middleman middleman = new Middleman();
ProxyFactory pf = new ProxyFactory();
pf.setTarget(tom);//要代理谁
pf.setAdvice(middleman);//代理者
return (Tom)pf.getProxy();
}
}
//测试端 先不发生变化
public class Test{
public static void main(String[] args){
Tom tom = TomFactory.create();
tom.eat();
tom.sleep();
}
}
小结:以上是MethodInterceptor(环绕通知)的示例,它可以做的事情如下:
- 在目标行为执行之前,之后做行为动作
- 可以更改入参,返回值
- 阻止方法的执行!
这种写法相较于之前jdk的代理有一定的优化,但是它还需要更改工厂,还需要进一步的优化,可以把ProxyFactory类放到Spring.xml中管理,接下来介绍MethodBeforeAdvice(前置通知)、AfterReturningAdvice(后置通知)
- MethodBeforeAdvice 在目标行为执行之前,做行为动作,不能更改入参,不能更改方法的任何行为,但是你可以抛出异常!抛出异常也能阻止方法运行!
- AfterReturningAdvice 在目标行为执行之后,做行为动作,不能更改方法的任何内容!
//此时的经纪人1
public class Middleman implements MethodBeforeAdvice{
public void before(Method arg0,Object[] arg1,Object arg2) throws Throwable{
System.out.println(arg0.getName()+"before");
}
}
//此时的经纪人2
public class Middleman2 implements AfterReturningAdvice{
public void afterReturning(Object arg0,Method arg1,Object[] arg2,Object arg3) throws Throwable{
System.out.println(arg0.getName()+"after");
}
}
//此时把Tom和Middleman全交由spring.xml管理,在spring.xml中
<bean id="tom" class="com.test.Tom"></bean>
<bean id="middleman" class="com.test.Middleman"></bean>
<bean id="middleman2" class="com.test.Middleman2"></bean>
//下面让tom和middleman发生依赖关系,以前是在工厂里发生依赖
<bean id="newTom" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref local="tom" />
</property>
<property name="interceptorNames">//这里用的是复数,即说明代理者可以是多个
<list>
<value>middleman</value>
<value>middleman2</value>
</list>
</property>
</bean>
//此时的测试端
public class Test{
public static void main(String[] args){
Resource resource = new ClassPathResource("spring.xml");
BeanFactory bf = new XmlBeanFactory(resource);
Tom tom = (Tom)bf.getBean("newTom");
tom.eat();
tom.sleep();
}
}
//此时打印出来的结果应该是:
eat before
tom is eating
eat after
sleep before
tom is sleeping
sleep after
代码写到这里,基本上有一个方向,将来可以代理所有的biz类,在biz类开启之前,用前置通知开启事务,在biz类执行完后,所有代码提交事务,关闭连接
但是目前的代码还有缺陷,因为目前只能一次代理一个类,未完待续….