声明计算器接口Calculator,包含加减乘除的抽象方法
package com.rgf.controller;
public interface Calculator {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
1.2创建实现类
package com.rgf.controller;
public class CalculatorPureImpl implements Calculator{
@Override
public int add(int i, int j) {
int result=i+j;
System.out.println("方法内部 result="+result);
return result;
}
@Override
public int sub(int i, int j) {
int result=i-j;
System.out.println("方法内部 result="+result);
return result;
}
@Override
public int mul(int i, int j) {
int result=i*j;
System.out.println("方法内部 result="+result);
return result;
}
@Override
public int div(int i, int j) {
int result=i/j;
System.out.println("方法内部 result="+result);
return result;
}
}
1.3创建带日志功能的实现类:
package com.rgf.spring.proxy;
public class CalculatorImpl implements Calculator{
@Override
public int add(int i, int j) {
System.out.println("日志,方法:add,参数:"+i+","+j);
int result=i+j;
System.out.println("方法内部,result:"+result);
System.out.println("日志,方法:add,结果:"+result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("日志,方法:sub,参数:"+i+","+j);
int result=i-j;
System.out.println("方法内部,result:"+result);
System.out.println("日志,方法:sub,结果:"+result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("日志,方法:mul,参数:"+i+","+j);
int result=i*j;
System.out.println("方法内部,result:"+result);
System.out.println("日志,方法:mul,结果:"+result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("日志,方法:div,参数:"+i+","+j);
int result=i/j;
System.out.println("方法内部,result:"+result);
System.out.println("日志,方法:div,结果:"+result);
return result;
}
}
我们发现该代码与核心代码混合起来了,在核心业务里面混进来一些非核心业务代码
我们针对带日志功能的实现类,我们发现有如下缺陷:
1.对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
2.附加功能分散在各个业务功能方法中,不利于统一维护
解决思路:
解耦,我们需要把附加功能从业务功能代码中抽取出来
1.2代理模式
二十三种设计模式中的一种,属于结构型模式,它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再直接对目标方法进行调用,而是通过代理类间接调用,让不属于目标方法核心逻辑的代码从目标方法中剥离出来--解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
即为对目标对象然后创建一个她所对应的代理类,代理对象,每一次在访问目标对象的时候,有了代理模式之后,通过代理对象间接的去访问目标对象,然后在代理对象中会直接调用目标对象实现功能的过程,在代理对象中控制目标方法的执行,可以在目标方法执行的前后和执行的过程中去加入一些额外的代码。(核心业务代码在目标对象中,非核心业务代码在代理对象中)
生活中的代理:
1.广告商找大明星拍广告需要经过经纪人
2合作伙伴找大老板谈合作要约见面时间需要通过秘书
3.房产中介是买卖双方的代理
代理:将非核心业务逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
目标:被代理'‘套用”了非核心逻辑代码的类、对象、方法。
我们查看代码如下所示:
package com.rgf.spring.proxy;
public class CalculatorStaticProxy implements Calculator{
private CalculatorImpl target;
public CalculatorStaticProxy(CalculatorImpl target) {
this.target = target;
}
@Override
public int add(int i, int j) {
int result=0;
try {
System.out.println("日志,方法:add,参数:" + i + "," + j);
result = target.add(i, j);
System.out.println("日志,方法:add,结果:" + result);
}catch (Exception e){
e.printStackTrace();
}finally {
}
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("日志,方法:add,参数:"+i+","+j);
int result = target.sub(i, j);
System.out.println("日志,方法:add,结果:"+result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("日志,方法:add,参数:"+i+","+j);
int result = target.mul(i, j);
System.out.println("日志,方法:add,结果:"+result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("日志,方法:add,参数:"+i+","+j);
int result = target.div(i, j);
System.out.println("日志,方法:add,结果:"+result);
return result;
}
}
代理类我们·进行测试:
package com.rgf.proxy;
import com.rgf.spring.proxy.CalculatorImpl;
import com.rgf.spring.proxy.CalculatorStaticProxy;
import org.junit.Test;
public class ProxyTest {
@Test
public void testProxy(){
CalculatorStaticProxy proxy=new CalculatorStaticProxy(new CalculatorImpl());
proxy.add(1,2);
}
}
我们进行运行之后如下所示:
我们发现通过代理类将核心业务代码和非核心业务代码分开了,可以增强代码的适用性。
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。例如如上的日志功能,将来其他地方也需要附加日志,还得再声明多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
通过jdk提供给我们的一些方法api,然后动态的为某一个目标类来创建他所对应的动态代理类(动态去生成目标类所对应的代理类)
我们的接口如下所示:
package com.rgf.spring.proxy; public interface Calculator { int add(int i,int j); int sub(int i,int j); int mul(int i,int j); int div(int i,int j); }
我们的接口的实现类如下所示:
package com.rgf.spring.proxy; public class CalculatorImpl implements Calculator{ @Override public int add(int i, int j) { int result=i+j; System.out.println("方法内部,result:"+result); return result; } @Override public int sub(int i, int j) { int result=i-j; System.out.println("方法内部,result:"+result); return result; } @Override public int mul(int i, int j) { int result=i*j; System.out.println("方法内部,result:"+result); return result; } @Override public int div(int i, int j) { int result=i/j; System.out.println("方法内部,result:"+result); return result; } }
我们的动态代理类如下所示:
package com.rgf.spring.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Array; import java.util.Arrays; /** * 帮助我们动态的生成目标类的代理类的工具类 */ public class ProxyFactory { private Object target; public ProxyFactory(Object target) { this.target = target; } /** * 此方法会帮助我们动态的生成一个代理类,类想要被执行就需要被加载,类的加载需要经过类加载器的加载 * ClassLoader loader(类加载器):指定加载动态生成的代理类的类加载器 * (类加载器: * 根类加载器:底层是C来实现的。主要加载当前的一些核心类库 * 扩展类加载器:加载的是扩展类库 * 应用类加载器:加载当前自己所写的类,或者引入的第三方jar包里面的类(获取当前某个类型的class对象,直接.getClassloader获取应用类加载器 * 自定义类加载器) *Class[] interfaces():获取目标对象实现的所有接口的class对象的数组 *InvocationHandler h():设置代理类中的抽象方法如何重写 * @return */ public Object getProxy(){ ClassLoader classLoader = this.getClass().getClassLoader(); Class>[] interfaces = target.getClass().getInterfaces(); InvocationHandler h = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("日志,方法"+method.getName()+","+ Arrays.toString(args)); //proxy表示代理对象,method表示要执行的方法,args表示要执行的方法的参数列表 Object result = method.invoke(target, args); System.out.println("日志,方法"+method.getName()+",结果"+result); return result; } }; return Proxy.newProxyInstance(classLoader,interfaces,h); } }
测试类如下所示:
package com.rgf.proxy; import com.rgf.spring.proxy.Calculator; import com.rgf.spring.proxy.CalculatorImpl; import com.rgf.spring.proxy.CalculatorStaticProxy; import com.rgf.spring.proxy.ProxyFactory; import org.junit.Test; public class ProxyTest { @Test public void testProxy(){ ProxyFactory proxyFactory=new ProxyFactory(new CalculatorImpl()); Calculator proxy =(Calculator) proxyFactory.getProxy(); proxy.add(1,2); } }
运行之后如下所示:
我们可以在动态代理里面实现其他方法:
package com.rgf.spring.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Array; import java.util.Arrays; /** * 帮助我们动态的生成目标类的代理类的工具类 */ public class ProxyFactory { private Object target; public ProxyFactory(Object target) { this.target = target; } /** * 此方法会帮助我们动态的生成一个代理类,类想要被执行就需要被加载,类的加载需要经过类加载器的加载 * ClassLoader loader(类加载器):指定加载动态生成的代理类的类加载器 * (类加载器: * 根类加载器:底层是C来实现的。主要加载当前的一些核心类库 * 扩展类加载器:加载的是扩展类库 * 应用类加载器:加载当前自己所写的类,或者引入的第三方jar包里面的类(获取当前某个类型的class对象,直接.getClassloader获取应用类加载器 * 自定义类加载器) *Class[] interfaces():获取目标对象实现的所有接口的class对象的数组 *InvocationHandler h():设置代理类中的抽象方法如何重写 * @return */ public Object getProxy(){ ClassLoader classLoader = this.getClass().getClassLoader(); Class>[] interfaces = target.getClass().getInterfaces(); InvocationHandler h = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; //ctrl+alt+T,选择进行补充。 try { System.out.println("日志,方法"+method.getName()+","+ Arrays.toString(args)); //proxy表示代理对象,method表示要执行的方法,args表示要执行的方法的参数列表 result = method.invoke(target, args); System.out.println("日志,方法"+method.getName()+",结果"+result); } catch (Exception e){ e.printStackTrace(); System.out.println("日志,方法"+method.getName()+",异常"+e); } finally { System.out.println("日志,方法"+method.getName()+",方法执行完毕"); } return result; } }; return Proxy.newProxyInstance(classLoader,interfaces,h); } }
我们继续进行测试如下所示:
package com.rgf.proxy; import com.rgf.spring.proxy.Calculator; import com.rgf.spring.proxy.CalculatorImpl; import com.rgf.spring.proxy.CalculatorStaticProxy; import com.rgf.spring.proxy.ProxyFactory; import org.junit.Test; public class ProxyTest { /** * 动态代理有两种: * 1.jdk动态代理,要求必须有接口,最终生成的代理类和目标类实现相同的接口,在com.sun.proxy包下,类名为$proxy2 * 2.cglib动态代理,最终生成的代理类会继承目标类,并且和目标类在相同的包下 */ @Test public void testProxy(){ ProxyFactory proxyFactory=new ProxyFactory(new CalculatorImpl()); Calculator proxy =(Calculator) proxyFactory.getProxy(); proxy.add(1,2); proxy.div(1,0); } }
我们运行之后如下所示:
AOP是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
(1)横切关注点:(针对目标对象)
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
(2)通知(针对切面)
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法
前置通知:在被代理的目标方法前执行
返回通知:在被代理的目标方法成功结束后执行
异常通知:在被代理的目标方法异常结束后执行
后置通知:在被代理的目标方法最终结束后执行
环绕通知:使用try-catch-finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
(3)切面
封装通知方法的类。
(4)目标
被代理的目标对象
(5)代理
向目标对象应用通知之后创建的代理对象
(6)连接点(抽取横切关注点的位置)
这也是一个纯逻辑概念,不是语法定义的
把方法排成一排,每一个横切位置看出x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。
(7)切入点
定位连接点的方式
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)
如果把连接点看作数据库中的记录,那么切入点就是查询记录的SQL语句。
Spring的AOP技术可以通过切入点定位到特定的连接点
切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件
我们在类中找到我们要抽取出来的代码,即为横切关注点,把抽取出来的放到一起即为切面,我们在切面中封装横切关注点,每个横切关注点为通知。我们抽取出来之后同时要利用切入点套到我们要连接的位置。
AOP的作用:
简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。