
目录
在 JavaEE 分层开发中,哪个层次对于我们来说是最重要的?
Java 的开发思路: DAO ---> Service ---> Controller
其中最重要的是 Service ,因为做任何一个项目,写任何一段代码,最主要的目的都是为了满足用户的需求
Service 中包含了哪些代码?
Service 中 = 核心功能(几十行 上百行代码) + 额外功能(附加功能)
1、核心功能
业务运算
DAO 调用
2、额外功能
(1)不属于业务
(2)可有可无
(3)代码量小
事务、日志、性能....
额外功能 书写在 Service 中到底好不好呢?
Service 层调用者的角度(Controller):需要在 Service 中书写额外功能
软件设计者的角度:Service 中不需要额外功能(会造成代码不好维护)
现实生活中的解决方式:

我们把房东当成一个类(业务类 Service),房东提供了出租房屋的业务方法, 出租房屋的核心功能就是签合同和收钱,但是出租房屋光有核心功能是不够的,还得有一些额外功能:广告,看房。(类似于事务,日志....)
站在房东的角度来讲,它不愿意做这些额外功能了,但是房客不允许房东不做这些额外功能,要想解决这个问题,就得引入一个新的角色:中介(代理类)
中介就代替房东提供了这些额外功能:广告和看房,在这些方法中,首要的职责就是把房东曾经不干的额外功能由中介来干
但是最核心的功能,还是由房东自身来做的,这个时候对于房客来讲,既能享受到房东提供的核心功能,又能享受到中介提供的额外功能,诉求就满足了
如果有朝一日对额外功能不满意了,不需要修改原来的代码,可以直接换一个新的中介公司,让它提供一个新的额外方法,代码的维护性也就大大提高了
通过代理类,为原始类增加额外的功能
好处:利于原始类的维护
1、目标类 / 原始类:也就是房东,指的是业务类(核心功能 --> 业务运算 --> DAO 的调用)
2、目标方法 / 原始方法:目标类(原始类)中的方法,就是目标方法(原始方法)
3、额外功能(附加功能):以日志,事务,性能...为代表
代理类 = 目标类(原始类) + 额外功能 + 原始类(目标类)实现相同的接口
- 房东 ---> public interface UserService{
- m1
- m2
- }
- UserServiceImpl implements UserService{
- m1 ---> 业务运算 DAO调⽤
- m2
- }
- UserServiceProxy implements UserService{
- m1
- m2
- }
- //代理类的开发
- public class UserServiceProxy implements UserService{
- //获得原始对象
- private UserServiceImpl userService = new UserServiceImpl();
-
- @Override
- public void register(User user) {
- System.out.println("------log--------");
- userService.register(user);
- }
-
- @Override
- public boolean login(String name, String password) {
- System.out.println("-------log------");
- return userService.login(name,password);
- }
- }

1、静态代理文件数量过多,会不利于项目管理
有一个 UserServiceImpl 的原始类,就要提供一个 UserServiceproxy 的代理类,与之对应的,类的数量就会成倍的增长
2、额外功能维护性差
当我们想换一个日志的实现方式的时候,很多代码都得跟着修改,所以代码的维护性非常差
概念:通过代理类,为原始类(目标类)增加额外功能
优点:利于原始类(目标类)的维护
引入 Spring 动态代理相关的 jar 包
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-aopartifactId>
- <version>5.1.14.RELEASEversion>
- dependency>
-
- <dependency>
- <groupId>org.aspectjgroupId>
- <artifactId>aspectjrtartifactId>
- <version>1.8.8version>
- dependency>
-
- <dependency>
- <groupId>org.aspectjgroupId>
- <artifactId>aspectjweaverartifactId>
- <version>1.8.3version>
- dependency>
- public class UserServiceImpl implements UserService{
- @Override
- public void register(User user) {
- System.out.println("UserServiceImpl.register 业务运算 + DAO ");
- }
-
- @Override
- public boolean login(String name, String password) {
- System.out.println("UserServiceImpl.login");
- return true;
- }
- }
<bean id="userService" class="proxy.OrderServiceImpl">bean>
MethodBeforeAdvice 是一个接口
我们需要把额外功能写在接口的实现中,额外功能会在原始方法执行之前运行额外功能
- public class Before implements MethodBeforeAdvice {
-
- //作用:需要把运行在原始方法运行之前运行的额外功能,书写在 beofre 方法中
-
- @Override
- public void before(Method method, Object[] objects, Object o) throws Throwable {
- System.out.println("------method before advice-------");
- }
- }
<bean id="before" class="dynamic.Before">bean>
切入点:额外功能加入的位置
Spring 引入切入点的目的:由程序员根据自己的需要,来决定额外功能加入给哪个原始方法
简单的测试:所有方法都作为切入点,都加入额外的功能
通过 Spring 的配置文件完成
expression :切入点表达式,要根据自己的需求来写
- <aop:config>
- <aop:pointcut id = "pc" expression = "execution(* *(..))"/>
-
- aop:config>
把 第二步 和 第三步 进行整合
- <aop:config>
- <aop:pointcut id = "pc" expression = "execution(* *(..))"/>
-
- <aop:advisor advice-ref="before" pointcut-ref = "pc"/>
- aop:config>
表达的含义:所有的方法都加入 before 的额外功能
目的:获得 Spring 工厂创建的动态代理对象,并进行调用
- ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
-
- //注意:
- // 1、Spring 的工厂,通过原始对象的 id 值获得的是代理对象
- // 2、获得代理对象之后,可以通过声明接口类型,进行对象的存储
-
- UserService userService = (UserService) ctx.getBean("userService");
-
- userService.login();
- userService.registere();
注意:
1、Spring 的工厂,通过原始对象的 id 值获得的是代理对象
2、获得代理对象之后,可以通过声明接口类型,进行对象的存储
(1)Spring 创建的动态代理类在哪里?
动态代理类是 Spring 框架在运行时,通过动态字节码技术在虚拟机中创建的,运行在虚拟机内部,等程序结束后,会和虚拟机一起消失
动态字节码技术:通过第三方动态字节码框架在虚拟机中创建对应的类的字节码,进而创建对象,当虚拟机关闭,动态字节码也跟着消失
结论:动态代理,不需要定义类文件,都是 JVM 运行过程当中,动态创建的,所以不会造成静态代理中类文件数量过多影响项目管理的问题

(2)动态代理编程会简化代理的开发
在额外功能不改变的前提下,创建其它目标类(原始类)的代理对象时,只需要指定原始(目标)对象即可
(3)动态代理的维护性大大增强了
对额外功能不满意的情况下,不用进行修改,直接创建一个新的额外功能即可
1、MethodBeforeAdvice 接口作用:额外功能在运行在原始方法执行之前,进行额外功能操作。

2、before 方法的 3 个参数在实战中 ,该如何使用?
before 方法的参数在实战中,会根据需要来进行使用,不一定都会用到,也有可能都不用
- Servlet{
- service(HttpRequest request,HttpResponse response){
- request.getParameter("name") -->
-
- response.getWriter() --->
-
- }
- }
和 MethodBeforeAdvice 的区别:
MethodBeforeAdvice 只能运行在原始方法执行之前,相对来讲,功能比较单一
MethodInterceptor,可以运行在原始方法执行之前,也可以运行在原始方法执行之后,灵活性更高
注意:在这里我们选择 aopllicance 的包提供的接口

- public class Arround implements MethodInterceptor {
-
- /*
- invoke 方法的作用: 额外功能书写在 invoke
- 额外功能可以运行在原始方法之前,原始方法之后,原始方法的之前和之后
- 要先提前确定原始方法如何运行:
- 参数:MethodInvocation (类似前面提到的的Method)代表额外功能所增加给的原始方法
- invocation.proceed() 就代表对应的方法的执行
- 返回值:Object:原始方法的返回值
- */
- @Override
- public Object invoke(MethodInvocation invocation) throws Throwable {
-
- System.out.println("-----额外功能 log----");
- Object ret = invocation.proceed();
-
- return ret;
- }
- }
- <bean id="arround" class="dynamic.Arround">bean>
-
- <aop:config>
-
- <aop:pointcut id = "pc" expression = "execution(* *(..))"/>
-
-
- <aop:advisor advice-ref="arround" pointcut-ref = "pc"/>
- aop:config>
切入点:决定了额外功能加入的位置
- <aop:pointcut id="pc" expression="execution(* *(..))"/>
- exection(* *(..)) ---> 匹配了所有⽅法
1、execution : 切入点函数
2、* *(..) 切入点表达式
* *(..) ---> 所有方法

- * ---> 修饰符 返回值
- * ---> ⽅法名
- ()---> 参数表
- ..---> 对于参数没有要求 (参数有没有,参数有⼏个都⾏,参数是什么类型的都⾏)
定义 login 方法作为切入点
* login(..)
定义 login 方法,且 login 方法有两个字符串类型的参数,作为切入点
* login(String,String)
注意:如果参数的类型不是 java.lang 包当中的,那必须写类型的权限定名
* regidter(proxy.User)
指定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上对应的额外功能了
- <aop:config>
-
- <aop:pointcut id = "pc" expression = "execution(* proxy.UserServiceImpl.*(..))"/>
-
-
- <aop:advisor advice-ref="arround" pointcut-ref = "pc"/>
- aop:config>
类中所有方法加入额外功能:
- ## 类中所有的方法都加入了额外功能
- * proxy.userService.*(..)
忽略包:
- ## 类只存在一层包
- * *.UserServiceImpl.*(..)
-
- ## 类存在多层包
- * *..UserServiceImpl.*(..)
指定包作为额外功能加入的位置,自然包中的所有类及其方法都会加入额外的功能

- # 切入点包中的所有类,必须在 proxy 中,不能在 proxy 包的子包中
-
- * proxy.*.*(..)
- # 切入点当前包及当前包的子包都生效
-
- * proxy..*.*(..)
切入点函数:用于执行切入点表达式
execution 是最为重要的切入点函数,功能最全
可以执行:方法切入点表达式,类切入点表达式,包切入点表达式
弊端:execution 在执行切入点表达式的时候,书写麻烦
注意:其它的切入点函数,仅仅简化的是 execution 书写复杂度,功能上完全一致
作用:主要用于函数(方法)参数的匹配
切入点:方法的参数必须得是两个字符串类型的参数
- execution( * *(String,String) )
-
- args(String,String)
作用:主要用于进行类、包切入点表达式的匹配
切入点: UserServiceImpl
- execution( * *..UserService.*(..) )
-
- within( * .. UserServiceImpl )
-
-
- execution(* proxy..*.*(..) )
-
- within( proxy.. )
作用:为具有特殊注解的方法加入额外功能
< aop: pointcut id = "" expression = "@annotation(Log)"/>
指的是:整合多个切入点函数一起配合工作,进而完成更为复杂的需求
and 与操作
案例:方法名:login 参数:String ,String
- execution ( * login(String,String) )
-
- execution ( * login(..) ) and args( String,String )
注意:与操作,不能用于同种类型的切入点函数
例如: execution and execution
or 或操作
- 案例:register⽅法 和 login⽅法作为切⼊点
- execution(* login(..)) or execution(* register(..))