前言
Sping5学习的下半部分主要是围绕AOP(面向切面编程),进行基础的学习和巩固。
代理模式
-
为什么要学习代理模式?因为这个SpringAOP的底层!!
-
代理模式的分类:
(1)静态代理
(2)动态代理 -
通过一个简单的例子来阐述代理模式:
角色分析:
(1)抽象角色
:一般会使用接口或者抽象类来解决(上图的租房)
(2)真实角色
:被代理的角色(房东)
(3)代理角色
:代理真实角色,代理过后一般会做一些附属的操作
(4)客户
:访问代理对象的人(住房子的人) -
代码具体展示静态代理:
(1)Rent接口
://租房接口 public interface Rent { public void rent(); }
(2)
Host
://房东 public class Host implements Rent{ public void rent() { System.out.println("房东要出租房!!!"); } }
(3)
SimpleProxy
://静态代理 public class SimpleProxy implements Rent{ private Host host; public SimpleProxy(Host host){ this.host = host; } public void rent() { System.out.println("中介来啦"); seeHouse(); host.rent(); } //代理可以做的其它事情 public void seeHouse(){ System.out.println("房东带你看房!!"); } }
(4)
Clent
://客户 public class Client { public static void main(String[] args) { SimpleProxy simpleProxy = new SimpleProxy(new Host()); simpleProxy.rent(); } }
-
代理模式的好处:
(1)可以使得真实角色的操作更加纯粹,不用去关注一些公共的业务
(2)公共的交给代理角色,实现了业务的分工
(3)公共业务发生更改时,方便管理 -
我再举一个例子来加深理解代理模式,并且引出对AOP的理解:
(1)新建一个业务接口UserDao主要是增删改查的业务:/** * 业务dao层接口:crud增删改查 */ public interface UserDao { public void add(); public void delete(); public void update(); public void select(); }
(2)写一个接口的实现类UserService业务实现,主要是业务的实现:
public class UserService implements UserDao { public void add() { System.out.println("增"); } public void delete() { System.out.println("删"); } public void update() { System.out.println("改"); } public void select() { System.out.println("查"); } }
(3)现在我们存在一个需求:在调用增删改查的业务前后记入一下log打印执行的是什么方法,如果我们不使用代理模式的话,我们则需要在每个业务实现的方法里都要加上打印log的方法,现在这里的方法只有4个,但是如果有很多这样的业务实现类了,一方面改动量很大,另一方面我们业务的方法里变得不纯粹了,里面夹杂了一些其它操作,这个是万万不能允许的。
因此我们现在需要用代理的方式来把日志的打印加上去,在不动原先业务的前提下:public class UserServiceProxy implements UserDao { private UserService userService; public UserServiceProxy(UserService userService){ this.userService = userService; } public void add() { this.printLog("add"); userService.add(); } public void delete() { this.printLog("delete"); userService.delete(); } public void update() { this.printLog("update"); userService.update(); } public void select() { this.printLog("select"); userService.select(); } /** * 打印日志 */ public void printLog(String msg){ System.out.println("打印"+msg+"日志!!!"); } }
(4)测试:
public static void main(String[] args) { UserServiceProxy userServiceProxy = new UserServiceProxy(new UserService()); userServiceProxy.add(); } }
- 通过上面的例子让我们对代理模式有了更深的理解,AOP的底层其实就是代理模式:
之前我们的开发模式是纵向开发,现在我们要加一个日志进去,这就是一个横向开发 。正常代码测试没有问题并且上线了,后期用户量达到一定程度我们需要修改一些东西,增加一些功能,在不改变原有业务的情况下,我们使用一个代理机制写一个代理类,这种横向开发的模式也就是AOP的底层实现机制。
动态代理详解
-
针对静态代理的缺点,每有一个真实的角色就会产生一个代理角色,代码量会翻倍,开发效率会降低
-
动态代理和静态代理的角色一样
-
动态代理的代理类是动态生成的
-
动态代理分为两大类:
(1)基于接口的动态代理 >>>> jdk的动态代理(上面有一篇文章专门讲的java编程思想中的动态代理)
(2)基于类的动态代理 >>>> cglib
(3)java字节码:javassist -
在这里我们把上面租房子的代理类改造成动态代理:
//我们会用这个类自动生产代理类 public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 private Rent rent; public ProxyInvocationHandler(Rent rent) { this.rent = rent; } //生成得到的代理类 public Object getProxy() { return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this); } //处理代理实例,并返回结果 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //动态代理的本质,就是使用反射机制实现 Object result = method.invoke(rent, args); return result; } }
-
写一个客户端类进行测试:
public static void main(String[] args) { ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(new Host()); Rent proxy = (Rent) proxyInvocationHandler.getProxy(); proxy.rent(); } }
-
基于上面的思想,把代理类写成一个通用的,并且修改上面增删改查例子增加log的例子的修改:
//我们会用这个类自动生产代理类 public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 private Object target; public ProxyInvocationHandler(Object target) { this.target = target; } //生成得到的代理类 public Object getProxy() { return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } //处理代理实例,并返回结果 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //调用日志输出方法名 log(method.getName()); //动态代理的本质,就是使用反射机制实现 Object result = method.invoke(target, args); return result; } //输出日志 public void log(String msg) { System.out.println("方法名:" + msg); } }
-
测试:
public static void main(String[] args) { UserService userService = new UserService(); ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(userService); UserDao proxy = (UserDao) proxyInvocationHandler.getProxy(); proxy.add(); }
- 总结:
(1)静态代理的优点都具有
(2)一个动态代理类代理的是一个接口,一般就是对应的一类的业务
(3)一个动态代理类可以代理多个类,只要实现了同一个接口即可
AOP基本概念
在前面的铺垫下,现在我们要进入正题了:AOP
- 什么是AOP?
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译的方式和运行时期动态代理实现程序功能的统一维护的一种技术。
- AOP中的一些名词概念
(1)横切关注点:跨越应用程序多个模块的方法和功能,即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。比如,日志,安全,缓存,事务等等…
(2)切面(Aspect):横切关注点被模块化的特殊对象,即,它是一个类(比如一个Log日志类)
(3)通知(Advice):切面必须要完成的工作,即,它是类中的一个方法(比如Log类中的一个方法)
(4)目标(Target):被通知的对象
(5)代理(Proxy):向目标对象应用通知之后创建的对象
(6)切入点(PointCut):切面通知执行的“地点”的定义
(7)连接点(JoinPoint):与切入点匹配的执行点
AOP的实现方式(一):使用Spring的API接口
我们通过一个示例来演示这种实现方式,主要是在业务代码的前后增加log通知:
-
(1)创建项目,引入织入的依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
-
(2)创建一个业务接口UserService和一个业务层的实现类UserServiceImpl:
/** * 业务层接口 */ public interface UserService { public void add(); public void delete(); public void update(); public void select(); }
public class UserServiceImpl implements UserService { public void add() { System.out.println("增加一个用户!!!"); } public void delete() { System.out.println("删除一个用户!!!"); } public void update() { System.out.println("更新一个用户!!!"); } public void select() { System.out.println("查询一个用户!!!"); } }
-
(3)创建前置通知日志BeforeLog实现MethodBeforeAdvice 接口,后置通知日志AfterLog实现AfterReturningAdvice 接口:
/** * 前置log */ //方法执行前增强实现MethodBoforeAdvice接口 public class BeforeLog implements MethodBeforeAdvice { /** * * @param method 要执行目标对象的方法 * @param args 参数 * @param target 目标对象 * @throws Throwable */ public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(method.getName() + "要被执行啦!!!"); } }
/** * 实现AfterReturningAdvice接口,实现后置通知 */ public class AfterLog implements AfterReturningAdvice { public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了"+method.getName()+"方法!!!!"); } }
-
这一步最重要,创建SpringApplicationContext.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--> <bean id="userServiceImpl" class="com.twy.service.UserServiceImpl"></bean> <bean id="beforeLog" class="com.twy.log.BeforeLog"></bean> <bean id="afterLog" class="com.twy.log.AfterLog"></bean> <!--配置AOP--> <aop:config> <!--切入点 expression:表达式 execution(* * * * *):要执行的位置,这个表达式意思是:这个实现类下的所有方法,任意传入参数--> <aop:pointcut id="pointcut" expression="execution(* com.twy.service.UserServiceImpl.*(..))"/> <!--执行环绕增加--> <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> </aop:config> </beans>
-
创建一个测试类:
public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("SpringApplicationContext.xml"); UserService userService = (UserService) context.getBean("userServiceImpl"); userService.add(); } }
AOP的实现方式(二):自定义类实现AOP
- 首先创建一个自定义类:
/** * 自定义类切入点 */ public class DiyPointCut { public void before(){ System.out.println("自定义类DiyPointCut的before方法,在调用方法前调用!!!"); } public void after(){ System.out.println("自定义类DiyPointCut的after方法,在调用方法后调用!!!"); } }
- 下面展示怎么在xml文件配置自定义类实现AOP:
具体的配置注解在代码中都有<?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"> <!--使用自定义类实现--> <!--注入类DiyPointCut--> <bean id="diy" class="com.twy.diy.DiyPointCut"/> <aop:config> <!--自定义切面 ref是要引用的类--> <aop:aspect ref="diy"> <!--切入点 expression:表达式 execution(* * * * *):要执行的位置,这个表达式意思是:这个实现类下的所有方法,任意传入参数--> <aop:pointcut id="pointcut" expression="execution(* com.twy.service.UserServiceImpl.*(..))"/> <!--通知--> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
AOP的实现方式(三):使用注解实现AOP
-
首先创建一个类AnnotationPointCut用注解实现切面:
/** * 注解实现AOP */ @Aspect //标注这个类是一个切面 public class AnnotationPointCut { @Before("execution(* com.twy.service.UserServiceImpl.*(..))") public void before() { System.out.println("注解实现:方法执行前-----"); } @After("execution(* com.twy.service.UserServiceImpl.*(..))") public void after() { System.out.println("注解实现:方法执行后-----"); } /** * 在环绕增强中,我们可以给定一个参数,代表我们要处理切入的点 */ @Around("execution(* com.twy.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("环绕前-----"); //执行方法 Object proceed = jp.proceed(); System.out.println("环绕后-----"); System.out.println(proceed); } }
-
接着我们要开启AspectJ自动代理,主要针对AOP注解开启,这里有两种方式:
(1)在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 http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注入AnnotationPointCut--> <bean id="userServiceImpl" class="com.twy.service.UserServiceImpl"></bean> <bean id="AnnotationPointCut" class="com.twy.annotation.AnnotationPointCut"/> <!--开启AspectJ自动代理,主要针对AOP注解开启--> <!--JDK(默认:proxy-target-class="false") cglib(proxy-target-class="true")--> <aop:aspectj-autoproxy/> </beans>
注意:在用xml文件开启自动代理的时候,遇到了一个错误:
通配符的匹配很全面, 但无法找到元素 ‘aop:aspectj-autoproxy’ 的声明
。后来通过查询文档发现,我们不仅仅要引入Aop的名称空间,还要在xsi:schemaLocation中加入aop的xsd文件:http://www.springframework.org/schema/aop/spring-aop.xsd
(2)在Java配置文件中开启自动代理:@Configuration @ComponentScan @EnableAspectJAutoProxy //开启AspectJ自动代理,主要针对AOP注解开启 public class JavaConfig { @Bean public UserServiceImpl UserServiceImpl(){ return new UserServiceImpl(); } @Bean public AnnotationPointCut annotationPointCut(){ return new AnnotationPointCut(); } }
-
创建一个测试类进行测试:
public class MyTest { public static void main(String[] args) { /** * XML文件 */ ApplicationContext context = new ClassPathXmlApplicationContext("SpringApplicationContext3.xml"); UserService userService = (UserService) context.getBean("userServiceImpl"); userService.add(); /** * JavaConfig配置文件 */ ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class); UserService userService = (UserService) context.getBean("UserServiceImpl"); userService.add(); } }
-
结果:
后续
后续,会结合书Spring4实战和Spring5实战,进行区别整理。
项目代码地址
https://github.com/jaksion/Spring5