前言
最近完成了自己的个人博客项目,要继续学习Spring了,AOP用的是动态代理,今天特地好好理解一下代理模式
路线
- 静态代理
- jdk动态代理
- CGLIB动态代理
写在前面
代理模式和装饰器模式,实现路线都是实现特地的接口,然后增加一些功能,那么它们的重要区别在哪呢?职能!,装饰器模式主要用于增强
方法,而代理模式主要用于控制
。举几个控制的例子,比如JDBC做事务,是否需要开启事务,可以用代理。类似下面的.
案例一:JDBC 例子
三部分
- interface task 代表一个接口
- Proxy 代表代理类
- RealTask代表真正进行操作的类
实现:
public interface task {
void doSomething();
}
public class RealTask implements task {
@Override
public void doSomething() {
System.out.println("正在处理....");
}
}
public class Proxy implements task {
private task task;
public Proxy(task task) {
this.task = task;
}
@Override
public void doSomething() {
System.out.println("开启事务");
task.doSomething();
System.out.println("提交事务");
}
}
测试:
public class Main {
public static void main(String[] args) {
task task = new RealTask();
Proxy proxy = new Proxy(task);
proxy.doSomething();
}
}
----output----
开启事务
正在处理....
提交事务
解释:
首先,最终doSomething的是谁?是RealTask
,虽然这里调用的是Proxy.doSomething方法,但是最终还是由RealTask来执行,它只是在这个基础上做了两件额外的事情,开启事务和提交事务
,它没有侵入到doSomeThing这个任务方法里面去----意思就是说,开启事务和你执行的各种数据库连接sql操作有关系吗?自然是没有的。Proxy就像是一个中介,然后对目标要干的事情加以控制。再想想装饰器模式,它是侵入到了doSomething里面。还不明白啥叫控制?再来个例子。
案例二:控制被执行的次数
public interface hello {
void sayHello();
}
public class RealHello implements hello {
@Override
public void sayHello() {
System.out.println("Hello");
}
}
public class ProxyHello implements hello {
private int time;
private hello hello;
@Override
public void sayHello() {
if (hello == null) {
hello = new RealHello();
}
if (this.time < 5) {
hello.sayHello();
}
this.time++;
}
}
测试:
public class Main {
public static void main(String[] args) {
hello hello = new RealHello();
hello proxy = new ProxyHello();
for (int i = 0; i < 10; i++) {
proxy.sayHello();
}
}
}
----output----
Hello
Hello
Hello
Hello
Hello
这次的控制就比较明显了,本来执行10次,因为有了Proxy,当执行次数到达5次的时候,就不会再调用hello方法,代理控制没有侵入到sayHello把?
动态代理
了解了什么是代理模式,代理模式是干嘛呢,它和装饰器模式的区别在哪,之后呢,我们还要了解啥叫静态代理,啥叫动态代理。
- 静态代理:静态 就是说在编译期就已经知道了,生成了class文件,我们知道某一个代理是为了某一个类的对象服务的。
- 动态代理:动态 是说,当有大量的或者需要在运行期间确定代理行为的时候,就要用到这里的动态。我们也不用写很多的代理类了
JDK 动态代理
几个步骤
- 被代理对象接口
- 被代理对象
- 处理Handler,处理代理过程
- Proxy的静态方法,创建代理类
- 通过代理类执行被代理的方法
public class Handler implements InvocationHandler {
private Object target;
private int time;
public Handler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (this.time < 5) {
method.invoke(target, args);
this.time++;
}
return null;
}
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
}
public static void main(String[] args) {
Hello hello = new RealHello();
Handler handler = new Handler(hello);
Hello proxy = (Hello) handler.getProxy();
for (int i = 0; i < 10; i++) {
proxy.sayHello();
}
}
----output----
Hello
Hello
Hello
Hello
Hello
实际引用–事务处理
看了上面的例子,我们用一个实际应用来演示上面的案例.Demo如下:
public interface ArticleService {
void newArticle(ArticleForm form, User user) throws NewArticleException;
}
被代理类实现:
@Override
public void newArticle(ArticleForm form, User user) throws NewArticleException {
// try {
Article article = Form2BeanUtils.form2Article(form);
int userId = user.getUserId();
// JDBCUtils.startTransaction();
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
Set<String> tagNames = form.getTags();
articleService.createTags(tagNames, conn);
Set<String> categories = form.getCategories();
articleService.createCategories(categories, conn);
// 获取新旧合并的的id号
List<Integer> categoriesIds = categoryDao.getIds(categories, conn);
List<Integer> tagIds = tagDao.getIds(tagNames, conn);
//建立连接关系1:n user_article, m:n article_categories, m:n article_tags
BigInteger article_id = articleDao.createArticle(article, userId, conn);
if (tagIds.size() != 0) {
articleDao.joinTag(article_id, tagIds, conn);
}
if (categoriesIds.size() != 0) {
articleDao.joinCategories(article_id, categoriesIds, conn);
}
/* JDBCUtils.commit();
} catch (Exception e) {
try {
e.printStackTrace();
JDBCUtils.rollback();
throw new NewArticleException(e);
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
try {
JDBCUtils.release();
} catch (SQLException e) {
e.printStackTrace();
}
}*/
}
处理类:
public class TransactionHandler implements InvocationHandler {
private Object target;
public TransactionHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
JDBCUtils.startTransaction();
method.invoke(target, args);
JDBCUtils.commit();
} catch (Exception e) {
try {
e.printStackTrace();
JDBCUtils.rollback();
throw new NewArticleException(e);
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
try {
JDBCUtils.release();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
public Object getProxy() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
使用:
TransactionHandler handler = new TransactionHandler(articleService);
ArticleService service = (ArticleService) handler.getProxy();
service.newArticle(form, user);
// articleService.newArticle(form, user);
注意: 注释部分是我之前没有用动态代理的方法,这样,事务每次运行就能复用这段开始事务,提交事务,而不用大量的写提交事务等语句。还是挺方便的~
CGLIB
CGLIB底层实现是ASM修改字节码,这个比较厉害了,我就从应用的基础上写个简单的例子,给自己留下一点印象:实现和上面一样的功能。
public class hello {
public void sayHello() {
System.out.println("hello");
}
}
public class MyInterceptor implements MethodInterceptor {
private int time;
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (this.time < 5) {
proxy.invokeSuper(obj, args);
this.time++;
}
return null;
}
}
public class App {
public static void main(String[] args) {
// 增强对象
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(hello.class);
// 设置拦截器
Callback interceptor = new MyInterceptor();
enhancer.setCallback(interceptor);
hello hello = (hello) enhancer.create();
for (int i = 0; i < 10; i++) {
hello.sayHello();
}
}
}
总结
- CGLIB和JDK 动态代理两者的区别:
- 实现角度: 后者需要实现接口,而CGLIB不需要,直接使用的super,当没有接口的时候就可以用CGLIB了
- 关系角度: 后者实现的代理Proxy,更像是被代理对象的兄弟,属于兄弟关系。而CGLIB就像是继承关系。
- 性能方面: 后者的实现是用的反射,创建对象速度优于CGLIB,而CGLIB虽然创建对象慢了点,但是它的方法执行速度却很快。(看的人家的,未验证)