文章目录
动态代理
最近在复习Spring的相关知识,当看到AOP这一章的时候,难免会有一点感悟,因为最开始学AOP的时候简直是一头雾水,AOP是一种思想,官方也解释得非常得官方化
小白作为入门很容易卡壳,毕竟学习一种思想的成本比学习增删改查要大得多,所以需要我们掌握足够多的理论基础,即内功要好,当然不需要理解以下内容也可以写出AOP的代码,但是对了解一定的底层原理有助于帮助我们更好的去深入学习和理解AOP这种思想,下面我将一步一步介绍AOP的底层——代理设计模式
1.场景分析
假如我们得到一个需求,需要给我们的dao层代码增加日志管理,这个时候,最常见的思维就是直接在dao层实现类代码上添加相关的逻辑,如下图所示
但是,当我们的dao层的方法逐渐增多,但是日志逻辑都是相同的,这样会造成代码的冗余,此时,我们就在想,为何不将日志记录和记录完成处理的代码抽取成一个日志类中的方法,然后只需要将这个对象作为dao的成员属性,在dao中需要的地方调用日志的逻辑即可,代码如下
Loging.java
public class Loging {
public void begin() {
System.out.println("开始记录日志");
}
public void end() {
System.out.println("结束记录日志");
}
}
然后dao层中的代码
public class PersonDaoImpl implements PersonDao {
private Loging log=new Loging();
/* (non-Javadoc) * @see myproxy.PersonDao#getPersonById(java.lang.Integer) */
@Override
public Person getPersonById(Integer id) {
log.begin();
System.out.println("查询数据库");
log.end();
return new Person();
}
/* (non-Javadoc) * @see myproxy.PersonDao#getNameById(java.lang.Integer) */
@Override
public Integer updateNameById(Integer id) {
log.begin();
System.out.println("更新数据库");
log.end();
return 1;
}
}
测试结果:
貌似我们也实现了业务的需要,但是和上一个测试demo相比,只是将日志逻辑进行了简单的封装,还是存在代码冗余的问题,接下来我要提出一个比较新的解决方案,静态代理
2 静态代理
静态代理的原理,可以用下图来理解下
首先春哥和经纪人都需要实现singer接口,并实现sing方法,假如我们想请春哥给我们唱一首歌,我们不能直接去叫她给我们唱歌,而是去找她的经纪人。但是我们的经纪人不是真正唱歌的那个人,她与春哥具有关联关系,在经纪人的sing方法中,我们可以用来处理一些业务逻辑,比如价钱到不到位之类的,如果满足结果,就调用春哥的sing方法进行唱歌,接下来附上代码
歌手接口
public interface Singer {
//唱歌方法
public void sing(int money);
}
经纪人和春哥的实现类
//春哥类
public class ChunBrother implements Singer {
@Override
public void sing(int money) {
System.out.println("春哥唱歌了");
}
}
//经纪人
public class Manager implements Singer {
private Singer singer;
public Manager(Singer singer) {
this.singer = singer;
}
@Override
public void sing(int money) {
}
}
测试代码
ChunBrother cg=new ChunBrother();
Manager m=new Manager(cg);
m.sing(15000);
可以看到,我们并没有给chunBrother类中添加多余的源码,所有添加的方法都是在代理类中完成的,但是静态代理有一个缺点就是代理类和真实类都需要实现同一个接口,当接口新添加方法的时候,都需要重写其方法,此时,我们就该用上今天的主角了,jdk动态代理技术
3 JDK动态代理
需求:在不污染service源代码(添加或者修改其源代码)的情况下,对其方法进行事务处理
用户service层和实现类
public interface PersonService {
public int addUser();
public int deleteUser(int id);
public int updateUser(int id);
}
public class PersonServiceImpl implements PersonService {
@Override
public int addUser() {
System.out.println("添加User");
return 0;
}
@Override
public int deleteUser(int id) {
System.out.println("删除User");
return 0;
}
@Override
public int updateUser(int id) {
System.out.println("更新用户");
return 0;
}
}
创建事务类
public class Transaction {
public void beginTX() {
System.out.println("开启事务");
}
public void commit() {
System.out.println("提交事务");
}
}
当创建后以上对象后,就进入了接下来的重点,我们需要创建一个代理对象类(经纪人),并实现InvocationHandler接口,重写invoke方法
public class Proxy<T> implements InvocationHandler {
private T target;
private Transaction tx=new Transaction();
public PersonProxy(T target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
tx.beginTX();
Object result = method.invoke(target, args);
tx.commit();
return result;
}
}
首先我们将事务类和真实的目标对象(春哥)作为成员变量,用来创建事务的类也可以通过构造方法或者setter传入,但是这里因为是一个demo所以直接new对象了。
然后介绍一下这三个参数
Object proxy:代表方法执行的那个对象
Method method:用户执行的方法
Object[] args:执行方法传入的参数
接下来,一句一句分析
Object result = method.invoke(target, args); 这句话的意思是执行目标对象的该方法,并传入参数,这里涉及到反射的知识,需要大家了解一下。这句话的意思是,在用户使用代理对象调用方法的时候会将调用的方法以及参数抽取出来,作为参数传入。(根据Java面向对象的思想,将所有的类抽取出一个类,就是Class;将所有方法抽取出一个类,那就是Method,方法中具有方法修饰符,返回值等属性,这些都是反射的知识)
执行方法后,如果有返回值就将返回值赋给result
既然Object result = method.invoke(target, args);是在执行目标类的方法,那么我们就可以在其前后完成一系列操作,比如记录日志,开启关闭事务,记录执行时间等操作,并且也没有更改源代码
最后
开始测试
1.创建真实对象
PersonDao personDao=new PersonDaoImpl();
2.创建PersonProxy动态代理器
PersonProxy<PersonDao> proxy=new PersonProxy<PersonDao>(personDao);
3.重点,创建personDao的代理对象
PersonDao p1=
(PersonDao) Proxy.newProxyInstance(PersonDao.class.getClassLoader(),
personDao.getClass().getInterfaces(),
proxy);
第一个参数是得到真实对象类型的类加载器,第二个参数是得到真实对象的所有接口实现,第三个参数,是传入PersonProxy对象对象。
在执行完该方法后将返回一个Object类型的对象,熟悉反射的朋友都应该清楚,此时返回的类型为Object类型
可以看到对象前面有一个$,以后看到这个一般都是代理对象。
执行结果
但是这个办法有一个缺点,即只能代理实现了接口的类,因为其参数需要它的接口数组,这时候有一个更高级的代理模式
4.CGLIB动态代理
GCLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势志入横切逻辑.
导入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
动态代理器
public class CglibProxy implements MethodInterceptor {
//代理对象
private Enhancer enhancer=new Enhancer();
// 切面类(这里指事务类)
private Transaction transaction;
public CglibProxy(Transaction transaction) {
this.transaction=transaction;
}
//设置并获得代理对象
public Object getEnhancer(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
transaction.beginTX();
Object invokeSuper = proxy.invokeSuper(obj, args);
transaction.commit();
return invokeSuper;
}
}
首先需要实现 MethodInterceptor接口,然后创建Enhancer对象,这个对象是用来根据真实对象的类型来创建代理对象,通过以下代码,来设置代理对象的类型和回调,然后返回创建的对象
public Object getEnhancer(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
测试
Transaction tx=new Transaction();
CglibProxy c=new CglibProxy(tx);
PersonDao p=new PersonDaoImpl();
PersonDao enhancer = (PersonDao) c.getEnhancer(p.getClass());
enhancer.updateNameById(1);