4.1 代理模式

代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

为什么要用代理模式?

  • 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
  • 开闭原则:回顾上一章说的设计原则,代理模式遵循的是开闭原则的设计思想。代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

有哪几种代理模式?

我们有多种不同的方式来实现代理。如果按照代理创建的时期来进行分类的话, 可以分为两种:静态代理、动态代理。静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。

4.2 动态代理

为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。

4.2.1 JDK动态代理

Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:

  • Interface InvocationHandler:该接口中仅定义了一个方法
public object invoke(Object obj,Method method, Object[] args)

在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组。这个抽象方法在代理类中动态实现。

  • Proxy:该类即为动态代理类,其中主要包含以下内容:

protected Proxy(InvocationHandler h):构造函数,用于给内部的h赋值。

static Class getProxyClass (ClassLoaderloader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。

static Object newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)

所谓DynamicProxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然,这个DynamicProxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。

在使用动态代理类时,我们必须实现InvocationHandler接口

通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式(DynamicSubject类)也可以动态改变,从而实现了非常灵活的动态代理关系。

必须有接口才能代理。

public interface HelloWord {
String sayHello(String toWhom);

void sayBye();
}

123456

实现接口的方法,被代理的类:

public class HelloWordImpl implements HelloWord {
@Override
public String sayHello(String toWhom) {
System.out.println("进入了真实逻辑");
return "hello," + toWhom;
}

@Override
public void sayBye() {
System.out.println("bye");
}
}
123456789101112

代理类实现InvocationHandler接口,重写invoke方法,接入代理的逻辑:

public class JdkProxy implements InvocationHandler {

private Object target = null;

/**
* 绑定代理对象
*
* @param target
* @return
*/
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}

/**
* 代理方法逻辑
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理逻辑开始");
Object object = method.invoke(target, args);
System.out.println("代理逻辑结束");
return object;
}
}

测试类:

public class JdkProxyTest {
public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();
HelloWord helloWord = (HelloWord) jdkProxy.bind(new HelloWordImpl());
System.out.println(helloWord.sayHello("Tom"));
helloWord.sayBye();
}
}

JDK动态代理步骤

  1. 创建一个实现接口InvocationHandler的类,它必须实现invoke方法

  2. 创建被代理的类以及接口
  3. 通过Proxy的静态方法newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
  4. 通过代理调用方法

4.2.2 CGLIB动态代理

cglib是一个java字节码的生成工具,它动态生成一个被代理类的子类,子类重写被代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

被代理类:

public class HelloServiceImpl {
public void sayHello(){
System.out.println("Hello Zhanghao");
}

public void sayBey(){
System.out.println("Bye Zhanghao");
}
}

实现MethodInterceptor接口生成方法拦截器:

public class HelloMethodInterceptor  implements MethodInterceptor{
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Before: " + method.getName());
Object object = methodProxy.invokeSuper(o, objects);
System.out.println("After: " + method.getName());
return object;
}
}

生成代理类对象并打印在代理类对象调用方法之后的执行结果:

public class Client {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhanghao/Documents/toy/spring-framework-source-study/");
Enhancer enhancer = new Enhancer();
//继承被代理类
enhancer.setSuperclass(HelloServiceImpl.class);
//设置回调
enhancer.setCallback(new HelloMethodInterceptor());
//设置代理类对象
HelloServiceImpl helloService = (HelloServiceImpl) enhancer.create();
//在调用代理类中方法时会被我们实现的方法拦截器进行拦截
helloService.sayBey();
}
}
result:
Before: sayBey
Bye Zhanghao
After: sayBey
构建代理类过程

我们可以从上面的代码示例中看到,代理类是由enhancer.create()创建的。Enhancer是CGLIB的字节码增强器,可以很方便的对类进行拓展。

创建代理类的过程:

  • 生成代理类的二进制字节码文件;
  • 加载二进制字节码,生成Class对象;
  • 通过反射机制获得实例构造,并创建代理类对象。

enhancer.create()实现:

/**
* Generate a new class if necessary and uses the specified
* callbacks (if any) to create a new object instance.
* Uses the no-arg constructor of the superclass.
* @return a new instance
*/
public Object create() {
classOnly = false;
argumentTypes = null;
return createHelper();
}

private Object createHelper() {
validate();
if (superclass != null) {
//设置生成类的名称
setNamePrefix(superclass.getName());
} else if (interfaces != null) {
//设置生成类的名称
setNamePrefix(interfaces[ReflectUtils.findPackageProtected(interfaces)].getName());
}
//生成代理类对象(在KEY_FACTORY.newInstance(...)->生成代理类的二进制字节码文件以及加载二进制字节码)
return super.create(KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter,
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID));
}

cglic一共会自动生成三个字节码文件。其中一个类HelloServiceImplEnhancerByCGLIBd855d4dc 继承了被代理类 HelloServiceImpl。这个类就是加强的代理类,其中会生成两个方法CGLIBsayHello1()sayHello()。 其中sayHello():

public final void sayHello() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if(this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}

if(var10000 != null) {
var10000.intercept(this, CGLIB$sayHello$1$Method, CGLIB$emptyArgs, CGLIB$sayHello$1$Proxy);
} else {
super.sayHello();
}
}

当代理对象的执行sayHello方法时,会首先判断一下是否存在实现了MethodInterceptor接口的CGLIB$CALLBACK_0;,如果存在,则将调用MethodInterceptor中的intercept方法。

与JDK代理对比

JDK代理要求被代理的类必须实现接口,有很强的局限性。而CGLIB动态代理则没有此类强制性要求。简单的说,CGLIB会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。但是如果被代理类被final修饰,那么它不可被继承,即不可被代理;同样,如果被代理类中存在final修饰的方法,那么该方法也不可被代理。

4.3 AOP

Aspect Oriented Programming:面向切面编程

什么时候会出现面向切面编程的需求?按照软件重构的思想,如果多个类中出现重复的代码,就应该考虑定义一个共同的抽象类,将这些共同的代码提取到抽象类中,比如Teacher,Student都有username,那么就可以把username及相关的get、set方法抽取到SysUser中,这种情况,我们称为纵向抽取。

但是如果,我们的情况是以下情况,又该怎么办? 给所有的类方法添加性能检测,事务控制,该怎么抽取? AOP就是希望将这些分散在各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立的模块中,让业务逻辑类依然保存最初的单纯。

AOP术语

  • 连接点(Joinpoint) 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。简单来说,就是在哪加入你的逻辑增强 连接点表示具体要拦截的方法,上面切点是定义一个范围,而连接点是具体到某个方法
  • 切点(PointCut) 每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。比如,连接点--数据库的记录,切点--查询条件 切点用于来限定Spring-AOP启动的范围,通常我们采用表达式的方式来设置,所以关键词是范围
  • 增强(Advice) 增强是织入到目标类连接点上的一段程序代码。在Spring中,像BeforeAdvice等还带有方位信息 通知是直译过来的结果,我个人感觉叫做“业务增强”更合适 对照代码就是拦截器定义的相关方法,通知分为如下几种: 前置通知(before):在执行业务代码前做些操作,比如获取连接对象 后置通知(after):在执行业务代码后做些操作,无论是否发生异常,它都会执行,比如关闭连接对象 异常通知(afterThrowing):在执行业务代码后出现异常,需要做的操作,比如回滚事务 返回通知(afterReturning),在执行业务代码后无异常,会执行的操作 环绕通知(around),这个目前跟我们谈论的事务没有对应的操作,所以暂时不谈
  • 目标对象(Target) 需要被加强的业务对象
  • 织入(Weaving) 织入就是将增强添加到对目标类具体连接点上的过程。 织入是一个形象的说法,具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。
  • 代理类(Proxy) 一个类被AOP织入增强后,就产生了一个代理类。
  • 切面(Aspect) 切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。

比如上文讨论的数据库事务,这个数据库事务代码贯穿了我们的整个代码,我们就可以这个叫做切面。 SpringAOP将切面定义的内容织入到我们的代码中,从而实现前后的控制逻辑。 比如我们常写的拦截器Interceptor,这就是一个切面类。

4.4 使用Spring AOP开发步骤

如果用maven的同学,引入pom依赖就好了

1) 先引入aop相关jar文件 (aspectj aop优秀组件)

  • spring-aop-3.2.5.RELEASE.jar 【spring3.2源码】
  • aopalliance.jar 【spring2.5源码/lib/aopalliance】
  • aspectjweaver.jar 【spring2.5源码/lib/aspectj】或【aspectj-1.8.2】
  • aspectjrt.jar 【spring2.5源码/lib/aspectj】或【aspectj-1.8.2】

注意: 用到spring2.5版本的jar文件,如果用jdk1.7可能会有问题

  • 需要升级aspectj组件,即使用aspectj-1.8.2版本中提供jar文件提供。

2) bean.xml中引入aop名称空间

  • xmlns:context="http://www.springframework.org/schema/context"
  • http://www.springframework.org/schema/context
  • http://www.springframework.org/schema/context/spring-context.xsd

4.4.1 引入名称空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

</beans>

4.4.2 注解方式实现AOP编程

我们之前手动的实现AOP编程是需要自己来编写代理工厂的,现在有了Spring,就不需要我们自己写代理工厂了。Spring内部会帮我们创建代理工厂。也就是说,不用我们自己写代理对象了。

因此,我们只要关心切面类、切入点、编写切入表达式指定拦截什么方法就可以了!

还是以上一个例子为案例,使用Spring的注解方式来实现AOP编程

4.4.2.1 在配置文件中开启AOP注解方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


<context:component-scan base-package="aa"/>

<!-- 开启aop注解方式 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>
4.4.2.2 代码

切面类

@Component
@Aspect//指定为切面类
public class AOP {


//里面的值为切入点表达式
@Before("execution(* aa.*.*(..))")
public void begin() {
System.out.println("开始事务");
}


@After("execution(* aa.*.*(..))")
public void close() {
System.out.println("关闭事务");
}
}

UserDao实现了IUser接口

@Component
public class UserDao implements IUser {

@Override
public void save() {
System.out.println("DB:保存用户");
}

}

IUser接口

public interface IUser {
void save();
}

测试代码:

public class App {

public static void main(String[] args) {

ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");

//这里得到的是代理对象....
IUser iUser = (IUser) ac.getBean("userDao");

System.out.println(iUser.getClass());

iUser.save();

}
}

4.4.3目标对象没有接口

上面我们测试的是UserDao有IUser接口,内部使用的是动态代理...那么我们这次测试的是目标对象没有接口

OrderDao没有实现接口

@Component
public class OrderDao {

public void save() {

System.out.println("我已经进货了!!!");

}
}

测试代码:

public class App {

public static void main(String[] args) {

ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");

OrderDao orderDao = (OrderDao) ac.getBean("orderDao");

System.out.println(orderDao.getClass());

orderDao.save();

}
}

4.4.4 AOP注解API

api:

  • @Aspect 指定一个类为切面类
  • @Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))") 指定切入点表达式
  • @Before("pointCut_()") 前置通知: 目标方法之前执行
  • @After("pointCut_()") 后置通知:目标方法之后执行(始终执行)
  • @AfterReturning("pointCut_()") 返回后通知: 执行方法结束前执行(异常不执行)
  • @AfterThrowing("pointCut_()") 异常通知: 出现异常时候执行
  • @Around("pointCut_()") 环绕通知: 环绕目标方法执行

// 前置通知 : 在执行目标方法之前执行
@Before("pointCut_()")
public void begin(){
System.out.println("开始事务/异常");
}

// 后置/最终通知:在执行目标方法之后执行 【无论是否出现异常最终都会执行】
@After("pointCut_()")
public void after(){
System.out.println("提交事务/关闭");
}

// 返回后通知: 在调用目标方法结束后执行 【出现异常不执行】
@AfterReturning("pointCut_()")
public void afterReturning() {
System.out.println("afterReturning()");
}

// 异常通知: 当目标方法执行异常时候执行此关注点代码
@AfterThrowing("pointCut_()")
public void afterThrowing(){
System.out.println("afterThrowing()");
}

// 环绕通知:环绕目标方式执行
@Around("pointCut_()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕前....");
pjp.proceed(); // 执行目标方法
System.out.println("环绕后....");
}

4.4.5 表达式优化

我们的代码是这样的:每次写Before、After等,都要重写一次切入点表达式,这样就不优雅了。

@Before("execution(* aa.*.*(..))")
public void begin() {
System.out.println("开始事务");
}


@After("execution(* aa.*.*(..))")
public void close() {
System.out.println("关闭事务");
}

于是乎,我们要使用@Pointcut这个注解,来指定切入点表达式,在用到的地方中,直接引用就行了!

那么我们的代码就可以改造成这样了:

@Component
@Aspect//指定为切面类
public class AOP {


// 指定切入点表达式,拦截哪个类的哪些方法
@Pointcut("execution(* aa.*.*(..))")
public void pt() {

}

@Before("pt()")
public void begin() {
System.out.println("开始事务");
}


@After("pt()")
public void close() {
System.out.println("关闭事务");
}
}


4.4.6 XML方式实现AOP编程

XML文件配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


<!--对象实例-->
<bean id="userDao" class="aa.UserDao"/>
<bean id="orderDao" class="aa.OrderDao"/>

<!--切面类-->
<bean id="aop" class="aa.AOP"/>

<!--AOP配置-->
<aop:config >

<!--定义切入表达式,拦截哪些方法-->
<aop:pointcut id="pointCut" expression="execution(* aa.*.*(..))"/>

<!--指定切面类是哪个-->
<aop:aspect ref="aop">

<!--指定来拦截的时候执行切面类的哪些方法-->
<aop:before method="begin" pointcut-ref="pointCut"/>
<aop:after method="close" pointcut-ref="pointCut"/>

</aop:aspect>
</aop:config>


</beans>

测试:

public class App {

@Test
public void test1() {

ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");

OrderDao orderDao = (OrderDao) ac.getBean("orderDao");

System.out.println(orderDao.getClass());

orderDao.save();

}

@Test
public void test2() {

ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");

IUser userDao = (IUser) ac.getBean("userDao");

System.out.println(userDao.getClass());

userDao.save();

}
}

4.5 切入点表达式

切入点表达式主要就是来配置拦截哪些类的哪些方法

4.5.1 语法解析

那么它的语法是这样子的:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

符号讲解:

  • ?号代表0或1,可以不写
  • “*”号代表任意类型,0或多
  • 方法参数为..表示为可变参数

参数讲解:

  • modifiers-pattern?【修饰的类型,可以不写】
  • ret-type-pattern【方法返回值类型,必写】
  • declaring-type-pattern?【方法声明的类型,可以不写】
  • name-pattern(param-pattern)【要匹配的名称,括号里面是方法的参数】
  • throws-pattern?【方法抛出的异常类型,可以不写】

官方也有给出一些例子给我们理解:

16223e1488feb8a0
16223e1488feb8a0

4.5.2 测试代码

<!-- 【拦截所有public方法】 -->
<!--<aop:pointcut expression="execution(public * *(..))" id="pt"/>-->

<!-- 【拦截所有save开头的方法 】 -->
<!--<aop:pointcut expression="execution(* save*(..))" id="pt"/>-->

<!-- 【拦截指定类的指定方法, 拦截时候一定要定位到方法】 -->
<!--<aop:pointcut expression="execution(public * cn.itcast.g_pointcut.OrderDao.save(..))" id="pt"/>-->

<!-- 【拦截指定类的所有方法】 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.*(..))" id="pt"/>-->

<!-- 【拦截指定包,以及其自包下所有类的所有方法】 -->
<!--<aop:pointcut expression="execution(* cn..*.*(..))" id="pt"/>-->

<!-- 【多个表达式】 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) || execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) or execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!-- 下面2个且关系的,没有意义 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) &amp;&amp; execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) and execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->

<!-- 【取非值】 -->
<!--<aop:pointcut expression="!execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->