黑马程序员——【Java高新技术】——代理
一、“代理概述”及“AOP概念”
(一)代理概述
1、问题:要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理等等,如何去做?
解答:编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
2、代理原理图,如下:
3、代理的优点
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换。例如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
(二)AOP概念
1、问题引入:
(1)系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
安全 事务 日志
StudentService ———|——————|——————|—————
CourseService ———|——————|——————|—————
MiscService ———|——————|——————|—————
(2)用具体的程序代码描述交叉业务:
method1 method2 method3
{ { {
------------------------------------------切面
.... .... ......
------------------------------------------切面
} } }
2、AOP概念
(1)定义:交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。
(2)可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
----------------------------------------切面
func1 func2 func3
{ { {
.... .... ....
} } }
-----------------------------------------切面
总结:(1)使用代理技术正好可以解决这种交叉业务模块化的问题,代理是实现AOP功能的核心和关键技术。(2)安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务。
二、动态代理技术
1、手动增加代理类存在的问题?
要为系统中的各种接口的类增加代理功能,则需要太多的代理类,全部采用静态代理方式,就要写成百上千个代理类,将是一件工作量巨大且非常麻烦的事情。
2、如何解决上述存在的问题?
JVM可以在“运行期”动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
3、动态类需注意细节:
JVM生成的动态类必须实现一个或多个接口,这样JVM就知道该实现什么方法。所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
4、如果一个目标类自身没有实现接口,如何让JVM动态生成的代理类与目标类有相同的方法列表呢?
生成的代理类的方法声明要不要和目标类的方法一样?要。但目标类自身并没有实现接口,那通过什么样的方式告诉JVM生成的代理类与目标类有相同的方法列表,JVM干不了这件事情,因为没接口。
这时候有一个第三方CGLIB库,CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
5、在代理方法中什么位置可以插入系统功能代码?
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
(1)在调用目标方法之前
(2)在调用目标方法之后
(3)在调用目标方法前后
(4)在处理目标方法异常的catch块中
三、JVM动态生成的类
(一)创建动态类及查看其方法列表信息
1、要求:
(1)创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
(2)编码列出动态类中的所有构造方法和参数签名
(3)编码列出动态类中的所有方法和参数签名
2、示例代码:
1 import java.lang.reflect.Constructor;
2 import java.lang.reflect.Method;
3 import java.lang.reflect.Proxy;
4 import java.util.Collection;
5 public class ProxyTest {
6 /**
7 * @param args
8 * @throws SecurityException
9 * @throws NoSuchMethodException
10 * @throws Exception
11 * @throws IllegalArgumentException
12 * @throws IllegalAccessException
13 * @throws InstantiationException
14 */
15 public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, Exception {
16 Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
17 System.out.println(clazzProxy1.getName());
18
19 System.out.println("------------begin constructor list ------------");
20 Constructor[] constructors = clazzProxy1.getConstructors();
21 for(Constructor constructor : constructors){
22 String name = constructor.getName();
23 StringBuilder sb = new StringBuilder(name);
24 sb.append("(");
25 Class[] clazzParams = constructor.getParameterTypes();
26 for(Class clazzParam : clazzParams ){
27 sb.append(clazzParam.getName()).append(",");
28 }
29 if( clazzParams!=null && clazzParams.length!=0)
30 sb.deleteCharAt(sb.length()-1);
31 sb.append(")");
32 System.out.println(sb);
33 }
34
35 System.out.println("------------begin method list -------------");
36 Method[] methods = clazzProxy1.getMethods();
37 for(Method method : methods){
38 String name = method.getName();
39 StringBuilder sb = new StringBuilder(name);
40 sb.append("(");
41 Class[] clazzParams = method.getParameterTypes();
42 for(Class clazzParam : clazzParams ){
43 sb.append(clazzParam.getName()).append(",");
44 }
45 if( clazzParams!=null && clazzParams.length!=0)
46 sb.deleteCharAt(sb.length()-1);
47 sb.append(")");
48 System.out.println(sb);
49 }
50 }
51 }
(二)创建动态类的实例对象及调用其方法
1、创建动态类的实例对象有三种方式:
(1)首先通过Proxy类的getProxyClass(ClassLoader loader, Class<?>... interfaces)方法,获取代理类的对象;然后通过反射获得构造方法;最后通过接口InvocationHandler的子类创建对象;
(2)首先通过Proxy类的getProxyClass(ClassLoader loader, Class<?>... interfaces)方法,获取代理类的对象;通过反射获得构造方法;通过给构造方法的newInstance()传入InvocationHandler的匿名内部类来创建对象;
(3)直接通过Proxy类的newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法,创建对象。
2、创建动态类的实例对象的代码实现
1 import java.lang.reflect.Constructor;
2 import java.lang.reflect.InvocationHandler;
3 import java.lang.reflect.Method;
4 import java.lang.reflect.Proxy;
5 import java.util.ArrayList;
6 import java.util.Collection;
7 public class ProxyTest {
8 /**
9 * @param args
10 * @throws SecurityException
11 * @throws NoSuchMethodException
12 * @throws Exception
13 * @throws IllegalArgumentException
14 * @throws IllegalAccessException
15 * @throws InstantiationException
16 */
17 public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, Exception {
18 Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
19 Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
20
21 System.out.println("------------begin create instance list --------------");
22 //System.out.println("------------方式一:创建动态类的实例对象 ----------");
23 class MyInvocationHandler1 implements InvocationHandler{
24 @Override
25 public Object invoke(Object proxy, Method method, Object[] args)
26 throws Throwable {
27 // TODO Auto-generated method stub
28 return null;
29 }
30 }
31 Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1());
32 System.out.println(proxy1);
33
34 // System.out.println("------------方式二:创建动态类的实例对象 -----------");
35 Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
36 @Override
37 public Object invoke(Object proxy, Method method, Object[] args)
38 throws Throwable {
39 // TODO Auto-generated method stub
40 return null;
41 }
42 });
43
44 // System.out.println("------------方式三:创建动态类的实例对象 ------------");
45 Collection proxy3 = (Collection)Proxy.newProxyInstance(
46 Collection.class.getClassLoader(),
47 new Class[]{Collection.class},
48 new InvocationHandler(){
49 ArrayList target = new ArrayList();
50 @Override
51 public Object invoke(Object proxy, Method method,
52 Object[] args) throws Throwable {
53 long beginTime = System.currentTimeMillis();
54 Object retVal = method.invoke(target, args);
55 long endTime = System.currentTimeMillis();
56 System.out.println(method.getName()+"run time"+(endTime-beginTime));
57 return retVal;
58 }
59 }
60 );
61 proxy3.add("zxx");
62 proxy3.add("flx");
63 proxy3.add("lhm");
64 proxy3.add("bxd");
65 proxy3.add("yzz");
66 System.out.println(proxy3.size());
67 }
68 }
(三)总结思考
问题:让JVM创建动态类及其实例对象,需要给它提供哪些信息?
解答:主要包括三个方面的信息:
(1)生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
(2)产生的类字节码必须有个一个关联的类加载器对象;
(3)生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
* 用Proxy.newInstance方法直接一步就创建出代理对象。
四、动态生成的类的内部代码分析
在上面“创建动态类的实例对象”的代码中,动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。
1、问题:构造方法接受一个InvocationHandler对象,接收这个对象要干什么用呢?该方法内部的代码是怎样的呢?
(1)构造方法接收一个参数,为了记住这个参数,以后运用它。
(2)内部代码:
1 $Proxy0 implements Collection{
2 InvocationHandler handler;
3 public $Proxy0(InvocationHandler handler){
4 this.handler = handler;
5 }
6 }
2、问题:实现Collection接口的动态类中的各个方法的代码又是怎样的呢? InvocationHandler接口中定义的invoke方法接收的三个参数又是什么意思?
1 (1)$Proxy0 implements Collection{
2 InvocationHandler handler;
3 public $Proxy0(InvocationHandler handler){
4 this.handler = handler;
5 }
6 //生成的Collection接口中的方法的运行原理
7 int size(){
8 return handler.invoke(this, this.getClass().getMethod("size"), null);
9 }
10 void clear(){
11 handler.invoke(this, this.getClass().getMethod("clear"), null);
12 }
13 boolean add(Object obj){
14 handler.invoke(this, this.getClass().getMethod("add"), obj);
15 }
16 }
(2)InvocationHandler接口中定义的invoke方法接收的三个参数意义,如下图说明:
说明:客户端调用了代理对象objProxy,调用了代理对象的add()方法,为该方法传递了字符串参数"abc"。
3、为什么动态类的实例对象的getClass()方法返回了正确结果呢?
调用调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。
五、动态生成的类成为目标类的代理
1、动态代理的工作原理图
2、eclipse重构出一个getProxy方法绑定接收目标同时返回代理对象,怎样将目标类作为参数传进去?
(1)直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义。
(2)为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。
(3)让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。
3、在上面将目标类作为参数传入之后,将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供?
(1)把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外界提供的代码!
(2)为bind方法增加一个Advice参数。
4、将目标类和系统功能作为参数传递给getProxy()方法,实现示例代码如下:
(1)创建ProxyTest类
1 import java.lang.reflect.Constructor;
2 import java.lang.reflect.InvocationHandler;
3 import java.lang.reflect.Method;
4 import java.lang.reflect.Proxy;
5 import java.util.ArrayList;
6 import java.util.Collection;
7 public class ProxyTest {
8 /** @param args
9 * @throws SecurityException
10 * @throws NoSuchMethodException
11 * @throws Exception
12 * @throws IllegalArgumentException
13 * @throws IllegalAccessException
14 * @throws InstantiationException
15 */
16 public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, Exception {
17 final ArrayList target = new ArrayList();//将目标抽取出来,方法里面的内部类要访问局部变量必须添加final关键字
18 Collection proxy3 = (Collection)getProxy(target,new MyAdvice());//抽取出来的方法
19 proxy3.add("zxx");
20 proxy3.add("flx");
21 proxy3.add("lhm");
22 System.out.println(proxy3.size());
23 }
24 private static Object getProxy(final Object target,final Advice advice) { /*做成通用的方法,返回Object*/
25 Object proxy3 = Proxy.newProxyInstance(
26 /*Collection.class.getClassLoader(), //第一个参数*/
27 target.getClass().getClassLoader(), // 代理类的类加载器与目标类的类加载器相同,与目标类有关。
28
29 /*new Class[]{Collection.class}, //第二个参数*/
30 target.getClass().getInterfaces(), //与target实现相同的接口,代理类要实现的接口也是目标类实现的接口,与目标类有关
31
32 new InvocationHandler(){ //第三个参数,
33 @Override
34 public Object invoke(Object proxy, Method method,
35 Object[] args) throws Throwable {
36 /*
37 long beginTime = System.currentTimeMillis(); //将系统功能抽取为一个对象
38 Object retVal = method.invoke(target, args);
39 long endTime = System.currentTimeMillis(); //将系统功能抽取为一个对象
40 System.out.println(method.getName()+"run time"+(endTime-beginTime));
41 return retVal;
42 */
43 advice.beforeMethod(method);
44 Object retVal = method.invoke(target, args);
45 advice.afterMethod(method);
46 return retVal;
47 }
48 }
49 );
50 return proxy3;
51 }
52 }
(2)创建Advice接口
import java.lang.reflect.Method;
public interface Advice {
//一般来说,这个建议的接口应该有四个方法,这四个方法可以分别插入:
/* 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,
* 还可以在代理方法中的如下四个位置加上系统功能代码:
* 1.在调用目标方法之前
* 2.在调用目标方法之后
* 3.在调用目标方法前后
* 4.在处理目标方法异常的catch块中
* */
void beforeMethod(Method method);
void afterMethod(Method method);
}
(3)创建Advice接口的子类MyAdvice
1 import java.lang.reflect.Method;
2 public class MyAdvice implements Advice {
3 long beginTime = 0;
4 public void beforeMethod(Method method) {
5 // TODO Auto-generated method stub
6 System.out.println("到黑马程序员训练营来学习了!");
7 beginTime = System.currentTimeMillis(); //将系统功能抽取为一个对象
8 }
9
10 public void afterMethod(Method method) {
11 // TODO Auto-generated method stub
12 System.out.println("从黑马程序员训练营毕业工作了!");
13 long endTime = System.currentTimeMillis(); //将系统功能抽取为一个对象
14 System.out.println(method.getName()+" method run of time "+(endTime-beginTime));
15 System.out.print(System.lineSeparator() );
16 }
17 }
六、实现类似spring的可配置的AOP框架
(一)工厂类BeanFactory
1、工厂类BeanFactory:负责创建目标类或代理类的实例对象,并通过配置文件实现切换。
2、getBean方法:根据参数字符串返回一个相应的实例对象。如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则返回该类示例对象的getProxy方法返回的对象。
3、BeanFactory的构造方法:接收代表配置文件的输入流对象的配置文件。
4、ProxyFactoryBean为BeanFactory提供配置参数信息:(1)目标(target)(2)通告(advice)
5、BeanFactory和ProxyFactoryBean:
(1)BeanFactory是一个纯粹的bean工程,就是创建bean即相应的对象的工厂。
(2)ProxyfactoryBean是BeanFactory中的一个特殊的Bean,是创建代理的工厂。
(二)实现类似spring的可配置的AOP框架的思路:
1、创建BeanFactory类:
(1)构造方法:接受一个配置文件,通过Properties对象加载InputStream流对象。
(2)创建getBean(String name)方法:根据类名name,拿到对应的类名className。
(3)根据类名获取其字节码对象,并创建实例对象bean。
(4)判断bean是否是特殊的Bean即ProxyFactoryBean。
① 如果是,就要创建代理类,并设置目标(target)和通告(advice),分别得到各自的实例对象,并返回代理类实例对象。
② 如果不是在返回Bean对象自己。
2、创建ProxyFactoryBean类,定义target和advice;定义getProxy()方法,用于创建代理类对象。
3、创建配置文件config.properties,对配置文件进行配置,配置内容如下
xxx=java.util.ArrayList
#xxx=cn.itheima.day3.aopframework.ProxyFactoryBean
xxx.advice=cn.itheima.day3.MyAdvice
xxx.target=java.util.ArrayList
注: #表示注释当前行。
4、作一个测试类:AopFrameworkTest进行测试。
(三)完整代码示例
1、创建BeanFactory类:
package cn.itheima.day3.aopframework;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import cn.itheima.day3.Advice;
public class BeanFactory {
Properties props = new Properties();
public BeanFactory(InputStream ips){
try {
props.load(ips);
} catch (IOException e) {
e.printStackTrace();
}
}
public Object getBean(String name){
String className = props.getProperty(name);//根据类名name,拿到对应的类名。
Object bean = null;
try {
Class clazz = Class.forName(className);
bean = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
if(bean instanceof ProxyFactoryBean){
Object proxy = null;
ProxyFactoryBean ProxyFactoryBean = (ProxyFactoryBean)bean;
try {
Advice advice = (Advice)Class.forName(props.getProperty(name+".advice")).newInstance();
Object target = Class.forName(props.getProperty(name+".target")).newInstance();
ProxyFactoryBean.setAdvice(advice);
ProxyFactoryBean.setTarget(target);
proxy = ProxyFactoryBean.getProxy();
} catch (Exception e) {
e.printStackTrace();
}
return proxy;
}
return bean;
}
}
2、创建ProxyFactoryBean类:
1 package cn.itheima.day3.aopframework;
2
3 import java.lang.reflect.InvocationHandler;
4 import java.lang.reflect.Method;
5 import java.lang.reflect.Proxy;
6 import cn.itheima.day3.Advice;
7 public class ProxyFactoryBean {
8 private Advice advice;
9 private Object target;
10 public Advice getAdvice() {
11 return advice;
12 }
13 public void setAdvice(Advice advice) {
14 this.advice = advice;
15 }
16 public Object getTarget() {
17 return target;
18 }
19 public void setTarget(Object target) {
20 this.target = target;
21 }
22 public Object getProxy() {
23 Object proxy3 = Proxy.newProxyInstance(
24 target.getClass().getClassLoader(), // 代理类的类加载器与目标类的类加载器相同,与目标类有关。
25 target.getClass().getInterfaces(), //与target实现相同的接口,代理类要实现的接口也是目标类实现的接口,与目标类有关
26
27 new InvocationHandler(){ //第三个参数
28 @Override
29 public Object invoke(Object proxy, Method method,
30 Object[] args) throws Throwable {
31 advice.beforeMethod(method);
32 Object retVal = method.invoke(target, args);
33 advice.afterMethod(method);
34 return retVal;
35 }
36 }
37 );
38 return proxy3;
39 }
40 }
3、创建配置文件config.properties。
xxx=java.util.ArrayList
#xxx=cn.itheima.day3.aopframework.ProxyFactoryBean
xxx.advice=cn.itheima.day3.MyAdvice
xxx.target=java.util.ArrayList
4、创建AopFrameworkTest测试类,进行测试:
1 package cn.itheima.day3.aopframework;
2
3 import java.io.InputStream;
4 public class AopFrameworkTest {
5 /**
6 * @param args
7 */
8 public static void main(String[] args) {
9 // TODO Auto-generated method stub
10 InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
11 Object bean = new BeanFactory(ips).getBean("xxx");
12 System.out.println(bean.getClass().getName());
13 }
14 }
5、设计到的接口Advice及其子类MyAdvice
(1)Advice接口:
1 package cn.itheima.day3;
2 import java.lang.reflect.Method;
3 public interface Advice {
4 void beforeMethod(Method method);
5 void afterMethod(Method method);
6 }
(2)MyAdvice类:
1 package cn.itheima.day3;
2 import java.lang.reflect.Method;
3 public class MyAdvice implements Advice {
4 long beginTime = 0;
5 public void beforeMethod(Method method) {
6 // TODO Auto-generated method stub
7 System.out.println("到黑马程序员训练营来学习了!");
8 beginTime = System.currentTimeMillis(); //将系统功能抽取为一个对象
9 }
10
11 public void afterMethod(Method method) {
12 // TODO Auto-generated method stub
13 System.out.println("从黑马程序员训练营毕业工作了!");
14 long endTime = System.currentTimeMillis(); //将系统功能抽取为一个对象
15 System.out.println(method.getName()+" method run of time "+(endTime-beginTime));
16 System.out.print(System.lineSeparator() );
17 }
18 }