设计模式笔记—代理模式
一、动态代理
1.1、Spring实现AOP
横切关注点:跨越应用程序多个模块的方法或功能。(软件系统,可以看做由一组关注点即业务或功能或方法组成。其中,直接的业务关注点是直切关注点,而为直切关注点服务的,就是横切关注点。)即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。
切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类。
通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
目标(Target):被通知对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
切入点(PointCut):切面通知执行的“地点”的定义。
连接点(JointPoint):与切入点匹配的执行点。
下面示意图:
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
1.1、第一步:建好Maven项目后导入如下依赖包:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.0.RELEASE</version> </dependency>
1.1.2第二步:定义通知(实现该通知接口)
前置通知:org.springframework.aop.MethodBeforeAdvice;
package com.chenjiahao.spring.springdynamicproxy; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; /** * 前置通知 * */ public class BeforeAdvice implements MethodBeforeAdvice { /** * method 方法信息 * args 参数 * target 被代理的目标对象 */ public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("----前置通知----"); } }
后置通知:org.springframework.aop.AfterReturningAdvice;
package com.chenjiahao.spring.springdynamicproxy; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; /** * 后置通知 * */ public class AfterAdvice implements AfterReturningAdvice { /* * returnValue 返回值 * method 被调用的方法 * args 方法参数 * target 被代理对象 */ public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("----后置通知----"); } }
环绕通知:org.aopalliance.intercept.MethodInterceptor;
package com.chenjiahao.spring.springdynamicproxy; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 环绕通知 * 方法拦截器 * */ public class SurroundAdvice implements MethodInterceptor { public Object invoke(MethodInvocation mi) throws Throwable { /** *前置横切逻辑 * getMethod() 获得方法 * getThis() 获得被调用的的对象 * getArguments() 获得参数 * */ System.out.println("----环绕方法前置----:方法" + mi.getMethod() + " 被调用在对象" + mi.getThis() + "上,参数 " + mi.getArguments()); //方法调用 Object result = mi.proceed(); //后置横切逻辑 System.out.println("----环绕方法后置----:返回值:"+ result); return result; } }
1.1.3、第三步:封装动态代理类
package com.chenjiahao.spring.springdynamicproxy; import org.springframework.aop.framework.ProxyFactory; /** * 动态代理类 * */ public class SpringDynamic { /** * target 传入的实际对象 * */ public static<T> T getProxy(T target){ //创建Spring代理工厂 ProxyFactory proxyFactory=new ProxyFactory(); //设置代理对象 proxyFactory.setTarget(target); //添加通知方法 可以添加多个 proxyFactory.addAdvice(new SurroundAdvice()); //返回代理对象 Object obj=proxyFactory.getProxy(); return (T)obj; } }
1.1.4、第四步:测试
示例:
package com.chenjiahao.spring.springdynamicproxy; public class Client { public static void main(String[] args) { IMant mant = SpringDynamic.getProxy(new Mant()); mant.add(2, 4); } }
结果:
1.2、使用IoC配置实现AOP
1.2.1、第一步:建好Maven项目后导入如下依赖包:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.0.RELEASE</version> </dependency>
1.2.2、第二步:创建IoC配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--目标对象--> <bean id="target" class="com.chenjiahao.spring.iocdynamicproxy.Mant"></bean> <!--通知 如:前置通知 后置通知 环绕通知等...--> <bean id="advice" class="com.chenjiahao.spring.iocdynamicproxy.SurroundAdvice"></bean> <!--代理对象 --> <!--interceptorNames 通知数组 --> <!--p:target-ref 被代理的对象--> <!--p:proxyTargetClass 被代理对象是否为一个类,如果是则使用cglib,否则使用jdk动态代理 --> <bean id="dynamicproxy" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="advice" p:target-ref="target" p:proxyTargetClass="true"> </bean> </beans>
1.2.3、第三步:定义通知
package com.chenjiahao.spring.iocdynamicproxy; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 环绕通知 * 方法拦截器 * */ public class SurroundAdvice implements MethodInterceptor { public Object invoke(MethodInvocation mi) throws Throwable { /** *前置横切逻辑 * getMethod() 获得方法 * getThis() 获得被调用的的对象 * getArguments() 获得参数 * */ System.out.println("----环绕方法前置----:方法" + mi.getMethod() + " 被调用在对象" + mi.getThis() + "上,参数 " + mi.getArguments()); //方法调用 Object result = mi.proceed(); //后置横切逻辑 System.out.println("----环绕方法后置----:返回值:"+ result); return result; } }
1.2.4、第四步:测试
示例:
package com.chenjiahao.spring.iocdynamicproxy; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Client { public static void main(String[] args) { //获得容器 ApplicationContext ctx=new ClassPathXmlApplicationContext("springcfgproxy.xml"); IMant mant= (IMant) ctx.getBean("dynamicproxy"); mant.add(2, 4); mant.sub(2, 4); mant.mul(2, 4); mant.div(2, 4); } }
结果:
1.3、使用XML配置Spring AOP切面
1.3.1、第一步:Maven导入架包
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency>
1.3.2、第二步:定义通知类
package com.chenjiahao.spring.springxmldynamicprocy; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; public class Advices { //前置通知 public void beforeMethod(JoinPoint joinPoint) { System.out.println("----前置通知----"); } //后置通知 public void afterMethod(JoinPoint joinPoint) { System.out.println("----后置通知----"); } //返回值通知 public void afterReturning(JoinPoint joinPoint, Object result) { System.out.println("----返回值通知----"+result); } //抛出异常通知 //在方法出现异常时会执行的代码可以访问到异常对象,可以指定在出现特定异常时在执行通知代码 public void afterThrowing(JoinPoint joinPoint, Exception ex) { System.out.println("----抛出异常通知----"+ex.getMessage()); } //环绕通知 //环绕通知需要携带ProceedingJoinPoint类型的参数 //环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。 //而且环绕通知必须有返回值,返回值即为目标方法的返回值 public Object aroundMethod(ProceedingJoinPoint pjd) { Object object = null; try { System.out.println("----环绕通知前置----"); object = pjd.proceed(); System.out.println("----环绕通知后置----"); } catch (Throwable throwable) { throwable.printStackTrace(); } return object; } }
1.3.3、第三步:配置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: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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!--被代理对象--> <bean id="math" class="com.chenjiahao.spring.springxmldynamicprocy.Mant"></bean> <!--定义的通知--> <bean id="advice" class="com.chenjiahao.spring.springxmldynamicprocy.Advices"></bean> <!--AOP配置--> <!-- proxy-target-属性class表示被代理的类是否为一个没有实现接口的类,Spring会依据实现了接口则使用JDK内置的动态代理,如果未实现接口则使用cblib --> <aop:config proxy-target-class="true"> <!-- 切面配置 --> <!--ref表示通知对象的引用 --> <aop:aspect ref="advice"> <!-- 配置切入点(横切逻辑将注入的精确位置) --> <!--execution 第一个参数为访问修饰符如 public...等--> <!--com.chenjiahao.spring.springxmldynamicprocy.Mant.*(..)) 包下面的Mant类中的所有方法任易参数--> <aop:pointcut expression="execution(* com.chenjiahao.spring.springxmldynamicprocy.Mant.*(..))" id="pointcut1"/> <!--声明通知,method指定通知类型,pointcut指定切点,就是该通知应该注入那些方法中 --> <aop:before method="beforeMethod" pointcut-ref="pointcut1"/> <aop:after method="afterMethod" pointcut-ref="pointcut1"/> <aop:around method="aroundMethod" pointcut="execution(* com.chenjiahao.spring.springxmldynamicprocy.Mant.*(..))"></aop:around> <!--throwing 方法执行后的返回结果--> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut1" throwing="ex"></aop:after-throwing> </aop:aspect> </aop:config> </beans>
参数解读:
aop:config:AOP配置
proxy-target-class:表示被代理的类是否为一个没有实现接口的类,Spring会依据实现了接口则使用JDK内置的动态代理,如果未实现接口则使用cblib
aop:aspect:切面配置
ref:表示通知对象的应用(连接定义好的通知类)
aop:pointcut:切点配置 表达式只需要选其中一个
- execution:一般用于指定方法的执行,用的最多。
- within:指定某些类型的全部方法执行,也可用来指定一个包。
- this:Spring Aop是基于代理的,生成的bean也是一个代理对象,this就是这个代理对象,当这个对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
- target:当被代理的对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
- args:当执行的方法的参数是指定类型时生效。
- @target:当代理的目标对象上拥有指定的注解时生效。
- @args:当执行的方法参数类型上拥有指定的注解时生效。
- @within:与@target类似,看官方文档和网上的说法都是@within只需要目标对象的类或者父类上有指定的注解,则@within会生效,而@target则是必须是目标对象的类上有指定的注解。而根据笔者的测试这两者都是只要目标类或父类上有指定的注解即可。
- @annotation:当执行的方法上拥有指定的注解时生效。
- bean:当调用的方法是指定的bean的方法时生效。
(1) execution
execution是使用的最多的一种Pointcut表达式,表示某个方法的执行,其标准语法如下。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
modifiers-pattern表示方法的访问类型,public等;
ret-type-pattern表示方法的返回值类型,如String表示返回类型是String,“*”表示所有的返回类型;
declaring-type-pattern表示方法的声明类,如“com.elim..*”表示com.elim包及其子包下面的所有类型;
name-pattern表示方法的名称,如“add*”表示所有以add开头的方法名;
param-pattern表示方法参数的类型,name-pattern(param-pattern)其实是一起的表示的方法集对应的参数类型,如“add()”表示不带参数的add方法,“add(*)”表示带一个任意类型的参数的add方法,“add(*,String)”则表示带两个参数,且第二个参数是String类型的add方法;
throws-pattern表示异常类型;其中以问号结束的部分都是可以省略的。
- 1、“execution(* add())”匹配所有的不带参数的add()方法。
- 2、“execution(public * com..*.add*(..))”匹配所有com包及其子包下所有类的以add开头的所有public方法。
- 3、“execution(* *(..) throws Exception)”匹配所有抛出Exception的方法。
(2) within
within是用来指定类型的,指定类型中的所有方法将被拦截。
- 1、“within(com.spring.aop.service.UserServiceImpl)”匹配UserServiceImpl类对应对象的所有方法外部调用,而且这个对象只能是UserServiceImpl类型,不能是其子类型。
- 2、“within(com.elim..*)”匹配com.elim包及其子包下面所有的类的所有方法的外部调用。
(3) this
Spring Aop是基于代理的,this就表示代理对象。this类型的Pointcut表达式的语法是this(type),当生成的代理对象可以转换为type指定的类型时则表示匹配。基于JDK接口的代理和基于CGLIB的代理生成的代理对象是不一样的。
- 1、“this(com.spring.aop.service.IUserService)”匹配生成的代理对象是IUserService类型的所有方法的外部调用。
(4) target
Spring Aop是基于代理的,target则表示被代理的目标对象。当被代理的目标对象可以被转换为指定的类型时则表示匹配。
- 1、“target(com.spring.aop.service.IUserService)”则匹配所有被代理的目标对象能够转换为IUserService类型的所有方法的外部调用。
(5) args
args用来匹配方法参数的。
- 1、“args()”匹配任何不带参数的方法。
- 2、“args(java.lang.String)”匹配任何只带一个参数,而且这个参数的类型是String的方法。
- 3、“args(..)”带任意参数的方法。
- 4、“args(java.lang.String,..)”匹配带任意个参数,但是第一个参数的类型是String的方法。
- 5、“args(..,java.lang.String)”匹配带任意个参数,但是最后一个参数的类型是String的方法。
(6) @target
@target匹配当被代理的目标对象对应的类型及其父类型上拥有指定的注解时。
- 1、“@target(com.spring.support.MyAnnotation)”匹配被代理的目标对象对应的类型上拥有MyAnnotation注解时。
(7) @args
@args匹配被调用的方法上含有参数,且对应的参数类型上拥有指定的注解的情况。
- 1、“@args(com.spring.support.MyAnnotation)”匹配方法参数类型上拥有MyAnnotation注解的方法调用。如我们有一个方法add(MyParam param)接收一个MyParam类型的参数,而MyParam这个类是拥有注解MyAnnotation的,则它可以被Pointcut表达式“@args(com.elim.spring.support.MyAnnotation)”匹配上。
(8) @within
@within用于匹配被代理的目标对象对应的类型或其父类型拥有指定的注解的情况,但只有在调用拥有指定注解的类上的方法时才匹配。
- 1、“@within(com.spring.support.MyAnnotation)”匹配被调用的方法声明的类上拥有MyAnnotation注解的情况。比如有一个ClassA上使用了注解MyAnnotation标注,并且定义了一个方法a(),那么在调用ClassA.a()方法时将匹配该Pointcut;如果有一个ClassB上没有MyAnnotation注解,但是它继承自ClassA,同时它上面定义了一个方法b(),那么在调用ClassB().b()方法时不会匹配该Pointcut,但是在调用ClassB().a()时将匹配该方法调用,因为a()是定义在父类型ClassA上的,且ClassA上使用了MyAnnotation注解。但是如果子类ClassB覆写了父类ClassA的a()方法,则调用ClassB.a()方法时也不匹配该Pointcut。
(9) @annotation
@annotation用于匹配方法上拥有指定注解的情况。
- 1、“@annotation(com.spring.support.MyAnnotation)”匹配所有的方法上拥有MyAnnotation注解的方法外部调用。
(10) bean
bean用于匹配当调用的是指定的Spring的某个bean的方法时。
- 1、“bean(abc)”匹配Spring Bean容器中id或name为abc的bean的方法调用。
- 2、“bean(user*)”匹配所有id或name为以user开头的bean的方法调用。
(11)表达式组合
表达式的组合其实就是对应的表达式的逻辑运算,与、或、非。可以通过它们把多个表达式组合在一起。
- 1、“bean(userService) && args()”匹配id或name为userService的bean的所有无参方法。
- 2、“bean(userService) || @annotation(MyAnnotation)”匹配id或name为userService的bean的方法调用,或者是方法上使用了MyAnnotation注解的方法调用。
- 3、“bean(userService) && !args()”匹配id或name为userService的bean的所有有参方法调用。
1.3.4、第四部:测试
示例:
package com.chenjiahao.spring.springxmldynamicprocy; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Client { public static void main(String[] args) { //获得容器 ApplicationContext ctx=new ClassPathXmlApplicationContext("springxmlcfgdynameproxy.xml"); IMant mant= (IMant) ctx.getBean("math"); mant.add(2, 0); mant.sub(2, 0); mant.mul(2, 0); mant.div(2, 0); } }
结果: