整体逻辑是静态代理---》JDK原生动态代理---》spring框架实现动态代理自动选择用JDK或者cglib二、动态代理JDK原生首先静态代理的优点动态代理全都有并且修正了其缺点。一个代理对象直接代理一个接口可以代理多个类只要类实现了这个接口就行。1.动态代理的机制是反射,这也是动态代理难以理解的地方。2. 代理类是Proxy.newProxyInstance() 动态生成的不是我们直接写好的3. 动态代理分为两大类基于接口的动态代理基于类的动态代理基于接口 JDK 原生动态代理我们学这个需要了解两个类java.lang.reflect.Proxy类静态方法Proxy.newProxyInstance() 动态生成一个代理实例java.lang.reflect.InvocationHandler接口调用处理程序要重写invoke方法基于类cglibjava字节码实现javasist2.1 JDK原生实现动态代理的例子接口定义增删改查4个抽象方法package com.kuang.service; /** * 业务接口 */ public interface IService { void add(); void delete(); void modify(); void search(); }接口实现类一增删改查春晚小品的业务类package com.kuang.service; /** * 增删改查春晚小品的业务类 */ public class ComedyServiceImpl implements IService{ public void add() { System.out.println(增加一个春晚小品); } public void delete() { System.out.println(删除一个春晚小品); } public void modify() { System.out.println(修改一个春晚小品); } public void search() { System.out.println(查找一个春晚小品); } }接口实现类二增删改查春晚歌曲的业务类package com.kuang.service; /** * 增删改查春晚歌曲的业务类 */ public class SongServiceImpl implements IService{ public void add() { System.out.println(增加一个春晚歌曲); } public void delete() { System.out.println(删除一个春晚歌曲); } public void modify() { System.out.println(修改一个春晚歌曲); } public void search() { System.out.println(查找一个春晚歌曲); } }InvocationHandler的实现类可以看作动态代理的模板package com.kuang.service; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 处理类的【模板】 */ public class MyInvocationHandler implements InvocationHandler { //1.被代理的角色的接口 IService target; public void setService(IService target) { this.target target; } //2.动态生成代理对象 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this); } //3.处理代理对象并返回结果 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result method.invoke(target, args); return result; } //4.代理自己要干的一系列事情写在这里 }客户端package com.kuang.service; import java.lang.reflect.Method; public class Client { public static void main(String[] args) { ComedyServiceImpl comedyService new ComedyServiceImpl(); MyInvocationHandler handler new MyInvocationHandler(); //设置被代理的真实角色 handler.setService(comedyService); //动态生成代理对象 IService proxy (IService) handler.getProxy(); proxy.add(); //---调用invoke proxy.delete(); proxy.modify(); proxy.search(); System.out.println(------------------); SongServiceImpl songService new SongServiceImpl(); //设置被代理的真实角色 handler.setService(songService); //动态生成代理对象 proxy (IService) handler.getProxy(); //这不也是重新new了一个代理对象吗 proxy.add(); proxy.delete(); proxy.modify(); proxy.search(); } }考虑一个问题使用动态代理如果要增加一个打印日志业务怎么加呢应该把增加的业务写在InvocationHandler实现类MyInvocationHandler的invoke方法里。我们依然调用add(方法但是多做了两件事情。考虑一个问题使用动态代理如果要增加一个打印日志业务怎么加呢如上实现的其实就是面向切面编程AOP三、什么是面向切面编程8个controller业务逻辑写好了我不想动但我想让这8个controller执行前用日志打印出请求报文的具体内容就写个一个切面切进去一个切面切8个controller大概就这么个意思。也可以用expression表达式指定只切入部分controller。优点利用AOP可以对业务逻辑的各个部分进行隔离从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性同时提高了开发的效率。切入点可以把切入的地方理解为切入点比如某个方法调用前、调用后方法抛出异常后对类成员的访问以及异常处理程序块的执行等现在我们不用手动实现而是用spring去实现AOP。3.1、AOP环境1. 导入AOP包之前已经导过spring-aop包了还需要下面这个dependency groupIdorg.aspectj/groupId artifactIdaspectjweaver/artifactId version1.9.4/version /dependency2. 导入aop约束直接照着beans的约束改就行3.2、spring实现AOP的方式一—— spring API编程式1. 有一个增删改查接口和它的实现类是一个字都不能改的package com.kuang.service; public interface IService { public void add(); public void delete(); public void modify(); public void search(); }package com.kuang.service; public class ServiceImpl implements IService{ public void add() { System.out.println(增加一个神马东西); } public void delete() { System.out.println(删除一个神马东西); } public void modify() { System.out.println(修改一个神马东西); } public void search() { System.out.println(搜索一个神马东西); } }2. 现在我们想在增、删、改、查的方法前面和后面增加输出日志的操作。LogBefore.java继承MethodBeforeAdvice接口也可以继承其他Advice接口实现before方法package com.kuang.log; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class LogBefore implements MethodBeforeAdvice { /** * * param method :要切入的方法真实对象的方法 * param args方法的参数 * param target 真实对象 * throws Throwable */ public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(log-----准备执行target.getClass().getName()的method.getName()方法); } }LogAfter.java同理继承接口实现after方法import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; public class LogAfter implements AfterReturningAdvice {4 /** * * param returnValue 方法的返回值 * param method 切入的方法 * param args 方法的参数 * param target 真实对象 * throws Throwable */ public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println(log-----method.getName()方法执行完毕返回值为returnValue.toString()); } }到现在为止我们只是写了1个接口和3个类如何把后2个类切入进去呢3. 先把这些类都注册到spring中然后配置AOP!--配置aop-- aop:config !--切入点expression表达式描述切入的位置这里的意思是只在ServiceImpl类的add方法切入-- aop:pointcut idpointcut1 expressionexecution(* com.kuang.service.ServiceImpl.add(..)) / !-环绕增加 -- aop:advisor advice-reflogBefore pointcut-refpointcut1/ aop:advisor advice-reflogAfter pointcut-refpointcut1/ /aop:config下面详细解释1expression表达式execution(* * * * .. *) 6个位置分别表示 1方法权限可以没有 2返回值类型 3包名类名可以没有 4方法名 5参数 6抛出异常可以没有举几个例子1) 任意公共方法 PS这里省略了全路径名和抛出异常 execution(public * *(..)) 2) 以set开头的任意方法 PS这里省略了权限、全路径名和抛出异常 execution(* set*(..)) 3)AccountService类中的所有方法 execution(* com.xyz.service.AccountService.*(..)) 4com.xyz.service包中所有类中任意方法不包含子包中的类 execution(* com.xyz.service.*.*(..)) 5com.xyz.service包或者子包中定义的所有方法 execution(* com.xyz.service..*.*(..))2aop:advisor4. 测试public class MyTest { public static void main(String[] args) { ApplicationContext context new ClassPathXmlApplicationContext(applicationContext.xml); IService serviceImpl (IService) context.getBean(serviceImpl); serviceImpl.modify(); System.out.println(); serviceImpl.add(); } }PS如果报错无法生成pointcut实例 “Cannot resolve reference to bean pointcut1 while setting bean property pointcut” 可能是aop的包忘记导入或者导错了。3.3、spring实现AOP的方式二—— xml声明式第二种方式比第一种简单能实现的功能也比第一种少。定义一个类这个类就是切面。把切入的都写在类里。IService接口和ServiceImpl实现类和方式一用的一样并且属于不能改动的代码。然后我们不使用方式一的java API而是自定义一个切面里面包含logBefore和logAfter两个切入点要做的事。方式二的局限性也就来了因为是自定义的切面没有使用方式一的API那么被切入方法的一系列信息都获取不到。自定义切面类DiypPointCut.javapackage com.kuang.log; //自定义切面 public class DiyAspect { public void logBefore(){ System.out.println(log----方法要开始执行了但我无法知道是哪个方法); } public void logAfter(){ System.out.println(log----方法结束执行了但我无法知道是哪个方法); } }applicationContext.xml1创建切面的bean· 2aop:aspect 配置上切面的bean3aop:pointcut切入点4通知?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:aophttp://www.springframework.org/schema/aop xsi:schemaLocationhttp://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd bean idserviceImpl classcom.kuang.service.ServiceImpl/bean !--创建一个切面bean-- bean iddiyAspect classcom.kuang.log.DiyAspect/bean !--aop配置-- aop:config !--切面 ref切面的bean-id-- aop:aspect refdiyAspect !--切入点-- aop:pointcut idpointcut1 expressionexecution(* com.kuang.service.ServiceImpl.*(..))/ !--aop:before在切入点前切入 method切面类中自定义的方法名-- aop:before methodlogBefore pointcut-refpointcut1/aop:before aop:after methodlogAfter pointcut-refpointcut1/aop:after /aop:aspect /aop:config /beans测试 MyTest.javapublic class MyTest { public static void main(String[] args) { ApplicationContext context new ClassPathXmlApplicationContext(applicationContext.xml); IService serviceImpl (IService) context.getBean(serviceImpl); serviceImpl.modify(); } }结果log----方法要开始执行了但我无法知道是哪个方法 修改一个神马东西 log----方法结束执行了但我无法知道是哪个方法3.4、spring实现AOP的方式三—— 注解声明式❤方式三和方式二是一样的也是自定义切面所以功能不如方式一齐全。package com.kuang.log; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; Aspect public class DiyAspect { Before(execution(* *(..))) public void logBefore(){ System.out.println(log----方法要开始执行了但我无法知道是哪个方法); } After(execution(* *(..))) public void logAfter(){ System.out.println(log----方法结束执行了但我无法知道是哪个方法); } }这里用注解替代了xml配置文件中的aop:config出现问题原因在于我们导aspect织入包时跟着mvnrepository.com里加上了scope去掉就好了scope约定了jar包生效的范围如果不写默认一直生效PS: 注意有两个包里都有Before这个注解别搞错了xml配置文件中要创建切面类的bean还要开启切面注解支持?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:aophttp://www.springframework.org/schema/aop xsi:schemaLocationhttp://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd bean idserviceImpl classcom.kuang.service.ServiceImpl/bean !--使用注解方式也要创建一个切面bean-- bean iddiyAspect classcom.kuang.log.DiyAspect/bean !--开启切面注解支持-- aop:aspectj-autoproxy/aop:aspectj-autoproxy /beans运行结果同方式二其实标准的写法是package com.kuang.log; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; Aspect public class DiyAspect { PointCut(execution(* *(..))) public void aspectBefore(){} PointCut(execution(* *(..))) public void aspectAfter(){} Before(aspectBefore()) public void logBefore(){ System.out.println(log----方法要开始执行了请求报文); } After(aspectAfter()) public void logAfter(){ System.out.println(log----方法结束执行了响应报文); } }