总结了Spring 静态代理、动态代理、以及CGLib代理的代码实现方式
一.代理模式
代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
注意:
委托类对象就是我们后面说到的 目标对象(需要【被】代理的对象)
代理类对象就是我们后面说到的 代理对象(目标对象就是需要这个对象做为代理)
按照代理类的创建时期,代理类可分为两种。
静态代理类:
由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理类:在程序运行时,运用反射机制动态创建而成。
二. 静态代理
例如:
接口:HelloService
委托类:HelloServiceImpl
代理类:HelloServiceProxy
注意:委托类和代理类要实现同一个接口
public interface HelloService{
public String echo(String msg);
public Date getTime();
}
public class HelloServiceImpl implements HelloService{
public String echo(String msg){
return "echo:"+msg;
}
public Date getTime(){
return new Date();
}
}
public class HelloServiceProxy implements HelloService{
private HelloService helloService; //表示被代理的HelloService 实例
public HelloServiceProxy(HelloService helloService){
this.helloService=helloService;
}
public void setHelloServiceProxy(HelloService helloService){
this.helloService=helloService;
}
public String echo(String msg){
System.out.println("before calling echo()"); //目标方法调前处理
//调用委托类对象的方法(也就是目标对象方法/被代理对象方法)
//这个方法才是我们真正要执行的方法
String result=helloService.echo(msg);
System.out.println("after calling echo()"); //目标方法调用后处理
return result;
}
public Date getTime(){
System.out.println("before calling getTime()"); //目标方法调前处理
//调用委托类对象的方法(也就是目标对象方法/被代理对象方法)
//这个方法才是我们真正要执行的方法
Date date=helloService.getTime();
System.out.println("after calling getTime()"); //目标方法调用后处理
return date;
}
}
main:
HelloService helloService=new HelloServiceImpl();
HelloService helloServiceProxy=new HelloServiceProxy(helloService);
System.out.println(helloServiceProxy.echo("hello"));
三. 动态代理
与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包下面的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。
例子:
接口:
public interface IStudentService {
void save(Student s);
void delete(long id);
Student find(long id);
}
实现类
public class StudentServiceImpl implements IStudentService {
public void delete(long id) {
// 记录日志。。。
System.out.println("student is deleted...");
}
public Student find(long id) {
// 记录日志。。。
System.out.println("student is found...");
return null;
}
public void save(Student s) {
// 记录日志。。。
System.out.println("student is saved...");
}
}
日志类:
public class StudentLogger {
public void log(String msg){
System.out.println("log: "+msg);
}
}
//InvocationHandler接口的实现类,java的动态代理中需要使用
public class MyHandler implements InvocationHandler {
//目标对象
private Object target;
private StudentLogger logger = new StudentLogger();
public MyHandler() {
}
public MyHandler(Object target) {
this.target = target;
}
// 参数1 将来所产生的代理对象 Proxy4$
// 参数2 将来需要调用到的目标对象里面真正的那个方法的镜像
// 参数3 将来调用方法的时候所传的参数
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable {
// 获得将来所调用方法的名字
String methodName = m.getName();
// 用日志记录输出一下
logger.log(methodName + " is invoked...");
// 用反射的方式去调用将来需要真正调用的方法.
Object o = m.invoke(target, args);
return o;
}
get/set
....
}
main:
//目标对象
IStudentService service = new StudentServiceImpl();
//service是我们的目标对象。
//我们要给目标对象产生代理对象。
//目标对象service只能单独执行delete方法。
//但是我们需要的是:先执行log日志方法再执行delete方法。
//目标对象service做不到这个要求,所以我们要给目标对象service
//生成一个代理对象去完成这俩个操作.
//怎么给目标对象生成代理对象:
//JDK动态代理的方式
//获得目标对象的Class对象
Class c = service.getClass();
//获得目标对象的类加载器对象
ClassLoader classLoader = c.getClassLoader();
//获得目标对象所实现的所有接口
Class[] interfaces = c.getInterfaces();
//获得一个InvocationHandler接口的实现类对象,并把目标对象传进去
InvocationHandler h =
new MyHandler(service);
//参数1 目标对象的类加载器对象
//参数2 目标对象所实现的所有接口. Class类型数组
//参数3 InvocationHandler接口的实现类对象
IStudentService proxy =
(IStudentService)Proxy.newProxyInstance
(classLoader, interfaces, h);
//这里的proxy是一个实现了IStudentService接口动态生成的代理类的对象
proxy.delete();
注意:getClass()方法
toString()方法的区别
在invoke方法中调用代理对象的这两个方法
在main方法中调用代理对象的这两个方法
四 CGLib代理
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为目标对象创建一个子类对象,并在子类对象中拦截所有父类方法的调用,然后在方法调用前后调用后都可以加入自己想要执行的代码。JDK动态代理与CGLib动态代理都是Spring AOP的采用的代理方式。
简单的实现:
这是一个需要被代理的类,也就是父类,通过字节码技术创建这个类的子类,实现动态代理。
public class SayHello {
public void say(){
System.out.println("hello everyone");
}
}
注意:在cglib方式中,目标对象作为父类,代理对象作为目标对象动态生成的子类对象
该类实现了创建一个类的子类的方法(cglib给一个类生成代理对象的方式)
getProxy(SuperClass.class)方法通过参数即父类的class对象,创建出它的一个子类对象,也就是cglib方式的代理对象
intercept()方法拦截所有目标类方法的调用,
obj表示将来生成的代理对象,
method为目标类中方法的反射对象,args为方法的动态入参,
mproxy为代理类(子类)中方法的反射对象。
mproxy.invokeSuper(obj, args)通过代理类调用目标对象(父类)中的方法。
//在当前类创建目标对象的代理对象(目标对象的子类对象)
public class CglibProxy implements MethodInterceptor{
//返回目标对象的代理对象
public Object getProxy(Class clazz){
//可以动态生成目标对象的代理对象
Enhancer enhancer = new Enhancer();
//设置谁是父类
enhancer.setSuperclass(clazz);
//拦截住该方法调用的方法是谁
//this代表当前类的intercept方法
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy mproxy) throws Throwable {
System.out.println("前置代理");
//通过代理类调用父类中的方法
//即会执行到我们目标对象中的方法
Object result = mproxy.invokeSuper(obj, args);
System.out.println("后置代理");
return result;
}
}
main:
CglibProxy proxy = new CglibProxy();
//通过生成子类的方式创建代理类
SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);
proxyImp.say();
输出结果:
前置代理
hello everyone
后置代理
六.动态代理和CGLIB代理的区别
Spring实现AOP(Aspect Oriented Programming)是依赖JDK动态代理和CGLIB代理(不同情况spring会自己选择一种方式)。
JDK动态代理和CGLIB代理的对比:
JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的。
所以spring会有以下俩种选择动态代理实现方式的情况:
*如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
*如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间自动选择;
七.AOP的基本概念
认识AOP中的一些基本概念,然后在一个一个的例子中,不断的加强对这些概念的理解,同时要能自己表述出每个概念的含义
AOP
面向切面编程
aspect
切面/切面类
joinPoint
连接点
在spring的aop中只有类(目标对象)中的方法 可以做连接点,每一个方法都可以是一个连接点.
pointCut
切入点
一组连接点的集合
advice
通知/拦截器
用来控制切面类将来到底是织入到切入点的前面、后面或者是抛异常的时候。
adivsor
增强器
用来筛选类中的哪些方法是我们的连接点(哪些方法需要被拦截).
target
目标对象
proxy
代理对象
wave
织入:在运行期间advice将切面动态插入到切入点中去的过程。
前置通知(Before advice):
在某连接点(join point)之前执行的通知
返回后通知(After returning advice):
在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
抛出异常后通知(After throwing advice):
在方法抛出异常退出时执行的通知。
环绕通知(Around Advice):
包围一个连接点(join point)的通知,例如事务的处理,就需要这样的通知,因为事务需要在方法前开启,在方法后提交
八.advice的的具体例子及配置(配置没有使用简介的方式,使用的是很原始的方式)
在Spring中,Advice是由spring中的几个接口来指定(就像action类由struts2中的action接口来指定一样),主要有以下几种:
1) Before Advice
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
例如:
//有一下几个类或者接口:
Account.java
private int id;
private String name;
private double balance;//余额
get/set
...
AccountDao.java
//取款 账号减去多少钱
void withdraw(Account acc,double amt);
//存款 账号加上多少钱
void deposit(Account acc,double amt);
AccountDaoImpl.java
//简单的实现接口中的抽象方式
IAccountService.java
//银行账号的一个操作:例如转账
void bankAction();
AccountServiceImpl.java
private AccountDao accountDao;
private Account account;
//转账
public void bankAction(){
accountDao.withdraw(account, 100);
accountDao.deposit(account, 100);
}
get/set
...
//切面类
public class MyLogger {
public void log(String msg){
System.out.println("log:"+msg);
}
}
/*我们要做的事情:在转账方法(bankAction)执行之前进行一个日志输出*/
//前置通知
public class BeforeAdvice implements MethodBeforeAdvice {
//切面类
private MyLogger logger;
// 参数1 将来我们需要调用的目标对象中的方法镜像
// 参数2 将来调用方法的时候所传过来的参数
// 参数3 目标对象
//将来在调用目标对象方法之前,会先执行这个before方法
public void before(Method m, Object[] args, Object target) throws Throwable {
logger.log(m.getName() + " is invoked..");
/*
* 注意:这里一定不要自己手动的用反射去 调用这个目标对象中的方法,
* 因为spring 会帮我们去调用的,如果我们这个再去调用这个方法,
* 那么这这个方法会被调用俩次.
*
* m.invoke(target,args);
*
*/
}
get/set
}
配置xml文件: 注意ProxyFactoryBean的配置,htmlsingle中搜索即可
class="com.briup.aop.before.BeforeAdvice">
class="com.briup.aop.service.AccountServiceImpl">
class="org.springframework.aop.framework.ProxyFactoryBean">
com.briup.aop.service.IAccountService
beforeAdvice
2) After Returning advice
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable;
}
例如:
public class AfterAdvice implements AfterReturningAdvice {
private MyLogger logger;
//参数1 目标对象中的方法执行完返回值
//参数2 所执行方法的镜像对象
//参数3 执行方法时候所传的参数
//参数4 目标对象
//将来调用目标对象的方法之后会执行这个afterReturning方法
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
logger.log("after returning " + " target=" + target
+ " method Name=" + method.getName() + " args are:" + args
+ " returnValue=" + returnValue);
}
get/set
}
xml配置文件:
class="com.briup.aop.after.AfterAdvice">
class="com.briup.aop.service.AccountServiceImpl">
class="org.springframework.aop.framework.ProxyFactoryBean">
com.briup.aop.service.IAccountService
afterAdvice
注意:另外一个返回后通知接口:AfterAdvice的使用方式和这个是类似的,但是需要注意它们俩个之间的区别
3) 环绕Advice:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
例如:
public class AroundAdvice implements MethodInterceptor {
private MyLogger logger;
public Object invoke(MethodInvocation mi) throws Throwable {
// mi.getMethod()获得将来要调用的方法的镜像
//在目标方法执行之前做日志
logger.log(mi.getMethod().getName() + " is start...");
// 这个方法就是用来调用目标对象中的方法的
Object returnValue = mi.proceed();
//在目标方法执行之后做日志
logger.log(mi.getMethod().getName() + " is end...");
return returnValue;
}
get/set
xml配置文件:
class="com.briup.aop.around.AroundAdvice">
class="com.briup.aop.service.AccountServiceImpl">
class="org.springframework.aop.framework.ProxyFactoryBean">
com.briup.aop.service.IAccountService
aroundAdvice
4) Throws Advice
//ThrowsAdvice 是一个空接口,起标识作用
public interface ThrowsAdvice extends Advice {
}
例如:
public class ThrowingAdvice implements ThrowsAdvice {
private MyLogger logger;
public MyLogger getLogger() {
return logger;
}
public void setLogger(MyLogger logger) {
this.logger = logger;
}
//这里这个方法的名字一定要叫afterThrowing
//参数可以是1个也可以是四个
//1个参数的时候只能是一个异常类型的参数
//如果是4个参数的话,参数的顺序也一定要是下面的顺序
public void afterThrowing(Method method, Object[] args, Object target,Exception e) {
logger.log(e.getMessage());
}
//下面这样写也可以
/*
public void afterThrowing(Exception e) {
logger.log(e.getMessage());
}
*/
get/set
}
配置xml文件:
class="com.briup.aop.service.AccountServiceImpl">
class="org.springframework.aop.framework.ProxyFactoryBean">
com.briup.aop.service.IAccountService
throwAdvice
配置文件是基于每个advice来配置的有点繁琐,但是方便学习。实力有限,文章偏基础。