无论是代理模式还是AOP核心思想都是在不修改原来业务代码前提下,进行原来代码的增强
一:代理模式
代理作用:代理对象具有真实对象功能(比如真实对象有method1()那么代理对象也具有method1()),并且执行时让代理对象替代真是对象完成相应操作,并能够在执行前后对真实对象操作进行增强处理
代理模式使用场合:客户端不直接访问实际对象,而通过代理对象作为桥梁来完成间接访问
使用代理模式并不会创建新的方法,都是在真实类方法基础上进行增强,当然在增强方法中可以创建其他方法
1.静态代理VS动态代理
1.1静态代理:
静态代理:代理类和真实类实现同一个接口,这是静态代理的前提;代理类实现真实类同时对真实类方法进行增强处理
每一个不希望被修改的对象(SaveBusinessProcess)都要创建一个对应的代理类(SaveBusinessProcessProxy),这也正是静态代理的缺点。
比如,除了SaveBusinessProcess还有UpdateBusinessProcess,DeleteBusinessProcess等,使用静态代理都需要创建对应的代理UpdateBusinessProcessProxy,DeleteBusinessProcessProxy
初始SaveBusinessProcess在线上运行将数据存储到mysql——》在不更改SaveBusinessProcess存储数据到mysql前提下需要将这些数据保存到oracle——》新建代理类SaeBusinessProcessProxy,将SaveBusinessProcess作为成员变量
实例代码:
TestMain: public class BusinessProcessTest { public static void main(String[] args){ IBusinessProcess businessProcess=new SaveBusinessProcess(); IBusinessProcess businessProcessProxy=new SaveBusinessProcessProxy(businessProcess); businessProcessProxy.dataProcess(); } } Interface: public interface IBusinessProcess { public void dataProcess(); } Service: public class SaveBusinessProcess implements IBusinessProcess { @Override public void dataProcess() { //dataprocess(); --业务处理逻辑 System.out.println("save data process completed"); } } Proxy: public class SaveBusinessProcessProxy implements IBusinessProcess { private IBusinessProcess businessProcess; public SaveBusinessProcessProxy(IBusinessProcess businessProcess){ this.businessProcess=businessProcess; } @Override public void dataProcess() { //addProcess() --需要加强的代码 System.out.println("static proxy process"); this.businessProcess.dataProcess(); } }
控制台输出:
1.2 动态代理(JDK动态代理 && CGLIB动态代理)
当真实类中方法增多,静态代理类也要添加相应的方法,所以需要引入动态代理
动态代理中代理类采用反射方式,在不用随着真实类方法增加而增加的前提下,为真实类提供方法增强的目的
1.2.1 JDK动态代理(接口代理)--反射获取真实类任意method
JDK动态代理核心点: 接口、反射、Proxy.newProxyInstance()、implements InovationHandler、invoke()
Jdk代理设计到java.lang.reflect包中InovacationHandler接口和Proxy类,核心方法是如下invoke()方法
invoke()作用:调用包装在当前Method对象中的方法
invoke()原型:Object invoke(Object obj, Method method, Object...args) obj:实例化后对象,method: 目标方法,args:用于方法调用的参数
invoke()返回:根据obj和args调用的方法的返回值
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {...}
public interface Person {
void wakeup(); void sleep(); } public class Mum implements Person{ @Override public void wakeup() { System.out.println("mum wakeup"); } @Override public void sleep() { System.out.println("mum sleep"); } } public class Son implements Person{ @Override public void wakeup() { System.out.println("son wakeup"); } @Override public void sleep() { System.out.println("son sleep"); } } public class JdkProxy implements InvocationHandler { private static final String method1="wakeup"; private static final String method2="sleep"; private Object bean; public JdkProxy(Object bean){ this.bean=bean; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name= method.getName(); if(StringUtils.equalsIgnoreCase(name,method1)){ if(bean.getClass().getName().contains("Mum")){ System.out.println("Mum is preparing breakfast"); }else{ System.out.println("Son is washing face"); } }else if(StringUtils.equalsIgnoreCase(name,method2)){ if(bean.getClass().getName().contains("Mum")){ System.out.println("Mum is cleaning"); }else{ System.out.println("Son is dreaming"); } } return method.invoke(bean,args); } } public class MainTest { public static void main(String[] args) { Person person=new Son(); JdkProxy jdkProxy=new JdkProxy(person); //创建代理类类型的引用proxy,传入son实例那么这个proxy对象就是代理的son实例
Person son=(Person) Proxy.newProxyInstance(jdkProxy.getClass().getClassLoader(), new Class[]{Person.class},jdkProxy); //生成Son的代理实例.另外注意这里Person.class要是接口类 son.wakeup(); son.sleep(); System.out.println("--------------"); Person person1=new Mum(); JdkProxy jdkProxy1=new JdkProxy(person1); Person mum=(Person) Proxy.newProxyInstance(jdkProxy.getClass().getClassLoader(), new Class[]{Person.class},jdkProxy1); mum.wakeup(); mum.sleep(); } }
output:
Son is washing face
son wakeup
Son is dreaming
son sleep
--------------
Mum is preparing breakfast
mum wakeup
Mum is cleaning
mum sleep
JDK动态代理必须有接口原因:
这和JDK动态代理本身设计相关。点进Proxy.newProxyInstance()实现如下,可见JDK动态代理中生成的代理类需要传入被代理接口,并通过反射得到接口的方法对象,并将此方法对象传参给代理类的invoke()去执行,从而实现代理功能。所以接口是jdk动态代理的核心实现方式,没有接口就无法通过反射找到方法。
@CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); ... }
如果将上例中
Person person=new Son();
JdkProxy jdkProxy=new JdkProxy(person);
Person son=(Person) Proxy.newProxyInstance(jdkProxy.getClass().getClassLoader(), new Class[]{Person.class},jdkProxy);
改成
Son son=new Son();
JdkProxy jdkProxy=new JdkProxy(son);
Son son=(Son) Proxy.newProxyInstance(jdkProxy.getClass().getClassLoader(), new Class[]{Son.class},jdkProxy);
会报如下异常
Exception in thread "main" java.lang.IllegalArgumentException: proxymode.dynamicproxy.jdkproxy.Son is not an interface
at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:590)
at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:557)
at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:230)
at java.lang.reflect.WeakCache.get(WeakCache.java:127)
at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:419)
at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:719)
at proxymode.dynamicproxy.jdkproxy.MainTest.main(MainTest.java:9)
1.2.2 cglib动态代理
cglib动态代理不需要有接口存在,cglib动态代理过程中生成的是实现类的子类(Enhancer来生成实现类的子类)
public interface Person { void wakeup(); void sleep(); } public class Mum implements Person{ @Override public void wakeup() { System.out.println("mum wakeup"); } @Override public void sleep() { System.out.println("mum sleep"); } } public class Son implements Person{ @Override public void wakeup() { System.out.println("son wakeup"); } @Override public void sleep() { System.out.println("son sleep"); } } public class CglibProxy implements MethodInterceptor { private static final String method1="wakeup"; private static final String method2="sleep"; private Enhancer enhancer=new Enhancer(); private Object bean; public CglibProxy(Object bean){ this.bean=bean; } public Object getProxy(){ //cglib“凭空”创造一个原bean的子类,并把callback指向this,也就也是当前对象即这个proxy对象。从而调用intercept(),并且在intercept()中进行了附加功能的执行,并且最终还是调用原始bean的相应方法:son.wakeup() son.sleep() //设置要创建子类的类 enhancer.setSuperclass(bean.getClass()); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { String name= method.getName(); if(StringUtils.equalsIgnoreCase(name,method1)){ if(bean.getClass().getName().contains("Mum")){ System.out.println("Mum is preparing breakfast"); }else{ System.out.println("Son is washing face"); } }else if(StringUtils.equalsIgnoreCase(name,method2)){ if(bean.getClass().getName().contains("Mum")){ System.out.println("Mum is cleaning"); }else{ System.out.println("Son is dreaming"); } } return method.invoke(bean,objects); } } public class MainTest { public static void main(String[] args) { Son son=new Son(); CglibProxy cglibProxy=new CglibProxy(son); //创建代理类类型的引用proxy,传入son实例那么这个proxy对象就是代理的son实例 Son son1=(Son) cglibProxy.getProxy(); //使用代理类调用getProxy()方法,并强制类型转换 son1.wakeup(); son1.sleep(); System.out.println("--------------"); Mum mum=new Mum(); CglibProxy cglibProxy1=new CglibProxy(mum); Mum mum1=(Mum) cglibProxy1.getProxy(); mum1.wakeup(); mum1.sleep(); } }
output:
Son is washing face
son wakeup
Son is dreaming
son sleep
--------------
Mum is preparing breakfast
mum wakeup
Mum is cleaning
mum sleep
二: AOP
AOP:它不需要专门的编译方式和特殊的类装载器,它在运行期通过动态代理方式来实现将目标类织入性能监视代码
1. aop核心概念
切面(Aspect):一个模块化的横切逻辑(也称横切关注点),可能会横切多个对象。
连接点(Join point):程序执行中的某个具体执行点。如上例中原对象(非代理对象)的方法。
增强处理:切面在某个特定连接点上执行的代码逻辑。
切入点(pointcut):对连接点的特征进行描述,可使用正则表达式,类似于Servlet的url-pattern,用于匹配连接点(方法)。增强处理与一个切入点表达式相关联,并在与这个切入点匹配的某个连接点运行。
目标对象:被一个或多个切面增强的对象。
AOP代理(Proxy):由AOP所创建的对象,实现执行增强处理方法等功能,可以使JDK动态代理也可以是CGLIB代理;如果是接口,则可以使用JDK动态代理,如果是类(比如service只有一个实现,不需要使用接口或者Controller层只有一个实现)则使用CGLIB代理
织入:将增强处理连接到应用程序中的类型或对象上的过程,叫织入。
增强处理类型:在原对象的方法连接点之前插入的增强处理叫前置增强,在其之后的叫后置增强。此外还要环绕增强、异常抛出增强、最终增强等类型。
通知(advice),5种通知方式
@Before
:前置通知,在调用目标方法之前执行通知定义的任务@After
:后置通知,在目标方法执行结束后,无论执行结果如何都执行通知定义的任务@After-returning
:后置通知,在目标方法执行结束后,如果执行成功,则执行通知定义的任务@After-throwing
:异常通知,如果目标方法执行过程中抛出异常,则执行通知定义的任务@Around
:环绕通知,在目标方法执行前和执行后,都需要执行通知定义的任务。
2.AOP常用注解
@Aspect:作用:把当前类声明为切面类。
@Before:
作用:把当前方法看成是前置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@AfterReturning
作用:把当前方法看成是后置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@AfterThrowing
作用:把当前方法看成是异常通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@After
作用:把当前方法看成是始终通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@Around
作用:把当前方法看成是环绕通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@Pointcut
作用:指定切入点表达式
属性:
value:指定表达式的内容
@Order
作用: 指定切面执行顺序
@Aspect
// 切面执行顺序
@Order
(
3
)
实例:(将监控代码AspectDemo 织入业务代码AspectTestService中,实现业务代码和监控代码的解耦)
ProjectApplication: @SpringBootApplication public class ProjectApplication { public static void main(String[] args) { SpringApplication.run(ProjectApplication.class, args); } } Controller: @RestController @Slf4j public class AspectTestController { @Autowired private AspectTestService aspectTestService; @RequestMapping("aoptest") public void test(){ log.info("this is an aop controller..."); aspectTestService.test("abc"); // aspectTestService.testAgain(); } } Srvice: @Service @Slf4j public class AspectTestService { public void test(String name){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } log.info("this is an AOP test service ..."); } } AOP: @Aspect 定义切片类 @Component @Slf4j public class AspectDemo { @Before("pointCuteDemo()") public void doBefore(){ log.info("Aspect Demo before"); } @Around("pointCuteDemo()") public Object logPerformance(ProceedingJoinPoint pjp) throws Throwable { long starttime = System.currentTimeMillis(); String name = "-"; String result = "Y"; try { Object[] args=pjp.getArgs(); //获取方法参数值数组 MethodSignature methodSignature= (MethodSignature) pjp.getSignature(); //获取方法名 Class[] paramTypeArray=methodSignature.getParameterTypes(); //获取方法参数类型 name=methodSignature.toLongString(); log.info("args:{},methodSignature:{},paramTypeArray:{}",args,methodSignature,paramTypeArray); return pjp.proceed(); //pjp.proceed()上边内容在调用com.fql.example.project.aop.service包下任意方法之前执行;pjp.proceed()下边内容在调用com.fql.example.project.aop.service包下任意方法之后执行 } catch (Exception e) { result = "N"; throw e; } finally { long endtime = System.currentTimeMillis(); log.info("{},{},{}ms", name, result, endtime - starttime); } } @After("pointCuteDemo()") public void doAfter(){ log.info("Aspect Demo After"); } @Pointcut("execution(* com.fql.example.project.aop.service..*(..))") //service包下的任意类的任意方法时均会调用此方法 private void pointCuteDemo(){} }
参考:https://www.cnblogs.com/wangenxian/p/10885309.html
https://www.cnblogs.com/leifei/p/8263448.html
https://blog.csdn.net/weixin_43953283/article/details/125783249
创建代理类类型的引用proxy,传入son实例那么这个proxy对象就是代理的son实例