从基础到进阶:Spring IOC与AOP的全面解析

137 阅读13分钟

Spring

代理模式

什么是代理

代理模式也叫做委托模式,指的是隐藏真实的对象,通过代理对象来实现对真实对象的操作。在现实生活中,有很多代理模式的实例。可以用买房来解释代理模式:

买房子通常有两种途径,一:开发商自己带客户去看房买房,二:和中介公司合作,由中介公司带客户看房买房、

对于第二种方式,就相当于开发商是真实的对象,被隐藏了,而中介公司就属于代理代理对象,其中中介公司也有带客户看房和买房的功能。但最终的交易还是通过开发商进行交易的。

通常来说分为三种角色,Subject(抽象主题)、RealSubject(真实对象,也叫做被代理对象),Proxy(代理对象)。

  • Subject 抽象主题角色:可以是抽象类,也可以是接口。抽象主题是一个普通的业务类型,无特殊要求。
  • RealSubject 真实主题角色:也叫做被委托角色被代理角色,是业务逻辑的具体执行者,需要实现Subject,并实现所有的方法。
  • Proxy 代理主题角色:也叫做委托类代理类。它是用来被其他使用者调用的,但是在该类中,对业务真实处理的逻辑是调用的RealSubject的。

重点:代理模式的主要作用是在不修改具体业务代码的情况下对相关功能进行扩张,比如增加日志,事务管理等。

静态代理

我们通过前面介绍代理模式基本上能够清楚代理模式是用来做什么的,还是以上面买房的案例,我们利用静态代理模式,通过代码来实现它。

  1. 创建Subject角色

     public interface BuyHouse {
     ​
         void seeHouse();
     ​
         void payForHouse();
     }
    
  2. 创建RealSubject角色

     //开发商
     public class HouseS implements BuyHouse{
         @Override
         public void seeHouse() {
             System.out.println("带客户看房!!!");
         }
     ​
         @Override
         public void payForHouse() {
             System.out.println("为房子付款!!!");
         }
     }
    
  3. 创建Proxy角色

     public class HouseIntermediary implements BuyHouse{
     ​
         private BuyHouse buyHouse;
         //将真实对象委托给代理角色
         public HouseIntermediary(BuyHouse buyHouse) {
             this.buyHouse = buyHouse;
         }
     ​
         @Override
         public void seeHouse() {
             System.out.println("中介带客户!!!");
             //代理角色中的逻辑处理实际上是调用的真实对象的方法
             buyHouse.seeHouse();
         }
     ​
         @Override
         public void payForHouse() {
             System.out.println("中介带客户!!!");
             buyHouse.payForHouse();
         }
     }
    
  4. 测试

     public class StaticProxyTest {
         public static void main(String[] args) {
             BuyHouse buyHouse = new HouseS();
             HouseIntermediary  intermediary = new HouseIntermediary(buyHouse);
             //所有的操作都是调用的代理对象,隐藏了真实对象
             intermediary.seeHouse();
             intermediary.payForHouse();
         }
     }
    

静态代理的优缺点

优点:

  • 可以使得业务分工更加的纯粹,公共业务和实际业务拆离开来
  • 公共的业务(代理类)发生改变时,更加的集中和方便

缺点:

  • 需要独有的代理类,造成类文件多了,工作量多了。
  • 如果Subject接口增加或者减少了内容,那么修改起来也比较麻烦。

动态代理

为了解决静态代理代理类变多的问题,可以通过动态代理来实现同样的代理模式。

动态代理主要涉及两个类,一个是接口InvocationHandler,另一个是Proxy类。

InvocationHandler:调用处理程序,每个被代理实例都绑定一个调用处理程序,当被代理实例的方法被调用时,方法调用都会被编码并分派到其调用处理程序的invoke方法上。

Proxy:提供了创建动态代理类和实例的方法。

我们还是使用动态代理来实现上面买房看房的实例

  1. 创建Subject角色

     public interface BuyHouse {
     ​
         void seeHouse();
     ​
         void payForHouse();
     }
    
  2. 创建RealSubject角色

     public class HouseS implements BuyHouse {
         @Override
         public void seeHouse() {
             System.out.println("带客户看房!!!");
         }
     ​
         @Override
         public void payForHouse() {
             System.out.println("为房子付款!!!");
         }
     }
    
  3. 创建类并实现InvocationHandler接口,书写自己的调用处理程序。

     public class MyInvocationHandler implements InvocationHandler {
     ​
         //自定义调用处理程序绑定的真实对象
         Object buyHouse;
     ​
         public MyInvocationHandler(Object buyHouse) {
             this.buyHouse = buyHouse;
         }
     ​
         /**
          * 被代理实例调用方法后会自动调用到绑定的调用程序的invoke方法。
          * @param proxy :被代理对象
          * @param method :被代理实例调用的方法
          * @param args :被代理实例调用的方法所需要的参数
          * @return
          * @throws Throwable
          */
         @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
             System.out.println("被代理的 "+proxy.getClass().getName()+" 类调用了"+method.getName()+"方法!!");
             //在使用时,生成代理实例后调用方法后其实时调用的method.invoke()。所以可以在此处的前后对实际业务代码进行横向扩展,增加日志或者进行事务管理等。
             Object invoke = method.invoke(buyHouse, args);
             return invoke;
         }
     }
    
  4. 生成代理实例进行测试

 public class Test {
 ​
     public static void main(String[] args) {
         //创建真实对象
         BuyHouse buyHouse = new HouseS();
         //真实对象绑定调用处理程序
         MyInvocationHandler invocationHandler = new MyInvocationHandler(buyHouse);
         //生成代理对象实例,通过Proxy.newProxyInstance()
         BuyHouse buyHouse1 = (BuyHouse) Proxy.newProxyInstance(invocationHandler.getClass().getClassLoader(), HouseS.class.getInterfaces(), invocationHandler);
         //通过代理对象调用方法。会自动调用到调用处理程序的invoke方法
         buyHouse1.seeHouse();
         buyHouse1.payForHouse();
     }
 }

Proxy类的newProxyInstance方法,是用来生成代理对象

 public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

loader:类加载器

interfaces:真实对象的所有接口,也就是Subject角色。

h:自定义的调用处理程序

Spring两大特性

一个框架的产生必将用是来解决在开发过程中的一些复杂繁琐的工作,Spring翻译过来是“春天”的意思。表示开发者的春天来了。

Spring框架最主要的两大特性是IOC和AOP。下面我们来详细了解一下!

IOC

IOC并不是一项技术,而是一种思想。其主要的作用是用来管理bean。

我们使用传统的方法创建一个类的实例需要通过关键字:new

而在spring中,我们可以将所有的bean交由spring进行统一管理,在spring中有一个bean工厂,当需要使用类的实例时,只需在bean工厂中获取。

这种把传统的类实例创建方式交由spring进行统一管理的思想就叫做IOC。

代码实例

  1. 添加依赖

     <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-webmvc</artifactId>
           <version>5.1.10.RELEASE</version>
     </dependency>
    
  2. 创建Hello类

     public class Hello {
         private String name;
         public String getName() {
             return name;
         }
         public void setName(String name) {
             this.name = name;
         }
         public void show(){
             System.out.println("Hello,"+ name );
         }
     }
    
  3. 创建并编写spring配置文件-applicationContext.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"
            xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd">
         
         
     </beans>
    
  4. 注册bean到spring中

     <?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就是java对象 ,交由Spring创建和管理-->
         <bean id="hello" class="com.example.Hello">
             <property name="name" value="Spring"/>
         </bean>
     </beans>
    
  5. 测试

     public class Test {
         public static void main(String[] args) {
             ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
             Hello hello = (Hello) context.getBean("hello");
             hello.show();
         }
     }
    

    通过上面的实例可以看到我们没有new Hello类,却可以使用Hello的实例去调用方法,因为Hello的实例创建和属性赋值都是spring做的。

IOC管理对象的方式

基于无参构造管理bean

  1. 创建无参构造类

     public class Hello {
         private String name;
         public String getName() {
             return name;
         }
         public void setName(String name) {
             this.name = name;
         }
         public void show(){
             System.out.println("Hello,"+ name );
         }
     }
    
  2. 将无参构造类交由spring管理

     <?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就是java对象 ,交由Spring创建和管理-->
         <bean id="hello" class="com.example.Hello">
             <!--直接为属性注入值,name的值为setName()去掉set后的转小写-->
             <property name="name" value="Spring"/>
         </bean>
     </beans>
    

基于有参构造管理bean

  1. 创建有参构造类

     public class Hello2 {
         private String name;
         public Hello2(String name) {
             this.name = name;
         }
         public String getName() {
             return name;
         }
         public void setName(String name) {
             this.name = name;
         }
         public void show(){
             System.out.println("Hello,"+ name );
         }
     }
    
  2. 将有参构造类交由spring管理

     <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 id="hello2" class="com.example.Hello2">
             <constructor-arg name="name" value="spring2"></constructor-arg>
         </bean>
         
         <!--方法二:通过构造函数参数个数下标创建-->
         <bean id="hello2" class="com.example.Hello2">
             <constructor-arg index="0" value="spring2"></constructor-arg>
         </bean>
         
         <!--方法三:通过构造函数参数类型创建-->
         <bean id="hello2" class="com.example.Hello2">
             <constructor-arg type="java.lang.String" value="spring2"></constructor-arg>
         </bean>
         
     </beans>
    

DI

DI为依赖注入,它的概念相对来说比较模糊,DI和IOC应该是控制反转的不同层面的解释。

我个人理解可能就是:我们的bean交由spring进行管理时,bean中的属性需要赋值,就需要通过DI将属性值注入到spring管理的bean中。

AOP

Spring中的AOP思想就是基于动态代理实现的,动态代理要解决的问题就是在不动实际业务代码的前提上,去横向的增加一些功能。所以Spring AOP要解决的事情也是如此,即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .

SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

Advice.png

使用Spring的AOP思想完成具体需求

使用Spring的aop需要添加aspectjweaver依赖

 <dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjweaver</artifactId>
     <version>1.9.4</version>
 </dependency>

方式一:使用Spring api实现

在spring中提供了一些增强接口,我们可以通过实现这些增强接口来完成aop,这些内置的增强接口位于org.springframework.aop包下面

  1. 编写业务接口和实现类

     public interface MakeFood {
         //做牛奶
         void makeMilk();
         //做面条
         void makeNoddles();
     }
    
     public class RealMakeFood implements MakeFood{
         public void makeMilk() {
             System.out.println("开始做牛奶");
         }
         public void makeNoddles() {
             System.out.println("开始做面条");
         }
     }
    
  2. 创建并编写前置增强类

     //spring中内置了很多增强类接口,通过实现这些接口来达到aop的效果
     public class MyBeforeAdvice implements MethodBeforeAdvice {
         /**
          *
          * @param method : 要执行的目标真实对象的方法
          * @param objects : 被调用的方法的参数
          * @param o : 目标真实对象
          * @throws Throwable
          */
         public void before(Method method, Object[] objects, Object o) throws Throwable {
             System.out.println(o.getClass().getName()+"的"+method.getName()+"方法被调用!");
         }
     }
    
  3. 创建并编写后置增强类

     public class MyAfterAdvice implements AfterReturningAdvice {
     ​
         /**
          *
          * @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("在"+target.getClass().getName()+"的"+method.getName()+"方法调用后执行!");
         }
     }
    
  4. 将所有类注册到Spring中,然后配置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
             <!--需要导入aop约束-->
             http://www.springframework.org/schema/aop
             http://www.springframework.org/schema/aop/spring-aop.xsd">
         <!--注册bean-->
         <bean id="realMakeFood" class="com.example.springApi.RealMakeFood"/>
         <bean id="beforeAdvice" class="com.example.springApi.MyBeforeAdvice"/>
         <bean id="afterAdvice" class="com.example.springApi.MyAfterAdvice"/>
     ​
         <!--配置aop-->
         <aop:config>
             <!--切入点  expression属性为一个表达式,配置切入点的位置-->
             <aop:pointcut id="pointcut" expression="execution(* com.example.springApi.RealMakeFood.*(..))"/>
             <!--配置环绕通知 advice-ref属性用来绑定通知对象,pointcut-ref属性用来绑定切入点-->
             <aop:advisor advice-ref="beforeAdvice" pointcut-ref="pointcut"/>
             <aop:advisor advice-ref="afterAdvice" pointcut-ref="pointcut"/>
         </aop:config>
     </beans>
    
 **例: execution (* com.sample.service..*.*(..))  表达式详解
 ​
 整个表达式可以分为五个部分:
 ​
 1、execution()::表达式主体。
 ​
 2、第一个*号:表示返回类型, *号表示所有的类型。
 ​
 3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service包、子孙包下所有类的方法。
 ​
 4、第二个*号:表示类名,*号表示所有的类。
 ​
 5、.*(..):表示任何方法名,括号表示参数,两个点表示任何类型的参数

0. 编写测试

     public class Test {
         public static void main(String[] args) {
             ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
             //此处要使用MakeFood,使用RealMakeFood会报错
             MakeFood realMakeFood = (MakeFood) context.getBean("realMakeFood");
             realMakeFood.makeMilk();
         }
     }

方式二:使用自定义类来实现aop

  1. 编写业务接口和实现类

     public interface MakeFood {
         //做牛奶
         void makeMilk();
         //做面条
         void makeNoddles();
     }
    
     public class RealMakeFood implements MakeFood{
         public void makeMilk() {
             System.out.println("开始做牛奶");
         }
         public void makeNoddles() {
             System.out.println("开始做面条");
         }
     }
    
  2. 创建自定义增强类

     public class DiyAdvice {
     ​
         public void before(){
             System.out.println("前置通知!!!");
         }
     ​
         public void after(){
             System.out.println("后置通知!!!");
         }
     }
    
  3. 将所有类注册到Spring中,然后配置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
             <!--需要导入aop约束-->
             http://www.springframework.org/schema/aop
             http://www.springframework.org/schema/aop/spring-aop.xsd">
         <!--注册bean-->
         <bean id="diyAdvice" class="com.example.divClass.DiyAdvice"/>
         <bean id="realMakeFood2" class="com.example.divClass.RealMakeFood"/>
     ​
         <!--aop配置-->
         <aop:config>
             <!--配置并绑定自定义增强类-->
             <aop:aspect ref="diyAdvice">
                 <!--配置切入点-->
                 <aop:pointcut id="pointcut" expression="execution(* com.example.divClass.RealMakeFood.*(..))"/>
                 <!--绑定切入点和增强方法-->
                 <aop:before method="before" pointcut-ref="pointcut"/>
                 <aop:after method="after" pointcut-ref="pointcut"/>
             </aop:aspect>
         </aop:config>
     </beans>
    
  4. 编写测试

     public class Test {
         public static void main(String[] args) {
             ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
             //此处要使用MakeFood,使用RealMakeFood会报错
             MakeFood realMakeFood = (MakeFood) context.getBean("realMakeFood");
             realMakeFood.makeMilk();
         }
     }
    

方式三:通过注解方式使用aop

  1. 编写业务接口和实现类

     public interface MakeFood2 {
         //做牛奶
         void makeMilk();
         //做面条
         void makeNoddles();
     }
    
     public class RealMakeFood2 implements MakeFood2 {
         public void makeMilk() {
             System.out.println("开始做牛奶");
         }
         public void makeNoddles() {
             System.out.println("开始做面条");
         }
     }
    
  2. 创建切面类

     @Aspect
     public class MyAspect {
     ​
         @Before("execution(* com.example.zhujie.RealMakeFood2.*(..))")
         public void before(){
             System.out.println("前置通知!!!");
         }
         @After("execution(* com.example.zhujie.RealMakeFood2.*(..))")
         public void after(){
             System.out.println("后置通知!!!");
         }
     ​
         @Around("execution(* com.example.zhujie.RealMakeFood2.*(..))")
         public void  around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
             System.out.println("环绕前置开始");
             Object proceed = proceedingJoinPoint.proceed();
             System.out.println("环绕后置开始");
         }
     }
    
  3. 将所有类注册到Spring中,然后配置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
             <!--需要导入aop约束-->
             http://www.springframework.org/schema/aop
             http://www.springframework.org/schema/aop/spring-aop.xsd">
         <!--方式三:通过注解方式-->
         <!--注入bean-->
         <bean id="realMakeFood3" class="com.example.zhujie.RealMakeFood2"/>
         <bean id="myAspect" class="com.example.zhujie.MyAspect"/>
         <!--声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。-->
         <aop:aspectj-autoproxy/>
     </beans>
    

    注意:

    <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口, 则spring将自动使用CGLib动态代理。

  4. 编写测试

     public class Test {
         public static void main(String[] args) {
             ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
             //此处要使用MakeFood,使用RealMakeFood会报错
             MakeFood2 realMakeFood = (MakeFood2) context.getBean("realMakeFood3");
             realMakeFood.makeMilk();
         }
     }
    

表达式标签(10种)

  • execution:用于匹配方法执行的连接点
  • within:用于匹配指定类型内的方法执行
  • this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
  • target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配
  • args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
  • @within:用于匹配所以持有指定注解类型内的方法
  • @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
  • @args:用于匹配当前执行的方法传入的参数持有指定注解的执行
  • @annotation:用于匹配当前执行方法持有指定注解的方法
  • bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法

Spring事务管理

事务的作用是为了保证数据一致性和准确性。

事务的特性:

原子性

一致性

持久性

隔离性

事务的四个特性整合起来就是保证一套业务功能内的接口要么都成功,要么都失败!!!

spring中支持声明式事务和编程式事务。

  • 编程式事务:通过代码的方式手动的commit和rollback。需要修改业务代码,不方便
  • 声明式事务:通过配置文件进行配置,然后通过sringAOP的方式织入,不需要修改业务代码。

事务的传播特性

传播行为描述
PROPAGATION_REQUIRED如果没有,就开启一个事务;如果有,就加入当前事务(方法B看到自己已经运行在 方法A的事务内部,就不再起新的事务,直接加入方法A)
RROPAGATION_REQUIRES_NEW如果没有,就开启一个事务;如果有,就将当前事务挂起。(方法A所在的事务就会挂起,方法B会起一个新的事务,等待方法B的事务完成以后,方法A才继续执行)
PROPAGATION_NESTED如果没有,就开启一个事务;如果有,就在当前事务中嵌套其他事务
PROPAGATION_SUPPORTS如果没有,就以非事务方式执行;如果有,就加入当前事务(方法B看到自己已经运行在 方法A的事务内部,就不再起新的事务,直接加入方法A)
PROPAGATION_NOT_SUPPORTED如果没有,就以非事务方式执行;如果有,就将当前事务挂起,(方法A所在的事务就会挂起,而方法B以非事务的状态运行完,再继续方法A的事务)
PROPAGATION_NEVER如果没有,就以非事务方式执行;如果有,就抛出异常。
PROPAGATION_MANDATORY如果没有,就抛出异常;如果有,就使用当前事务