这一篇文章来说一说aop的前置:代理模式的思想
代理类被授权用来代表普通类,从而实现对普通类功能上的增强。
静态代理是指已经创建好的代理类,代理类在编译期就已经是确定存在的。
以卖房子为例:
共同接口House
/**
* 有关房子功能的接口
*/
public interface House {
void sale();
}
普通用户类Person
/**
* 用户类
*/
public class Person implements House {
private String name;
public Person(String name) {
this.name = name;
}
public void sale(){
System.out.println(name + "=>卖房子");
}
}
代理类Agent
/**
* 中介事务所 - 帮忙代理卖房子
* 静态代理
*/
public class Agent implements House {
/**
* 代理对象
*/
Person target;
public Agent(Person target) {
this.target = target;
}
@Override
public void sale() {
target.sale();
System.out.println("卖房完成,收取了中介费");
}
}
测试运行类Main
public class Main {
public static void main(String[] args) {
Person person = new Person("且听风吟丶");
Agent proxy = new Agent(person);
proxy.sale();
}
}
我们不常用静态代理,主要是因为:
动态代理是在运行期利用反射机制生成代理类
动态代理技术的实现有两种:
jdk动态代理只能代理接口,所以目标对象必须有接口,如果没有接口就不能实现jdk动态代理
代理类和目标类都实现同一个接口,那么代理类和目标类的方法名就一样了
为了体现代理接口而实现的动态代理特性,这里假设有两个接口被代理:
House接口和Car接口,其下都有sale方法:
/**
* 有关房子功能的接口
*/
public interface House {
void sale();
}
/**
* 有关车功能的接口
*/
public interface Car {
void sale();
}
接下来创建两个不同的普通类Person1和Person2,分别实现House和Car接口:
/**
* 用户1
*/
public class Person1 implements House {
private String name;
public Person1(String name) {
this.name = name;
}
@Override
public void sale(){
System.out.println(name + "=>卖房子");
}
}
/**
* 用户2
*/
public class Person2 implements Car {
private String name;
public Person2(String name) {
this.name = name;
}
@Override
public void sale(){
System.out.println(name + "=>卖车");
}
}
接下来创建一个动态代理类Agent,该类拦截并检测名为sale的方法,并且收取中介费:
/**
* 基于jdk动态代理实现的代理类
*/
@SuppressWarnings("all")
public class Agent implements InvocationHandler {
/**
* 要代理的对象 - 这里已经可以是多个实现了统一接口的类,而不是单独某个类
*/
private Object target;
public Agent(Object target) {
this.target = target;
}
/**
* 以当前类信息为基础,创建代理对象并且返回
*/
public <T> T getProxy(){
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
proxy表示动态代理类实例,method表示拦截的方法,args表示拦截方法的参数
*/
Object returnValue = null;
if ("sale".equals(method.getName())){
returnValue = method.invoke(target, args);
System.out.println("检测到执行sale,收取中介费");
System.out.println("代理类实例:" + proxy);
System.out.println("拦截方法:" + method);
System.out.println("方法参数:" + Arrays.toString(args));
}
return returnValue;
}
}
最后是测试运行类Main:
public class Main {
public static void main(String[] args) {
// 创建两个普通类对象Person1,Person2
House person1 = new Person1("用户111");
Car person2 = new Person2("用户222");
// 代理类的类型是接口类型
House proxy1 = new Agent(person1).getProxy();
Car proxy2 = new Agent(person2).getProxy();
proxy1.sale();
System.out.println("==========");
proxy2.sale();
}
}
需要注意的是:创建的普通类需要使用接口类型接收,否则在动态代理过程中会报如下转型错误:
java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to XXX
运行结果如下:
用户111=>卖房子
检测到执行sale,收取中介费
代理类实例:null
拦截方法:public abstract void jdkProxy.House.sale()
方法参数:null
==========
用户222=>卖车
检测到执行sale,收取中介费
代理类实例:null
拦截方法:public abstract void jdkProxy.Car.sale()
方法参数:null
可以看到,在普通类实现了接口之后,使用jdk代理就可以对不同的接口实现进行代理操作,但是前提必须是普通类实现了一个接口,没有接口是不行的。、
cglib也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能
cglib是一个开源的强大的高性能代码生成宝,可以在运行期扩展Java类与实现Java接口,广泛被AOP框架使用,如Spring,可以提供方法的拦截
cglib动态代理是代理类去继承目标类,然后重写其中目标类的方法啊,这样也可以保证代理类拥有目标类的同名方法
注意:被代理类不能是final类型的,因为无法被继承;如果目标对象的某些方法是static、final类型的,也不会被拦截。
首先需要导入cglib的支持Jar包,这里直接导入了spring-core的jar包即可:
spring-core-5.2.3.RELEASE2
接下来创建普通类Person:
public class Person {
private String name;
public Person() {
}
public void sale(){
System.out.println(name + " 卖房子");
}
public void setName(String name) {
this.name = name;
}
}
CGlib has one important restriction: the target class must provide a default constructor.
If you use property-based injection instead of constructor-based injection, the problem will go away.
翻译:必须有空构造器,不然会报错如下:
Superclass has no null constructors but no arguments were given
因为cglib每次都是使用默认构造函数来创建代理对象,因此创建实际对象的时候无论怎样赋值都是无效的
接下来创建cglib代理类Agent:
public class Agent implements MethodInterceptor {
private Object target;
public Agent(Object target) {
this.target = target;
}
/**
* 获取代理对象
* @return 代理对象proxy
*/
public Object getProxy(){
// cglib的工具类,用来创建子类(代理对象)
Enhancer en = new Enhancer();
// 设置代理对象父类
en.setSuperclass(target.getClass());
// 设置回调函数
en.setCallback(this);
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 执行原对象的方法
Object returnValue = methodProxy.invokeSuper(obj, args);
// 代理执行内容
System.out.println("收取中介费...");
return returnValue;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
}
最后是测试运行类Main:
public class Main {
public static void main(String[] args) {
Person person = new Person("且听风吟丶");
Person proxy = (Person) new Agent(person).getProxy();
proxy.sale();
}
}
输出结果如下:
null 卖房子
收取中介费...
可以看到,即使表面上使用了含有name参数的构造器,实际在创建代理对象的时候使用的仍然是空构造器,因此参数name为null
当Bean实现接口时,Spring就会用JDK的动态代理
当Bean没有实现接口时,Spring使用CGlib是实现
可以强制使用CGlib
(在spring配置中加入