以尚硅谷的教学代码为例:
/* * 动态代理 * * 代理模式: * 1、主题接口 * 2、被代理类 * 3、代理类(变化) * * 代理类分两个部分 * 1、 * 不直接写代理类,而是写一个“代理工作处理器”的类。 * 代理工作处理器:这个代理类要替被代理者完成xx事。 * 这个代理工作处理器必须实现一个接口:InvocationHandler * 2、动态的生成代理类及其它的对象 * 这个时候就需要借助java.lang.reflect.Proxy * (Proxy 提供用于创建动态代理类和实例的静态方法,它还是这些方法创建的所有动态代理类的超类) * static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) * 第一个参数:传入被代理者的类加载器 * 第二个参数:传入被代理者实现的接口们 * 第三个参数:传入代理者要替被代理者完成得工作的处理器对象 * * 不同的类加载器加载的Class,JVM认为是不同的类型。 */ public class TestDynamicProxy { public static void main (String[] args) { //(1)创建被代理者的对象 UserDao u = new UserDaoImpl(); //(2)获取被代理者的类加载器对象 ClassLoader loader = u.getClass().getClassLoader(); //(3)获取被代理者实现的接口们 Class<?>[] interfaces = u.getClass().getInterfaces(); //(4)创建代理者要替被代理者完成的工作的处理器对象 Handler h = new Handler(u);//传入被代理者 //动态创建代理类和它的对象 //得到的就是代理类的对象,代理类在内存中自动生成 UserDao ud = (UserDao) Proxy.newProxyInstance(loader,interfaces,h); ud.add(); } } //1.代理接口 interface UserDao{ void add(); } //2.被代理类 class UserDaoImpl implements UserDao{ @Override public void add() { System.out.println("add method running...."); } } //3.代理工作处理器 class Handler implements InvocationHandler{ private Object target; public Handler(Object target) { super(); this.target = target; } /* * 这个方法: * (1)它不是由程序员手动调用的,这个方法的代码会被编译器自动生成到代理类的对应方法中,当你调用代理类的方法时,自动执行这个方法 * (2)参数列表 * 第一参数:proxy 代理类对象 * 第二个参数:method 代理类要执行的真正的方法 * 第三个参数:给 method 方法的实参列表,如果有的话 * (3)编写代理类要替被代理者完成的工作 * 返回值:method方法的返回值,就是invoke的返回值,或者说invoke方法的返回值将来就是作为method方法的返回值 * 例如:给所有方法都增加一个功能,统计该方法的运行时间 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.currentTimeMillis(); System.out.println("开始执行"+ method.getName()+"方法"); System.out.println("代理类型"+ proxy.getClass().getName()); //执行真正的业务代码 Object retuenValue = method.invoke(target,args); long end = System.currentTimeMillis(); System.out.println("结束执行"+ method.getName() +"方法"); System.out.println("耗时:" + (end-start)); return retuenValue; } }Target类
public class Target implements TargetInerface { public void save() { System.out.println("save running..."); } }方法增强类
public class Advice { public void before(){ System.out.println("前置增强..."); } public void afterReturning(){ System.out.println("后置增强..."); } }动态代理 这里要注意:
动态生成的代理对象proxy要用他的父类如:TargetInterface或Object接收,否则会报错 public class ProxyText { public static void main(String[] args) { //获得目标对象 final Target target = new Target(); //获得增强对象 final Advice advice = new Advice(); //返回值 就是动态生成的代理对象 TargetInerface proxy = (TargetInerface) Proxy.newProxyInstance( target.getClass().getClassLoader(),//目标对象类加载器 target.getClass().getInterfaces(),//目标对象相同的接口字节码对象数组 new InvocationHandler() { //调用代理对象的任何方法 实质执行的都是invoke方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.before();//前置增强 Object invoke = method.invoke(target, args);//执行目标方法 advice.afterReturning();//后置增强 return invoke; } } ); proxy.save(); } }以黑马课程代码案例为例 Target类
public class Target { public void save() { System.out.println("save running..."); } }增强类
public class Advice { public void before(){ System.out.println("前置增强..."); } public void afterReturning(){ System.out.println("后置增强..."); } }测试类
public class ProxyText { public static void main(String[] args) { //获得目标对象 final Target target = new Target(); //获得增强对象 final Advice advice = new Advice(); //返回值 就是动态生成的代理对象 基于cglib //1、创建增强器 Enhancer enhancer = new Enhancer(); //2、给增强器设置父类 enhancer.setSuperclass(Target.class); //3、设置回调 enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { advice.before();//执行前置 method.invoke(target,args);//执行目标 advice.afterReturning();//执行后置 return null; } }); //4、创建代理对象 //这里可以用Target接,因为Proxy是基于Target生成的子类 Target proxy = (Target) enhancer.create(); proxy.save(); } }注意:
访问修饰符可以省略 execution(public void com.itheima.aop.Target.method()) 返回值类型、包名、类名、方法名可以使用星号* 代表任意 execution(void com.itheima.aop.Target.*(..)) 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类 execution(* com.itheima.aop.*.*(..)) execution(* com.itheima.aop..*.*(..)) 参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表 execution(* *..*.*(..)) 通知的类型 通知的配置语法: <aop:通知类型 method=“切面类中方法名” pointcut=“切点表达式"></aop:通知类型>. 通知的类型: 前置通知,aop:before 用于配置前置通知。指定增强的方法在切入点方法之前执行后置通知,aop:after-returning 用于配置后置通知。指定增强的方法在切入点方法之后执行环绕通知,aop:around 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行 注意:环绕通知的增强方法略有不同 //Proceeding JoinPoint:正在执行的连接点 == 切点 public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕前增强..."); Object proceed = pjp.proceed();//切点方法 System.out.println("环绕后增强..."); return proceed; } <aop:config> <!--声明切面--> <aop:aspect ref="myAspect"> <!--切面:切点+通知--> <aop:around method="around" pointcut="execution(public void com.itheima.aop.Target.save())"/> </aop:aspect> </aop:config> 异常抛出通知,aop:throwing 用于配置异常抛出通知。指定增强的方法在出现异常时执行最终通知,aop:after 用于配置最终通知。无论增强方式执行是否有异常都会执行 切点表达式的抽取 当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。 <aop:config> <!--声明切面--> <aop:aspect ref="myAspect"> <!--切面:切点+通知--> <aop:pointcut id="myPointcut" expression="execution(public void com.itheima.aop.Target.save())"/> <aop:before method="before" pointcut-ref = "myPointcut"/> <aop:around method="around" pointcut-ref = "myPointcut"/> </aop:aspect> </aop:config>切面类
@Component("myAspect") public class MyAspect { public void before(){ System.out.println("前置增强......"); } //Proceeding JoinPoint:正在执行的连接点 == 切点 public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕前增强..."); Object proceed = pjp.proceed();//切点方法 System.out.println("环绕后增强..."); return proceed; } } 在切面类中使用注解配置织入关系 首先需要设置切面类:@Aspect 然后设置通知类型:@Before(“execution([修饰符] 返回值类型 包名.类名.方法名(参数类型,…))”) @Component("myAspect") @Aspect public class MyAspect { @Before(value = "execution(* com.itheima.anno.*.*(..))") public void before(){ System.out.println("前置增强......"); } //Proceeding JoinPoint:正在执行的连接点 == 切点 public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕前增强..."); Object proceed = pjp.proceed();//切点方法 System.out.println("环绕后增强..."); return proceed; } } 在配置文件中开启组件扫描和 AOP 的自动代理 首先要设置aop和context的工作空间:xmlns:context=“http://www.springframework.org/schema/context” http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 然后开启组件扫描和AOP的自动代理: <context:component-scan base-package = “com.itheima.anno”/>和<aop:aspectj-autoproxy > </aop:aspectj-autoproxy> <?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:aop="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--开启组件扫描--> <context:component-scan base-package="com.itheima.anno"/> <!--aop的自动代理--> <aop:aspectj-autoproxy/> </beans> 测试 代码演示 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext-anno.xml") public class AnnoTest { @Autowired private TargetInterface target; @Test public void test1(){ target.save(); } } 注解通知的类型 通知的配置语法:@通知注解(“切点表达式") 前置通知:@Before 用于配置前置通知。指定增强的方法在切入点方法之前执行后置通知: @AfterReturning 用于配置后置通知。指定增强的方法在切入点方法之后执行环绕通知:@Around 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行异常抛出通知:@AfterThrowing 用于配置异常抛出通知。指定增强的方法在出现异常时执行最终通知:@After 用于配置最终通知。无论增强方式执行是否有异常都会执行 切点表达式的抽取 同 xml 配置 aop 一样,我们可以将切点表达式抽取。抽取方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在在增强注解中进行引用。 @Component("myAspect") @Aspect public class MyAspect { @Before(value = "MyAspect.myPoint()") public void before(){ System.out.println("前置增强......"); } @Around(value = "MyAspect.myPoint()") //Proceeding JoinPoint:正在执行的连接点 == 切点 public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕前增强..."); //获取当前执行方法所需要的的参数 Object[] args = pjp.getArgs(); Object proceed = pjp.proceed(args);//切点方法 System.out.println("环绕后增强..."); return proceed; } @Pointcut("execution(* com.itheima.anno.*.*(..))") public void myPoint(){} }本文用来复习用~ 内容引用1 内容引用2
部分内容参考尚硅谷教学视频 ↩︎
部分内容参考黑马程序员教学视频 ↩︎
