第3章 面向切面编程 3.1 AOP介绍   面向切面编程(Aspect-Oriented Programming,AOP)是继OOP编程之后的一个重要的编程思想。AOP与OOP(Object-Oriented Programming)编程模式不同,它提供了一种完全不同的编程思想。当然,OOP编程应用更广,AOP编程只能应用于特殊场景。   AOP编程不属于Spring必需的功能,Spring的 IoC容器可以不依赖AOP。如果不需要AOP功能,就可以不导入AOP相关包。   Spring??AOP提供了两种使用模式:基于XML的模式和基于@AspectJ注解的模式。   基于Spring??AOP的重要应用如下: * 用AOP声明性事务代替EJB的企业服务。 * 用AOP做日志处理。 * 用AOP做权限控制,如Spring Security。 3.1.1 AOP中的专业术语 * 切面(Aspect):模块化关注多个类的共性处理。事务管理是Java EE应用中关于横切关注的很好的例子。 * 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用时或者处理异常时。在Spring AOP中,一个连接点总是表示一个方法的执行。 * 通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括“around” “before”“after”等不同类型的通知。许多AOP框架(包括Spring)都是以拦截器作为通知模型,并维护一个以连接点为中心的拦截器链。 * 切入点(Pointcut):它由切入点表达式和签名组成。切入点如何与连接点匹配是AOP的核心,Spring默认使用AspectJ切入点语法。 * 引入(Introduction):它用来给出一个类型声明,添加额外的方法或属性。Spring允许引入新的接口给任何被织入的对象。例如,可以通过引入,使一个Bean实现IsModified接口,以便简化缓存机制。 * 目标对象(Target Object):被一个或者多个切面所通知的对象,也称作被通知对象。 既然Spring AOP是通过动态代理实现的,故这个对象永远是一个被代理对象。 * AOP代理(AOP Proxy):即动态代理,在Spring中,AOP代理可以是JDK的Proxy或者CGLIB。 * 织入(Weaving):创建一个通知者,把切面连接到其他应用程序类型或者对象上。这些可以在编译时、类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。 3.1.2 advice的通知类型   AOP通知有如下类型: * 前置通知(before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。 * 后置通知(after returning advice):在某连接点正常完成后执行的通知。例如,一个方法没有抛出任何异常,正常返回。 * 异常通知(after throwing advice):在方法抛出异常退出时执行的通知。 * 最终通知(after (finally) advice):当某连接点退出时执行的通知(不论是正常返回还是异常退出)。 * 环绕通知(around advice):包围一个连接点的通知,如方法调用,这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为,也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。   通过切入点匹配连接点的概念是AOP的关键,这使得AOP不同于其他仅仅提供拦截功能的旧技术。   环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,推荐使用尽可能简单的通知类型来实现需要的功能。例如,如果只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,若不在Joinpoint上调用用于环绕通知的proceed()方法,就不会有额外调用的问题。 3.1.3 AOP动态代理选择   AOP的基础是动态代理,Spring默认使用JDK动态代理作为AOP的代理。注意:JDK动态代理模式的代理对象返回类型只能是接口。   Spring也可以使用CGLIB动态代理,对于需要代理类但是没有定义接口的情况,使用CGLIB代理是很有必要的。如果一个业务对象并没有实现一个接口,默认使用CGLIB。   JDK代理或CGLIB代理对象,都是通过ProxyFactoryBean创建而成。 public class ProxyFactoryBean extends ProxyCreatorSupport   implements FactoryBean, BeanClassLoaderAware, BeanFactoryAware {   public Object getObject() throws BeansException {     initializeAdvisorChain();     if (isSingleton()) {       return getSingletonInstance();     }else {       return newPrototypeInstance();     }   } }   ProxyFactoryBean用于动态创建ProxyFactory对象,前面讲过FactoryBean模式,调用ProxyFactoryBean::getObject(),返回的对象是ProxyFactory。 public class ProxyFactory extends ProxyCreatorSupport {   public Object getProxy() {     return createAopProxy().getProxy();   }   public Object getProxy(ClassLoader classLoader) {     return createAopProxy().getProxy(classLoader);   } }   调用ProxyFactory::getProxy()即可返回动态代理。   注意:从Spring??3.2开始,不再需要导入cglib库文件,cglib库已经被打包在了spring- core下的org.springframework.cglib中了。在与hibernate或MyBatis集成时,注意解决cglib的版本冲突问题。 3.2 @AspectJ支持 3.2.1 @AspectJ介绍   @AspectJ是一种风格样式,可以把Java的普通类声明为一个切面。使用AspectJ需要aspectjweaver的支持。 3.2.2 autoproxying配置   @AspectJ 需要autoproxying配置,这样IoC容器启动时就可以查找AspectJ注解了。如下两种配置模式均可。   (1)Java类配置模式。 @Configuration @EnableAspectJAutoProxy public class AppConfig {}   (2)XML配置模式。 3.2.3 声明Aspect   切面中包含切入点、通知、引入等信息,因此@AspectJ开发从定义切面开始。一个系统中可以定义很多切面,每个切面中的内容相互独立。定义方法如下:   (1)新建一个Bean。 @Component public class DbProxy {}   (2)使用@Aspect声明一个切面。 @Component @Aspect public class DbProxy {}   @Aspect声明的类和普通类一样可以添加属性和方法,还可以包含切入点、通知等内容。 3.2.4 声明 Pointcut   切入点声明包含两部分:签名(由一个名字和多个参数组成)和切入点表达式。示例如下: @Component @Aspect public class DbProxy {    @Pointcut("execution(public * com.icss.biz.*.*(..))")    private void businessOperate() {} }   注意:   (1)示例中的@Pointcut("execution(public * com.icss.biz.*.*(..))")为切入点表达式。   (2)businessOperate()是签名,而且签名必须返回void。   (3)签名对应的方法不会被调用,需要的仅仅是方法的名字,因此方法中无须写任何 代码。   (4)签名方法可以使用任何可见性修饰符,如private、public等,因为签名可以被其他切面引用。   (5)可以把所有公用的切入点定义在一个切面中,供其他切面调用。 3.2.5 Pointcut表达式   1. Pointcut中的指示符   Spring AOP支持在切入点表达式中使用如下的AspectJ切入点指示符。 * execution:匹配方法执行的连接点,这是最常用的Spring的切入点指示符。 * within:限定匹配特定类型的连接点(使用Spring AOP时,在匹配的类型中定义方法的执行)。 * this:限定匹配特定的连接点(Spring AOP动态拦截的方法执行时),其中Bean reference(Spring AOP 代理)是指定类型的实例。 * target:限定匹配特定的连接点(Spring AOP动态拦截的方法执行时),其中目标对象(被代理的应用对象)是指定类型的实例。 * args:限定匹配特定的连接点(Spring AOP动态拦截的方法执行时),其中参数是指定类型的实例。 * @target:限定匹配特定的连接点(Spring AOP动态拦截的方法执行时),其中执行对象的类持有指定类型的注解。 * @args:限定匹配特定的连接点(Spring AOP动态拦截的方法执行时),其中实际传入参数的运行时类型持有指定类型的注解。 * @within:限定匹配特定的连接点,使用Spring AOP时,所执行方法所在的类型已指定注解。 * @annotation:限定匹配特定的连接点(Spring AOP动态拦截的方法执行时),其中连接点的主题持有指定的注解。   注意:对于JDK动态代理,只有public接口方法的调用能被拦截;对于cglib动态代理,public和protected方法调用都可以被拦截。Pointcut定义可以应用于任何non-public的方法。   2. 联合使用Pointcut表达式   可以使用&&、||、!把多个Pointcut表达式通过名字联合使用。示例如下: @Pointcut("execution(public * *(..))") private void anyPublicOperation() {} @Pointcut("within(com.xyz.someapp.trading..*)") private void inTrading() {}   把上面的两个Pointcut联合使用如下: @Pointcut("anyPublicOperation() && inTrading()") private void tradingOperation() {}   3. 共享通用的Pointcut表达式 @Aspect public class SystemArchitecture {    //用于业务层连接点    @Pointcut("execution(* com.xyz.someapp..service.*.*(..))")    public void businessService() {}    //用于数据访问层连接点    @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")    public void dataAccessOperation() {} }   Pointcut定义后一般在通知中调用,公用的切入点在其他切面中的通知上调用,通知稍后讲解。   4. execution指示器   在Spring AOP中,使用最多的就是execution指示器,定义格式如下: execution(修饰符 返回类型 方法名(参数) 异常) * 修饰符:常用public,也可使用*表示所有修饰符。 * 返回类型:最常用*,它代表了匹配任意的返回类型。 * 方法名:可以使用*,通配全部或部分名字。 * 方法参数:()表示无参;(..)表示0或任意多个参数;(*)表示一个参数,类型任意;(*, String),表示两个参数,第一个参数类型任意。   下面给出一些通用切入点表达式的例子。 * 任意公共方法的执行: execution(public * *(..)) * 任何一个名字以set开始的方法的执行: execution(* set*(..)) * AccountService接口定义的任意方法的执行: execution(* com.xyz.service.AccountService.*(..)) * 在service包中定义的任意方法的执行: execution(* com.xyz.service.*.*(..)) * 在service包或其子包中定义的任意方法的执行: execution(* com.xyz.service..*.*(..)) * 在service包中的任意连接点(在Spring AOP中只是方法执行): within(com.xyz.service.*) * 在service包或其子包中的任意连接点(在Spring AOP中只是方法执行): within(com.xyz.service..*) 3.2.6 声明advice    通知与Pointcut表达式关联,在Pointcut表达式关联匹配的方法前、方法后、方法环绕执行等。可以直接使用Pointcut表达式,也可以通过名字引用已定义好的Pointcut表达式。   1. 声明advice   1)前置通知   @Before表示前置通知,是Pointcut匹配的连接点方法在连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。   @Before中,通过名字引用公用的Pointcut表达式。参见3.2.5节Pointcut的定义,引用时需要使用“包名.类名.切入点签名”的格式,注意签名的可见性最好为public。   (1)在通知中调用其他切面中的Pointcut。 @Aspect public class BeforeExample {    @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")    public void doAccessCheck() {      // ...    } }   (2)在通知中直接定义Pointcut表达式。 @Aspect public class BeforeExample {    @Before("execution(* com.xyz.myapp.dao.*.*(..))")    public void doAccessCheck() {      // ...    } }   2)后置返回通知    @AfterRerurning表示后置通知,是Pointcut匹配的连接点方法正常完成后执行的通知(注意:方法正常执行,不能抛出任何异常)。 @Aspect public class AfterReturningExample {    @AfterReturning("com.xyz.myapp.SystemArchitecture.businessService()")    public void doAccessCheck() {      // ...    } }   3)后置异常通知    后置异常通知声明为@AfterThrowing。当Pointcut匹配的连接点方法的异常结束后,触发该通知。 @Aspect public class AfterThrowingExample {     @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")     public void doRecoveryActions() {       // ...   } }   4)最终通知    使用@After注解来声明后置通知,它翻译成最终通知更贴切,即finally advice。   不论一个连接点方法如何结束,是否存在异常,最终通知都会运行。最终通知必须准备处理正常返回和异常返回两种情况。   通常用@After来释放非托管资源,如数据库连接。 @Aspect public class AfterFinallyExample {    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")    public void doReleaseLock() {      // ...此处释放非托管资源    } }   5)环绕通知    环绕通知使用@Around注解来声明。在Pointcut匹配的连接点方法执行前和执行后都触发环绕通知。   环绕通知的第一个参数必须是 ProceedingJoinPoint类型,调用 ProceedingJoinPoint的proceed()方法,触发JoinPoint方法执行。 @Aspect public class AroundExample {   @Around("com.xyz.myapp.SystemArchitecture.businessService()")   public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {     // 开启观察     Object retVal = pjp.proceed();     // 停止观察     return retVal;   } }   2. 给advice传递参数    给通知传递参数时,org.aspectj.lang.JoinPoint常作为第一个参数使用。注意org.aspectj. lang.ProceedingJoinPoint是JoinPoint的子类,它只能用于环绕通知。   JoinPoint中有如下方法。 * getArgs():返回代理对象的方法参数。 * getTarget():返回目标对象。 * getSignature():返回被通知方法的信息。 * toString():打印被通知方法的有用信息。   (1)定义切面和通知。 import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Component @Aspect public class BeforeExample {    @Before("execution (* com.icss.biz.*.*(..))")    public void doAccessCheck(JoinPoint jp) {       System.out.println("before:" + jp.getTarget().toString());       System.out.println("before:" + jp.toString());       System.out.println("before:" + jp.getArgs()[0].toString());       System.out.println("before:" + jp.getSignature().toString());    } }   (2)代码测试: public static void main(String[] args) throws Exception{    UserBiz iuser = (UserBiz)SpringFactory.getBean("userBiz");    User user = iuser.login("admin", "123");      if(user != null) {      System.out.println("登录成功,身份是" + user.getRole());    }else {      System.out.println("登录失败");    }     }   (3)测试结果: INFO - Loading XML Bean definitions from class path resource [beans.xml] before:com.icss.biz.UserBiz@6647c2 before:execution(User com.icss.biz.UserBiz.login(String,String)) before:admin before:User com.icss.biz.UserBiz.login(String,String)   注意:导包时很容易出错,应该使用org.aspectj包。下面的JoinPoint和Joinpoint是有大小写区别的: import org.aopalliance.intercept.Joinpoint; //p为小写 import org.aspectj.lang.JoinPoint;      //P为大写 3.2.7 案例:StaffUser日志管理   基于AOP的日志管理,是AOP的典型应用场景之一。下面案例使用AspectJ实现了员工系统的自动日志管理。   (1)配置pom.xml。    org.springframework    spring-context    6.0.3    org.aspectj    aspectjweaver    1.9.19   (2)使用环绕通知配置日志代理。 @Component @Aspect public class LogProxy {      @Around("execution(public * com.icss.biz.*.*(..))")    public Object logging(ProceedingJoinPoint pjp) throws Throwable{         Object obj = null;           Log.logger.info(pjp.getSignature() + new Date().toString());       obj = pjp.proceed();       //注意必须要有返回值       Log.logger.info(pjp.toString() + new Date().toString());           return obj;    } }   (3)登录测试。 public static void main(String[] args) throws Exception{    UserBiz iuser = (UserBiz)SpringFactory.getBean("userBiz");    User user = iuser.login("admin", "123");      if(user != null) {      System.out.println("登录成功,身份是" + user.getRole());    }else {      System.out.println("登录失败");    } }   (4)测试结果如下: INFO - User com.icss.biz.UserBiz.login(String,String) UserBizMysql...login... UserDaoMysql...login... INFO - execution(User com.icss.biz.UserBiz.login(String,String)) execution(User com.icss.biz.UserBiz.login(String,String)) 登录成功,身份是1 3.2.8 案例:StaffUser数据库连接管理   使用AOP,可以实现所有业务层方法自动打开和关闭数据库。下面讲述操作步骤。   (1)使用@Before打开数据库,使用@After关闭数据库。 @Component @Aspect public class DbProxy {    @Pointcut("execution(public * com.icss.biz.*.*(..))")    private void businessOperate() {    }    @Before("businessOperate()")    public void openDataBase(JoinPoint jp) {      System.out.println("-----------打开数据库--------------");    }    @After("businessOperate()")    public void closeDataBase(JoinPoint jp) {      System.out.println(jp.toString());          System.out.println("-------------关闭数据库----------");    } }   (2)测试,即使出现异常,也要确保关闭数据库。 public static void main(String[] args) throws Exception{    UserBiz iuser = (UserBiz)SpringFactory.getBean("userBiz");    User user = iuser.login("admin", "123");      if(user != null) {      System.out.println("登录成功,身份是" + user.getRole());    }else {      System.out.println("登录失败");    } }   (3)异常测试结果。 -----------打开数据库-------------- INFO - User com.icss.biz.UserBiz.login(String,String) UserBizMysql...login... UserDaoMysql...login... execution(User com.icss.biz.UserBiz.login(String,String)) -------------关闭数据库---------- Exception in thread "main" java.lang.Exception: 异常测试...    at com.icss.dao.UserDaoMysql.login(UserDaoMysql.java:40)    at com.icss.biz.UserBiz.login(UserBiz.java:28) 3.3 基于XML的AOP配置   如果喜欢XML格式,Spring通过AOP命名标签提供了切面编程支持。与@AspectJ风格一致的Pointcut表达式和Advice,在XML中同样支持。在Spring的配置文件中,所有的切面和通知都必须定义在元素内部(context可以包含多个 )。   一个可以包含pointcut、advisor和aspect元素 (注意这三个元素必须按照这个顺序进行声明)。   警告:   (1)风格配置使得Spring的auto-proxying机制变得很笨重。   (2)如果已经通过 BeanNameAutoProxyCreator或类似的织入方案显式使用了auto- proxying,它可能会导致某些问题(如通知没有被织入)。   (3)推荐的使用模式是仅使用风格,或者仅使用AutoProxyCreator风格。 3.3.1 声明Aspect   使用来声明切面,通过ref可以引用支撑Bean。           ...        ...      注意:切面中要配置通知,通知需要代码实现,因此必须要有一个支撑Bean。 3.3.2 声明Pointcut   应该声明在内,这样其他aspect和advisor就可以共享这个Pointcut。      XML中的Pointcut表达式,与前面在@AspectJ中使用的Pointcut表达式语法完全一致。XML配置中的id就是切入点的签名(它和方法无关,就是个名称识别),expression为切入点表达式,也可以通过名字引用其他Pointcut。   示例如下:   (1)引用@AspectJ定义的切入点。      (2)引用XML中定义的切入点。                ...     3.3.3 声明Advice   在@AspectJ中定义的5个通知类型,XML中都支持,而且语意相同。   (1)声明前置通知。           (2)声明后置返回通知。       (3)声明后置异常通知。       (4)声明最终通知。        ...   (5)声明环绕通知。        ...   (6)在支撑Bean中定义通知要响应的方法。 public class ABean {      public void doAccessCheck(JoinPoint jp) {        }      public void doRecoveryActions(JoinPoint jp ) {        }      public void doReleaseLock() {        }      public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable{       return pjp.proceed();    } } 3.3.4 使用Advisor   Advisor(通知器)这个概念源于Spring对AOP的支持,在@AspectJ中没有等价内容。一个Advisor就是一个自包含的切面,Advisor中只能有一个通知。Aspect需要一个Bean的支持,Advisor不需要,这样配置更加简单。   示例1:                          示例2: //Advisor与事务通知策略协同使用                                          3.3.5 案例:StaffUser日志管理   本节使用XML配置切面、切入点、通知,使用日志跟踪员工系统的服务层与持久层方法的调用。   (1)配置pom.xml。     org.springframework    spring-context    6.0.3   org.aspectj   aspectjweaver   1.9.19   org.apache.logging.log4j   log4j-slf4j-impl   2.13.3        (2)编写日志处理代码。 public Object logging(ProceedingJoinPoint pjp) throws Throwable{      Object obj = null;    Log.logger.info(pjp.getSignature() + new Date().toString());    obj = pjp.proceed();      //注意必须要有返回值    Log.logger.info(pjp.toString() + new Date().toString());        return obj; }   (3)配置切面和Pointcut。                                (4)代码测试。 public static void main(String[] args) throws Exception{    UserBiz iuser = (UserBiz)SpringFactory.getBean("userBiz");    User user = iuser.login("admin", "123");      if(user != null) {       System.out.println("登录成功,身份是" + user.getRole());    }else {       System.out.println("登录失败");    }       }   测试结果: INFO - User com.icss.biz.UserBiz.login(String,String) UserBizMysql...login... UserDaoMysql...login... INFO - execution(User com.icss.biz.UserBiz.login(String,String)) execution(User com.icss.biz.UserBiz.login(String,String)) 登录成功,身份是1   (5)上述代码是给服务层打日志,现在增加持久层方法日志。在XML可以使用and或or连接多条件。                                 测试结果: INFO - User com.icss.biz.UserBiz.login(String,String) UserBizMysql...login... INFO - User com.icss.dao.IUserDao.login(String,String) UserDaoMysql...login... INFO - execution(User com.icss.dao.IUserDao.login(String,String)) INFO - execution(User com.icss.biz.UserBiz.login(String,String)) execution(User com.icss.biz.UserBiz.login(String,String)) 登录成功,身份是1 3.3.6 案例:StaffUser数据库连接管理   前面介绍了员工系统,下面使用XML配置切面、切入点、通知,实现数据库连接管理、自动打开数据库连接、自动关闭数据库连接。下面讲述操作步骤。   (1)编写打开、关闭数据库的代码。 public class DbProxy {      public void openDataBase(JoinPoint jp) {       System.out.println("-----------打开数据库--------------");    }    public void closeDataBase(JoinPoint jp) {       System.out.println(jp.toString());           System.out.println("-------------关闭数据库----------");    }   }   (2)编写XML配置信息。                                                (3)用户登录测试结果。 -----------打开数据库-------------- UserBizMysql...login... UserDaoMysql...login... -------------关闭数据库---------- 登录成功,身份是1 3.4 动态代理机制   Spring的 AOP一部分使用JDK动态代理,一部分使用CGLIB创建代理。如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。 若该目标对象没有实现任何接口,则会创建一个CGLIB代理。从Spring 3.2开始,cglib包已经被打入了spring-core包中。 3.4.1 静态代理   动态代理是在静态代理模式基础上发展起来的,因此学习动态代理之前,需要先理解静态代理模式。   1. 设计模式之静态代理   GOF静态代理模式为其他对象提供一种代理以控制对这个对象的访问。图3-1为代理模式的类图。 图3-1?代理模式   客户向代理发出请求,真正做事的是被代理者(见图3-2)。代理模式被广泛应用,如hibernate的懒加载机制。 图3-2?代理调用   2. 案例:明星与经纪人   电影明星和歌星都有经纪人,拍电影、演出、娱乐活动等都需要和经纪人联系,但实际参加演出的是明星。这里需要注意的是,代理人不是简单委托,他会从中做一些额外的事情。   (1)接口设计,定义明星的三个行为:拍电影、拍广告、参加活动。 public interface IStar {       public void playMovie();      //拍电影     public void playAd();       //拍广告     public void playActive();     //参加活动 }   (2)定义MovieStar业务类,实现IStar接口。 public class MovieStar implements IStar{    private String name;        public String getName() {       return name;    }    public MovieStar(String name) {       this.name = name;    }      public void playMovie() {       System.out.println(this.name + " 在拍电影...");        }        public void playAd() {       System.out.println(this.name + " 在拍广告...");        }      public void playActive() {       System.out.println(this.name + " 在参加活动...");          } }   (3)定义静态代理类。代理类与被代理类必须要实现相同的接口。代理人在被代理人实际参与的活动中,会做一些额外的事情。 public class StarProxy implements IStar{      private String name;    private MovieStar star;      public StarProxy(String name,MovieStar star) {         this.name = name;       this.star = star;       System.out.println(this.name + "成为" + star.getName()+"的经纪人");    }      public void playMovie() {         star.playMovie();         System.out.println(this.name + "协调合约与电影拍摄事宜...");    }      public void playAd() {       star.playAd();       System.out.println(this.name + "协调合约与广告拍摄事宜...");          }      public void playActive() {       star.playActive();           System.out.println(this.name + "协调合约与活动安排事宜...");      } }   (4)代码测试。   发起活动的都是代理者,实际执行的是被代理者。 public static void main(String[] args) {    MovieStarstar = new MovieStar("演员甲");        IStarproxy = new StarProxy("经纪人乙",star);    proxy.playActive();    proxy.playAd();    proxy.playMovie();     }   测试结果: 经纪人乙成为演员甲的经纪人 演员甲在参加活动... 经纪人乙协调合约与活动安排事宜... 演员甲在拍广告... 经纪人乙协调合约与广告拍摄事宜... 演员甲在拍电影... 经纪人乙协调合约与电影拍摄事宜... 3.4.2 JDK动态代理   使用JDK的动态代理机制可以实现自动记录日志功能。为了对比分析,首先用静态代理模式来实现日志管理功能,对比分析静态代理与动态代理的区别。   1. 案例:静态代理的日志实现       如下示例使用静态代理模式,为所有图书业务操作打日志。   (1)定义业务接口IBook和业务行为。 public interface IBook {      public void getBookInfo(); //获取图书信息    public void buyBook();    //买书    public void updateBook();  //更新图书信息 }   (2)业务实现类BookBiz,实现接口IBook。 public class BookBiz implements IBook{        public void getBookInfo() {       System.out.println("getBookInfo...");        }          public void buyBook() {       System.out.println("buyBook...");        }      public void updateBook() {       System.out.println("updateBook...");        } }   (3)定义代理类BookLog,它实现了IBook接口,同时在构造器中传入被代理的对象BookBiz。 public class BookLog implements IBook{      private BookBiz bookBiz;      public BookLog(BookBiz bookBiz) {       this.bookBiz = bookBiz;    }      public void getBookInfo() {       Log.logger.info("getBookInfo...begin...");       bookBiz.getBookInfo();       Log.logger.info("getBookInfo...end...");        }      public void buyBook() {       Log.logger.info("buyBook...begin...");       bookBiz.buyBook();       Log.logger.info("buyBook...end...");        }      public void updateBook() {       Log.logger.info("updateBook...begin...");       bookBiz.updateBook();       Log.logger.info("updateBook...end...");        } }   (4)代码测试。 public static void main(String[] args) {            IBook ibook = new BookLog(new BookBiz());    ibook.buyBook();     }   测试结果如下: INFO - buyBook...begin... buyBook... INFO - buyBook...end...   提问:如果业务接口IUser中的方法也需要实现自动日志功能,怎么办?继续使用静态代理模式,需要给IUser增加UserLog实现类,这样是不是太烦琐了,如何解决? public interface IUser {      public void regist(User user) throws Exception;      public void login(String uname,String pwd) throws Exception;   }   2. Proxy类和InvocationHandler接口   java.lang.reflect.Proxy是JDK 提供的工具类。Proxy提供了创建动态代理对象的方法,它也是创建所有动态代理类的超类。 public class Proxy implements java.io.Serializable {      public static Object newProxyInstance(ClassLoader loader,                Class[] interfaces, InvocationHandler h){} }   Proxy.newProxyInstance()返回的对象就是动态代理对象,示例如下: InvocationHandler handler = new MyInvocationHandler(...); Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),                    new Class[] { Foo.class }, handler);   3. 案例:JDK动态代理日志   使用java.lang.reflect .Proxy实现日志的动态代理,可以给所有业务类增加日志功能。创建的每个动态代理类都是对Proxy的包装,有专一的用途,如日志代理、事务代理、权限代理等。   (1)编写日志动态代理类,实现InvocationHandler接口。 public class LogDynamic implements InvocationHandler{      private Object target;      /**    * 传入被代理对象,返回代理对象    */    public Object bind(Object target) {      this.target = target;         return Proxy.newProxyInstance(target.getClass().getClassLoader(),          target.getClass().getInterfaces(),this);    }      /**     * 当调用被代理对象上的方法时,这个invoke方法会被自动激活     */     public Object invoke(Object proxy, Method method, Object[] args)                         throws Throwable {           Log.logger.info(method.getName() + " beging...");           //动态调用被代理对象的方法       Object result = method.invoke(target, args);           Log.logger.info(method.getName() + " ending...");           return result;    } }   (2)创建LogDynamic动态代理对象,分别绑定UserBiz和BookBiz对象进行测试。 public static void main(String[] args)throws Exception{        LogDynamic logd = new LogDynamic();        IUser proxy = (IUser)logd.bind(new UserBiz());      proxy.login("tom", "123445");          IBook bookProxy = (IBook)logd.bind(new BookBiz());    bookProxy.buyBook(); }   (3)测试结果显示,IUser和IBook对象绑定后,都可以动态打印日志。 INFO - login beging... tom is logging... INFO - login ending... INFO - buyBook beging... buyBook... INFO - buyBook ending...     总结:只需要编写一个LogDynamic日志动态代理类,业务对象如new UserBiz()、new BookBiz()等与动态代理类完全解耦。绑定其他业务对象,仍然可以输出日志信息。 3.4.3 项目案例:自动管理数据库连接   前面几节的员工系统使用的都是伪代码。从本节开始,连接真实的数据库。首先,考虑如何管理数据库的连接,实现自动打开、关闭数据库的功能。注意:在实际项目中,打开数据库的位置一般在持久层,但是关闭数据库连接的位置在服务层或持久层。为何这样使用数据库连接,后面将详细介绍。   员工系统需要同时支持Oracle和MySQL,代码演示时以MySQL为主。   1. 配置MySQL数据库环境   (1)安装MySQL8数据库。   (2)创建库: create database staff;   打开库: use staff;   (3)创建表: create table TStaff (     sno           varchar(9) not null,     name          varchar(30),    birthday       date,