图示分析:例如,在开发中普遍需要使用到日志输出功能,会将日志输出功能大量耦合到项目的不同位置,如上图左侧所示。
而日志输出功能与其实与项目本身的核心业务逻辑无关,我们只是为了不时的查看项目的运行状态。
则可以将日志功能单独提出去开发,在需要的地方将日志输出功能(所谓:日志功能切面)反织回去即可,如上图右侧所示。
整个版本1,只是一个BookService实体类,业务功能和切面功能严重耦合
package com.example.proxy01;
/**
调用BookService实体对象中的buy()方法即可
通过子类代理来实现将业务功能和切面功能初步拆分解耦
实体类BookService及其子类SubBookService
BookService实体类
package com.example.proxy02;
/**
SubBookService子类
package com.example.proxy02;
/**
调用SubBookService实体类对象中的buy()方法即可
通过静态代理,可以进行受代理对象的灵活切换
Service接口
package com.example.proxy03;
/**
BookServiceImpl实现类
package com.example.proxy03;
/**
ProductServiceImpl实现类
package com.example.proxy03;
/**
静态代理对象
package com.example.proxy03;
/**
静态代理对象
*/
public class Agent implements Service{
//接口类型的参数
Service target;
//传入接口类型的参数,灵活调用多种Service接口的实现类
public Agent(Service target){
this.target = target;
}
@Override
public void buy() {
try{
System.out.println("开启事务….");
target.buy();
System.out.println("提交事务….");
}catch (Exception e){
System.out.println("关闭事务….");
}
}
}
面向接口编程,可灵活代理多种Service接口的实现类,灵活切换受代理对象
package com.example.test;
import com.example.proxy03.Agent;
import com.example.proxy03.ProductServiceImpl;
import com.example.proxy03.Service;
import org.junit.Test;
public class TestProxy03 {
@Test
public void testProxy03(){
//可以灵活切换受代理对象,因为接口类型的参数都能接住
//Service agent = new Agent(new BookServiceImpl());
Service agent = new Agent(new ProductServiceImpl());
agent.buy();
}
}
AOP版本3中,虽然受代理对象可以灵活切换,但是不同的受代理对象被绑定到相同的切面功能,切面功能无法灵活切换
可以将上述切面功能上升到接口层次,针对不同切面功能有不同实现类
核心:就像Agent代理对象持有Service接口类型的变量一样,若持有切面接口类型的变量,则可以接收不同切面接口的实现类,实现不同切面功能的灵活切换
考虑到切面功能出现在业务功能的前后关系,以及异常处理等情况,可以根据切面出现的时机定义切面接口中的方法
推导出需要定义切面接口以及如何定义接口中方法的思路图示
业务接口:Service接口
package com.example.proxy04;
/**
切面接口:Aop接口
package com.example.proxy04;
/**
业务功能实现类:BookServiceImpl和ProductServiceImpl
package com.example.proxy04;
/**
package com.example.proxy04;
public class ProductServiceImpl implements Service {
@Override
public void buy() {
System.out.println("产品生产业务….");
}
}
切面功能实现类:TransAopImpl和LogAopImpl
package com.example.proxy04;
public class TransAopImpl implements Aop{
@Override
public void before() {
System.out.println("开启事务….");
}
@Override
public void after() {
System.out.println("提交事务....");
}
@Override
public void exception() {
System.out.println("回滚事务....");
}
}
package com.example.proxy04;
//切面接口中定义方法时使用了default,不必实现所有接口方法,按需实现方法即可
public class LogAopImpl implements Aop{
@Override
public void before() {
System.out.println("前置日志输出….");
}
}
可以实现业务功能和切面功能的灵活组合
而且就像下面第2个测试一样,因为代理对象也是Service的一个实现类,所以代理对象还可以再次被代理,一个业务功能被多个切面包围,实现多切面
此时的版本已经很灵活,也已经揭示出了AOP面向切面编程的核心
package com.example.test;
import com.example.proxy04.*;
import org.junit.Test;
public class TestProxy04 {
//测试:单个业务功能 + 单个切面功能
@Test
public void testProxy04(){
//分别传入要完成的业务功能和要切入的功能,可以灵活组合,这里就可以有业务功能和切面功能的4种组合:2 x 2
//Service agent = new Agent(new ProductServiceImpl(), new LogAopImpl());
//Service agent = new Agent(new ProductServiceImpl(), new TransAopImpl());
//Service agent = new Agent(new BookServiceImpl(), new LogAopImpl());
Service agent = new Agent(new BookServiceImpl(), new TransAopImpl());
agent.buy();
}
//测试:单个业务功能 + 多个切面功能(本例为:日志切面 + 事务切面)
@Test
public void testManyProxies(){
Service agent = new Agent(new ProductServiceImpl(), new LogAopImpl());
Service agent2 = new Agent(agent, new TransAopImpl());
agent2.buy();
}
}
静态代理可以做到受代理对象的灵活切换,但是不可以做到代理功能的灵活切换,就像我们用动态代理优化静态代理一样,还可以用jdk动态代理继续优化上述AOP版本4
在Spring原生的AOP框架中,底层就是使用的jdk动态代理,AOP版本5最接近Spring原生AOP框架
AOP版本5中除了用ProxyFactory代理工厂来动态获取代理对象外(不再写AOP版本4中的Agent类,4版本是静态的,现在不用写了),其他接口和实现类与AOP版本4完全一致,不再赘述
新增ProxyFactory类,代替AOP版本4中的Agent类
参数比较多,看起来有些乱(包涵 包涵),若对jdk动态代理不是很熟悉,可以参考mybatis博客集(mybatis 01 对jdk动态代理有详细讨论)
package com.example.proxy05;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
代理工厂,获取动态代理对象
*/
public class ProxyFactory {
//静态方法获取jdk动态代理对象:传入业务功能对象 + 切面功能对象
public static Object getProxy(Service target, Aop aop){
//该方法有三个参数,第三个参数是一个匿名内部类
return Proxy.newProxyInstance(
//参数1
target.getClass().getClassLoader(),
//参数2
target.getClass().getInterfaces(), //参数3:匿名内部类重写的方法又有三个参数
//其中method用来反射调用外部调用的那个方法,args是调用目标方法时要传的参数
new InvocationHandler() {
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
//用来存放目标对象被调用的方法的返回值
Object res = null;
try{
//切面功能
aop.before();
//业务功能,根据外部调用的功能,动态代理目标对象被调用的方法
res = method.invoke(target, args);
//切面功能
aop.after();
}catch (Exception e){
//切面功能
aop.exception();
}
//返回目标对象被调用的方法的返回值给外部调用者
return res;
}
}
);
}
}
package com.example.test;
import com.example.proxy05.BookServiceImpl;
import com.example.proxy05.ProxyFactory;
import com.example.proxy05.Service;
import com.example.proxy05.TransAopImpl;
import org.junit.Test;
public class TestProxy05 {
//测试:AOP版本5
@Test
public void testProxy05(){
//获取动态代理对象,传入业务功能对象和切面功能对象,这里传入的业务对象和切面对象可以有多种组合
Service agent = (Service) ProxyFactory.getProxy(new BookServiceImpl(), new TransAopImpl());
//完成业务功能和切面功能的组合
agent.buy();
}
}
开启事务….
图书购买业务….
提交事务….
Process finished with exit code 0
为了体现动态代理的优点,并测试有参数和有返回值的方法都可被代理,为Service接口扩展功能:order(预定图书的功能)
Service接口新增order方法
package com.example.proxy05;
/**
静态代理接口
*/
public interface Service {
//定义业务功能
void buy();
//新扩展一个预定功能
default String order(int orderNums){return null;}
//default,不强制实现类都实现该方法,按需实现
}
让BookServiceImpl实现该新增的方法
package com.example.proxy05;
/**
}
package com.example.test;
import com.example.proxy05.BookServiceImpl;
import com.example.proxy05.ProxyFactory;
import com.example.proxy05.Service;
import com.example.proxy05.TransAopImpl;
import org.junit.Test;
public class TestProxy05 {
@Test
public void testProxy05(){
//获取动态代理对象,传入业务功能对象和切面功能对象
Service agent = (Service) ProxyFactory.getProxy(new BookServiceImpl(), new TransAopImpl());
//完成业务功能和切面功能的组合
String res = agent.order(10);
System.out.println("返回结果: " + res);
}
}
开启事务….
新预定图书: 10 册
提交事务….
返回结果: 预定成功
Process finished with exit code 0
手机扫一扫
移动阅读更方便
你可能感兴趣的文章