一.静态代理 代理模式需要创建一个代理对象,一个目标对象,通过代理对象调用目标对象,实现我们需要实现的功能,代码如下:
public interface Ticket { void buyTicket(); } /** * 12306 直接购买 */ public class By12306TicketImpl implements Ticket{ @Override public void buyTicket() { System.out.println("12306 买票"); } } /** * 通过黄牛购买 */ public class HuangNiuTicketImpl implements Ticket { private Ticket ticket; public void setTicket(Ticket ticket) { this.ticket = ticket; } @Override public void buyTicket() { System.out.println("黄牛开始买票"); if (ticket!=null){ ticket.buyTicket(); } System.out.println("黄牛买票成功"); } }测试
public class Test { public static void main(String[] args) { // 自己买票 Ticket ticket = new By12306TicketImpl(); ticket.buyTicket(); // 通过黄牛买票,最终票的来源还是12306 HuangNiuTicketImpl huangNiu = new HuangNiuTicketImpl(); huangNiu.setTicket(ticket); huangNiu.buyTicket(); } }二.JDK动态代理 JDK动态代理通过反射获取接口进行实现,主要步骤为: 1.实现InvocationHandler接口
2.创建代理对象 3.通过反射执行目标对象方法火车票代理对象
package com.qfedu.jdk; import com.qfedu.ticket.Ticket; import com.qfedu.ticket.impl.TicketBy12306Impl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkProxyTest1 { public static void main(String[] args) { // 12306 目标对象 final TicketBy12306Impl t12306 = new TicketBy12306Impl(); // 创建一个代理对象 // ClassLoader loader, 类加载器 // Class<?>[] interfaces, 目标对象 实现的接口 代理目标对象必须 实现接口 // t12306.getClass().getInterfaces() 获取目标对象实现的接口 // InvocationHandler h 当代理对象执行 接口的方法时 会回调InvocationHandler 的 invoke Ticket proxyTicket = (Ticket) Proxy.newProxyInstance(t12306.getClass().getClassLoader(), t12306.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理对象开始调用 目标对象"); // 让目标对象执行方法 并且获取返回结果 Object result = method.invoke(t12306,args); System.out.println("代理对象结束调用 目标对象"); return result; } }); String str = proxyTicket.bubTicket(); System.out.println("str:"+str); } }动态代理
package com.qfedu.jdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 专门用于 产生 JDK代理对象 */ public class JdkProxy implements InvocationHandler{ // 目标对象 private Object target; public Object createProxy(final Object target){ this.target = target; // 创建 代理对象 // this 当前对象实现 InvocationHandler //新的代理对象 newProxyInstance return Proxy.newProxyInstance( target.getClass().getClassLoader(),//类加载器 target.getClass().getInterfaces(),//获取目标对象实现的接口 this ); } //传入的参数为Object proxy代理对象,Method method目标方法,Object[] args代理对象传入的参数 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理对象开始调用 目标对象"); // 让目标对象执行方法 并且获取返回结果 Object result = method.invoke(target,args); System.out.println("代理对象结束调用 目标对象"); return result; } }动态代理测试
package com.qfedu.jdk; import com.qfedu.subject.Subject; import com.qfedu.subject.impl.RealSubject; import com.qfedu.ticket.Ticket; import com.qfedu.ticket.impl.TicketBy12306Impl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * JdkProxy 可以产生多个代理对象 ,但是代理对象必须实现 接口 如果目标对象 没有实现接口 则报错 */ public class JdkProxyTest2 { public static void main(String[] args) { // 12306 目标对象 final Ticket t12306 = new TicketBy12306Impl(); //创建代理对象 JdkProxy jdkProxy = new JdkProxy(); // 将12306设置进代理对象内 Ticket huangNiuticket = (Ticket) jdkProxy.createProxy(t12306); //代理对象调用目标方法,实现功能 String str = huangNiuticket.bubTicket(); System.out.println("str:"+str); System.out.println("***********************************"); // 目标对象 Subject realSubject = new RealSubject(); // 创建Subject 的代理对象 Subject proxySubject = (Subject) jdkProxy.createProxy(realSubject); proxySubject.action(); } }三.CGLIB动态带代理 cglib是代理类,基于asm字节码生成框架,用于动态生成代理类,与JDK动态代理不同有以下几点 1.JDK动态代理要求被代理类实现某个接口,而cglib无该要求 2.JDK动态代理生成的代理类是该接口实现类,也就是说,不能代理接口中没有方法,而cglib生成的代理类继承被代理类 3.在字节码的生成和类的创建上,cglib动态代理效率更高 4.在代理方法的执行效率上,由于采用了FastClass,cglib的效率更高(以空间换时间) 注:因为JDK动态代理中代理类中的方法是通过反射调用的,而cglib因为引入了FastClass,可以直接调用代理类对象的方法。 重点:
1.cglib可以代理类和接口
2.cglib 直接调用生成的类的方法,而jdkproxy是通过反射,返回效率差
引入cglib依赖
package com.qfedu.cglib; import com.qfedu.ticket.impl.TicketByWindow; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * cglib 创建代理代理对象的工具类 */ public class CglibProxy implements MethodInterceptor { /** * 创建代理对象 * @param superclass * @return */ //Class superclass参数为目标对象类 public Object createProxy(Class superclass){ Enhancer enhancer = new Enhancer(); //把目标对象类设置进代理对象类 enhancer.setSuperclass(superclass); //设置当前类为代理类 enhancer.setCallback(this); // 创建 代理对象 return enhancer.create(); } //Object o目标对象,Object[] objects目标对象执行需要的参数,MethodProxy methodProxy目标对象调用的方法,Method method目标方法 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("cglib 代理开始调用 目标对象"); // 让目标 对象执行 // o 目标对象 // objects 目标对象执行方法需要的惨呼 Object result = methodProxy.invokeSuper(o,objects); System.out.println("cglib-result:"+result); System.out.println("cglib 代理结束调用 目标对象"); return result; } }测试:
public class Test { public static void main(String[] args) { //设置产生代理的 位置 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D://cglib_proxy_classes"); //创建代理类的对象 CGLibProxy cgLibProxy = new CGLibProxy(); // Cglib 代理的目标对象有方法的实现 // 不需要创建 目标对象,CGLibProxy 会帮助我们创建 目标对象和代理对象 Ticket proxyTicket = (Ticket) cgLibProxy.getInstance(By12306TicketImpl.class); proxyTicket.buyTicket(); // Cglib 代理的目标对象是一个类,没有接口 WindowTicket proxyWindowTicket = (WindowTicket) cgLibProxy.getInstance(WindowTicket.class); proxyWindowTicket.buyTicket(); } }四.AOP切面 1.什么是aop:aop就是面向切面编程,aop解决的问题就是将共同重复的代码进行抽离(日志,事物,鉴权) 2.什么事切面:切面就是一个类,类里面包含某一类具体业务(日志,事物,鉴权),用这个类去拦截/增强你需要监控的方法或者业务 3.切入点:就是切面需要拦截增强的类的内部方法 4.连接点:每一个类里普通的方法都是连接点 5.切面:就是封装某一中业务抽离(日志,事物,鉴权) 6.增强/通知:就是切面中的方法对目标对象中的方法进行增强/拦截/通知 7.织入:是一个过程/动作,就是将切面切向要拦截那些类的方法的过程 8.aop实现就是通过动态代理实现的,实现的两种方式 a.基于自身的ProxyFactoryBean 实现aop功能 b.借助于aspectj第三方aop框架实现 9.基于ProxyFactoryBean实现aop ProxyFactoryBean 创建代理对象工厂,继承FactoryBean FactoryBean:作用是创建bean,容器加载时所有的对象都是由FactoryBean的子类创建 BeanFactory:是spring的核心容器之一,顶层接口,作用主要是让用户获取bean,判断bean 是否单例 基于ProxyFactoryBean 实现AOP 1.引入依赖
<properties> <!--在当前pom 或者父类pom 中声明属性 --> <spirng.version>5.0.16.RELEASE</spirng.version> </properties> <dependencies> <!-- spring 核心包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spirng.version}</version> </dependency> <!-- 导入spring aop 依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spirng.version}</version> </dependency> </dependencies>2.创建切面
/** * 创建一个切面 * * 切面放置的就是 对目标对象方法增强业务 (日志 事务) */ public class MyAspect implements MethodInterceptor { // 反射执行的方法 public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("MyInspect----"+methodInvocation.getMethod().getName()+"开始执行"); // 让目标对象执行 Object result = methodInvocation.proceed(); System.out.println("MyInspect--result:"+result); System.out.println("MyInspect----"+methodInvocation.getMethod().getName()+"结束执行"); return result; } }3.在xml中织入切面和目标对象,proxyfactorybean实现切面切点,只能切入一种方法,不可以动态切入
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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 --> <bean id="studentDao" class="com.qfedu.dao.IStudentDaoImpl"> </bean> <!-- 声明一个切面--> <bean id="myAspect" class="com.qfedu.proxyfactorybean.MyAspect"> </bean> <!-- ProxyFactoryBean 作用就是创建 代理对象 通过容器根据id = proxyBean 就可以得到studentDao 的代理对象 --> <bean id="proxyBean" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- proxyTargetClass 创建代理对象 true 代表强制使用 cglib false 接口使用jdk代理 类 使用cgLib --> <property name="proxyTargetClass" value="false"></property> <!-- proxyInterfaces 代理那些接口 可以不写 ,有接口可以写也可以不写 没接口不写 --> <property name="proxyInterfaces" value="com.qfedu.dao.IStudentDao"></property> <!-- target 创建的代理对象 需要让目标对象执行相应方法 需要目标对象引入 --> <property name="target" ref="studentDao"></property> <!-- 指定代理类的切面 value="myAspect" --> <property name="interceptorNames" value="myAspect" ></property> </bean> </beans>4.测试
public class ProxyFactoryTest { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("proxyfactoybean/bean.xml"); // 得到代理对象 IStudentDao iStudentDao = (IStudentDao) applicationContext.getBean("proxyBean"); // 让代理对象执行 iStudentDao.findStudentById(1000); } }基于AspectJ实现AOP
通知就是切面中的方法,一般常用的类型: org.springframework.aop.MethodBeforeAdvice(前置通知)在目标方法执行前实施增强,可以应用于权限管理等功能。 org.springframework.aop.AfterReturningAdvice(后置通知)在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。 org.aopalliance.intercept.MethodInterceptor(环绕通知)在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。 org.springframework.aop.ThrowsAdvice(异常抛出通知) 在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。 org.springframework.aop.IntroductionInterceptor(引介通知)在目标类中添加一些新的方法和属性,可以应用于修改老版本程序。 基于xml实现: <!--引入 aspectj依赖--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.12</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.12</version> </dependency>创建切面
/** * 切面 */ public class MyAspect { //对目标对象增强的方法 /** * 前置通知 * 在目标对象调用前 调用 * @param joinPoint */ public void myBefore(JoinPoint joinPoint){ // joinPoint 连接点 在这里我们得到的是 切入点 他们都是方法 // 获取目标对象 // System.out.println("myBefore-target:"+joinPoint.getTarget()); System.out.println("myBefore-被调用的方法名:"+joinPoint.getSignature().getName()); } /** * 后置通知 * 目标对象 能偶正常的执行完毕 并可以得到结果 * @param joinPoint */ public void myAfterReturning(JoinPoint joinPoint,Object result){ // System.out.println("Returning-target:"+joinPoint.getTarget()); System.out.println("myAfterReturning-被调用的方法名:"+joinPoint.getSignature().getName()); System.out.println("myAfterReturning-result"+result); } /** * 环绕通知 * @param proceedingJoinPoint * @throws Throwable */ public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("myAround-被调用的方法名:"+proceedingJoinPoint.getSignature().getName()); // 让 目标对象执行 相应的方法 如果不执行代表 被拦截 // 返回值 目标对象执行 的结果 Object result = proceedingJoinPoint.proceed(); System.out.println("myAround-result:"+result); // 必须将 目标对象执行结果 返回给代理 return result; } /** * 异常通知 * * Throwable e 接收 目标对象产生的异常 * @param joinPoint */ public void myAfterThrowing(JoinPoint joinPoint,Throwable e){ System.out.println("myAfterThrowing-被调用的方法名:"+joinPoint.getSignature().getName()); // 得到异常信息 System.out.println("myAfterThrowing-异常信息:"+e.getMessage()); } /** * 最终通知 相当于 finally 无论如何都会调用 * @param joinPoint */ public void myAfter(JoinPoint joinPoint){ System.out.println("myAfter-被调用的方法名:"+joinPoint.getSignature().getName()); } }在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: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 https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--声明切面--> <bean id="myAspect" class="com.qfedu.aspectj.xml.MyAspect"> </bean> <!-- 目标对象--> <bean id="studentDao" class="com.qfedu.dao.IStudentDaoImpl"> </bean> <!-- 基于aspectj xml 实现 所有织入实现功能都必须写在 aop:config --> <aop:config> <!-- 配置 切点 全局切点 可以被多个切面所应用 切点 就是要拦截的方法 expression="execution() 切点表达式 就是找到那些需要被拦截的方法 --> <!-- <aop:pointcut id="myPoint" expression="execution(* com.qfedu.dao.*.*(..))"/>--> <!-- 配置切面 切面就是一个类 里面的方法主要用于拦截/增强 目标对象 ref="myAspect" 引用切面实例 进行织入 过程 --> <aop:aspect id="aopAspect" ref="myAspect"> <!-- 局部切点 只能被 当前切面使用 --> <aop:pointcut id="myPoint" expression="execution(* com.qfedu.dao.*.*(..))"/> <!-- 前置通知 和 切点绑定 --> <aop:before method="myBefore" pointcut-ref="myPoint"></aop:before> <!-- 绑定 后置通知 和切点 returning="result" 指定后置通知用那个参数 接收目标对象返回的结果 --> <aop:after-returning method="myAfterReturning" pointcut-ref="myPoint" returning="result"></aop:after-returning> <!-- 绑定 环绕通知 和切点--> <aop:around method="myAround" pointcut-ref="myPoint"></aop:around> <!-- 绑定异常通知 和 切点--> <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPoint" throwing="e"></aop:after-throwing> <!--绑定 最终通知 和切点--> <aop:after method="myAfter" pointcut-ref="myPoint"></aop:after> </aop:aspect> </aop:config>测试
public class AspectjXmlTest { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aspectj/xml/aspectj_bean.xml"); // 得到代理对象 IStudentDao iStudentDao = applicationContext.getBean(IStudentDao.class); Student student = iStudentDao.findStudentById(1000); System.out.println("student:"+student); } }基于注解实现AOP 注解声明切面
@Component//将切面加入容器 @Aspect// 声明当前类是一个切面 类 public class MyAspect { /** * 声明一个切点 * */ @Pointcut("execution(* com.qfedu.dao.*.*(..))") // 声明当前方法是切点 并传递切点表达式 public void myPoint(){} // 通知 将通知和切点绑定 //对目标对象增强的方法 /** * 前置通知 * 在目标对象调用前 调用 * @param joinPoint */ @Before("myPoint()")// 将当前的前置通知 和 切点绑定 public void myBefore(JoinPoint joinPoint){ // joinPoint 连接点 在这里我们得到的是 切入点 他们都是方法 // 获取目标对象 // System.out.println("myBefore-target:"+joinPoint.getTarget()); System.out.println("myBefore-被调用的方法名:"+joinPoint.getSignature().getName()); } /** * 后置通知 * 目标对象 能偶正常的执行完毕 并可以得到结果 * @param joinPoint */ @AfterReturning(pointcut = "myPoint()",returning = "result") public void myAfterReturning(JoinPoint joinPoint,Object result){ // System.out.println("Returning-target:"+joinPoint.getTarget()); System.out.println("myAfterReturning-被调用的方法名:"+joinPoint.getSignature().getName()); System.out.println("myAfterReturning-result"+result); } /** * 环绕通知 * @param proceedingJoinPoint * @throws Throwable */ @Around("myPoint()") public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("myAround-被调用的方法名:"+proceedingJoinPoint.getSignature().getName()); // 让 目标对象执行 相应的方法 如果不执行代表 被拦截 // 返回值 目标对象执行 的结果 Object result = proceedingJoinPoint.proceed(); System.out.println("myAround-result:"+result); // 必须将 目标对象执行结果 返回给代理 return result; } /** * 异常通知 * * Throwable e 接收 目标对象产生的异常 * @param joinPoint */ @AfterThrowing(pointcut = "myPoint()",throwing = "e") public void myAfterThrowing(JoinPoint joinPoint, Throwable e){ System.out.println("myAfterThrowing-被调用的方法名:"+joinPoint.getSignature().getName()); // 得到异常信息 System.out.println("myAfterThrowing-异常信息:"+e.getMessage()); } /** * 最终通知 相当于 finally 无论如何都会调用 * @param joinPoint */ @After("myPoint()") public void myAfter(JoinPoint joinPoint){ System.out.println("myAfter-被调用的方法名:"+joinPoint.getSignature().getName()); } }开启注解功能
<?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:context="http://www.springframework.org/schema/context" 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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 让@Component 生效--> <context:component-scan base-package="com.qfedu"></context:component-scan> <!-- 开启注解 aspectj 功能 让 @Aspect 生效 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>测试
public class AspectjAnnotationTest { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aspectj/annotation/aspectj_annotation_bean.xml"); // 得到代理对象 IStudentDao iStudentDao = (IStudentDao) applicationContext.getBean(IStudentDao.class); Student student = iStudentDao.findStudentById(1000); System.out.println("student:"+student); } }