Spring详解(IOC和AOP)

目录

一,什么是Spring?

二,控制反转(IOC)

2.1 控制反转是什么?

2.2 如何实现控制反转?

2.3 依赖注入

2.3.1 set注入简单类型

2.3.2 注入Bean

 2.3.3 注入数组

 2.3.4 注入List集合

 2.3.5 注入Set集合

 2.3.6 注入Map集合

 2.3.7 注入Properties

2.4 Bean的作用域

2.5 IOC的注解式开发

2.5.1声明Bean的注解常见的有四个

2.5.2 spring给出四个注解用来给属性赋值:

 2.6 IOC的全注解开发

三,AOP

 3.1:AOP中的名词解释

3.2切入点表达式

 3.3 基于AspectJ的AOP注解式开发

 3.4 通知类型

3.4.1 通知的执行顺序

3.4.2 有多个切面时,切面的执行顺序

3.5全注解式开发AOP

3.6 AOP的XML开发(了解)


一,什么是Spring?

百度百科这样形容Spring:Spring框架是一个开放源代码J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于J2EE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC

总之:Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的框架。

二,控制反转(IOC)

2.1 控制反转是什么?

  • 控制反转是一种思想。
  • 控制反转是为了降低程序耦合度,提高扩展度,达到OCP原则,达到DIP原则。
  • 控制反转就是把对象的控制权交出去,交给容器(这里是Spring容器)进行管理

2.2 如何实现控制反转?

  • DI(Dependency Injection):依赖注入
  • 依赖注入通常有两种方法:set注入和构造方法注入

2.3 依赖注入

  1. 构造方法注入
    1. 通过构造方法来给属性赋值
  2.  set方法注入
    1. set方法注入,是基于set方法实现的,底层会通过反射机制调用该属性的set方法给属性进行赋值,这种方法要求该属性必须提供对外的set方法。

下面分别演示两种注入方法:

先创建一个Maven工程:

点击下一步并命名项目: 

 然后无脑下一步即可。

这时来准备我们的依赖,因为是maven工程,所以这里只给出坐标


        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.22</version>
        </dependency>

​

 这里引入spring-context后会关联引入一些其他的jar包,如下图:

 然后就可以写代码了。

构造方法注入是spring容器通过构造方法来给属性赋值、所以类必须有构造方法

先写一个实体类:

package com.hkd.Bean;

public class user {
    String name;
    int age;

    public user() {
    }

    public user(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "user{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

 写好实体类后就可以 编写Spring配置文件了。

在IDEA中文件的目录结构如下:

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 id="user" class="com.hkd.Bean.user">
        <constructor-arg index="0" value="张三"></constructor-arg>
        <constructor-arg index="1" value="20"></constructor-arg>
    </bean>


</beans>



​

 运行后可看到结果:

 这里constructor-arg标签中的index属性指的是,参数在构造方法中的顺序。

除了index可以指定参数外也可用参数的名称来指定:

 如果需要的参数是引用类型则按照下面写法:

2.3.1 set注入简单类型

        简单类型包括:

  • 基本本数据类型 
  • 基本数据类型对应的包装类
  • String或其他的CharSequence⼦类
  • Number⼦类
  • Date⼦类
  • Enum⼦类
  • URI
  • URL
  • Temporal⼦类
  • Locale
  • Class
  • 另外还包括以上简单值类型对应的数组类型

简单类型注入的代码大致为:

     <bean id=""  class="">
        <property name=""  value=""></property>
     </bean>

 在这里有几个标签:

  • bean:具有id属性和class属性,id是spring容器帮忙创建的对象的名称这里的user等同于t通过user user =  new user();创建的user对象,class的值表示要创建的对象的类路径,spring容器拿到这个路径后会通过反射机制创建该类的对象。
  • property:property单词本身就有属性性质的意思,这里指的也是对象中的属性,其name属性值为对象的属性名,value值就是要给对象中属性赋予的值。

先创建一个实体类,提供set和构造方法

package com.hkd.Bean;

public class user {
    String name ;
    int age;
    
    public user() {
    }

    public user(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "user{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

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 id="user"  class="com.hkd.Bean.user">
        <property name="name" value="张三"></property>
        <property name="age"  value="20"></property>
     </bean>
</beans>

到这里准备工作完成可以进行set注入的测试了:

写一个测试类如下:

        

package myTest;

import com.hkd.Bean.user;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class DiTest {
    @Test
    public void TestSetDI(){
        ApplicationContext ioc  = new ClassPathXmlApplicationContext("applicationContext.xml");
        user user = ioc.getBean("user", user.class);
        System.out.println(user);

    }
}

 控制台可看到结果为:

简单类型注入成功。

2.3.2 注入Bean

        如果在一个对象在另一个对象中作为一个属性,那么在注入的时候就不能用和简单类型一样的方法进行注入了,此时的注入格式如下:

    <bean id="" class="">
            <property name="" ref=""></property>
    </bean>

 这里可以看到相比于注入简单类型,注入Bean知识将value=""换为了ref="",此时ref=""中的值应填写为一个Bean的id就是spring容器管理的一个对象的名称。

如在刚才的程序中添加一个Vip类,类中有一个user属性,则可以按照一下方式写:

VIP类:

package com.hkd.Bean;

public class Vip {
    int id;
    int balance;
    user user;

    public Vip() {
    }

    public Vip(int id, int balance) {
        this.id = id;
        this.balance = balance;
    }

    public void setUser(com.hkd.Bean.user user) {
        this.user = user;
    }

    public void setId(int id) {
        this.id = id;
    }
    
    public void setBalance(int balance) {
        this.balance = balance;
    }
    @Override
    public String toString() {
        return "Vip{" +
                "id=" + id +
                ", balance=" + balance +
                ", user=" + user +
                '}';
    }
}

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 id="user1"  class="com.hkd.Bean.user">
        <property name="name" value="张三"></property>
        <property name="age"  value="20"></property>
     </bean>
    <bean id="vip" class="com.hkd.Bean.Vip">
        <property name="id" value="1"></property>
        <property name="balance" value="123"></property>
        <property name="user" ref="user1"></property>
    </bean>
   
</beans>

在控制台可以看到user1对象被成功注入到了Vip了中。                       

 

 除了这种方式还有一种注入Bean的方式:

 2.3.3 注入数组

        当输入的数组类型是简单类型时:

    <bean id="user" class="com.hkd.Bean.user">
        <property name="bobby">
            <array>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>烫头</value>
            </array>
        </property>
    </bean>

非简单类型时:

     <bean id="user" class="com.hkd.Bean.user">
        <property name="bobby">
            <array>
                <ref bean="爱好1"></ref>
                <ref bean="爱好2"></ref>
                <ref bean="爱好3"></ref>
            </array>
        </property>
    </bean>

 2.3.4 注入List集合

    <bean id="user" class="com.hkd.Bean.user">
        <property name="bobby">
            <list>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>烫头</value>
            </list>
        </property>
    </bean>

 注:当List集合中是简单类型时使用value标签反之使用ref标签

 2.3.5 注入Set集合

      <bean id="user" class="com.hkd.Bean.user">
        <property name="bobby">
            <set>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>烫头</value>
            </set>
        </property>
    </bean>

 注:当Set集合中是简单类型时使用value标签反之使用ref标签 

 2.3.6 注入Map集合

    <bean id="user" class="com.hkd.Bean.user">
        <property name="bobby">
            <map>
                <entry key="1" value="抽烟"></entry>
                <entry key="2" value="喝酒"></entry>
                <entry key="3" value="烫头"></entry>
            </map>
        </property>
    </bean>

 要点:

  • 如果key是非简单类型,则使用key-ref
  • 如果value是非简单类型,则使用value-ref

 2.3.7 注入Properties

    <bean id="dataSource" class="com.hkd.Util.DataSource">
        <property name="properties">
            <props>
                <prop key="driver">com.mysql.cj.jdbc.Driver</prop>
                <prop key="url">jdbc:mysql://localhost:3306/spring</prop>
                <prop key="username">root</prop>
                <prop key="password">123456</prop>
            </props>
        </property>
    </bean>

2.4 Bean的作用域

        Spring创建的对象默认是单例的。

        如果想让对象是多例的可以在指定scope的属性值为prototype,这样每调用一次getBean方法都会新创建一个对象。

 默认情况下scope的值为singleton,即为单例

2.5 IOC的注解式开发

2.5.1声明Bean的注解常见的有四个

  • @Component        
  • @Controller      (用于表示层)
  • @Service          (用于业务层)
  • @Repository    (用于持久层)

这四个注解中@Controller,@Service,@Repository是@Component的别名, 四个注解用法相同,对程序来说这三个都是一样的,那么为什么还要搞三个别名呢--这是给程序员看的,为了提高高代码的可读性。

这里拿@Component注解举例,其他三个用法相同:

@Component注解写在类上

package com.hkd.Bean;

import org.springframework.stereotype.Component;

@Component(value = "person")
public class preson {
    String name;
}

 这里的value值就是spring容器在创建对象时的对象名,可以忽略不写,此时对象名为首字母小写的类名

当然仅仅在类中写上注解还不行,spring容器怎么样才能发现,或者说spring容器怎么判断程序员是否写了注解呢?

这时要在spring的核心配置文件中告诉spring容器去哪些包中找带有注解的类,即需要添加包扫描,spring容器会到对应的包中检查是否写了注解,如果写了注解,那就创建对象。

 如果是多个包有两种解决⽅案:

  • 第⼀种:在配置⽂件中指定多个包,⽤逗号隔开。
  • 第⼆种:指定多个包的共同⽗包。

 此时需要添加context命名空间

 至此对象就可以成功创建了,那有了对象要怎么给属性赋值呢?

2.5.2 spring给出四个注解用来给属性赋值:

  • @Value
  • @Autowried
  • @Qualifier
  • @Resource

 @Value : 当属性的类型是简单类型时使用@Value进行注入,

@Value可以写在属性上,构造方法里,set方法上,且当@Value写在属性上时可以不提供set方法

@Autowried :当属性的类型是引用类型时使用@Autowried注入(按类型注入)

@Autowried注解可以标注在哪⾥?

  • 构造⽅法上
  • ⽅法上
  • 形参上
  • 属性上
  • 注解上         

该注解有⼀个required属性,默认值是true,表示在注⼊的时候要求被注⼊的Bean必须是 存在的,如果不存在则报错。如果required属性设置为false,表示注⼊的Bean存在或者不存在都没 关系,存在的话就注⼊,不存在的话,也不报错。 

因为@Autowried是按类型注入的,所以如果在spring容器中有多个相同类型的对象的话,@Autowried在注入时就会发生混乱,此时程序会报错。

这里的bigBoss和littleBoss是Boss的实现类

 当我们进行注入的时候就会报错,发现了两个对象。

 有没有办法解决这个问题呢?

肯定是有的,还有一个@Qualifier,这两个注解进行搭配使用就可以根据名称进行注入。

我们将上面代码稍加改动

此时再运行代码就不会出错且在控制台输出:

@Resource:@Resource也可以完成⾮简单类型注⼊。

                        那它和@Autowired注解有什么区别?

  •  Resource注解是JDK扩展包中的,也就是说属于JDK的⼀部分。所以该注解是标准注解,更加具 有通⽤性。@Autowired注解是Spring框架⾃⼰的。
  • @Resource注解默认根据名称装配byName,未指定name时,使⽤属性名作为name。通过name 找不到的话会⾃动启动通过类型byType装配。 @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解⼀起 ⽤。
  • @Resource注解⽤在属性上、setter⽅法上。 @Autowired注解⽤在属性上、setter⽅法上、构造⽅法上、构造⽅法参数上。
  • @Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引⼊依赖:【如果是JDK8的话不需 要额外引⼊依赖。⾼于JDK11或低于JDK8需要引⼊以下依赖。
<dependency>
 <groupId>javax.annotation</groupId>
 <artifactId>javax.annotation-api</artifactId>
 <version>1.3.2</version>
</dependency>

 2.6 IOC的全注解开发

IOC的全注解开发就是不再写XML配置文件了,而是写一个类来代替XML配置文件

package com.hkd.Bean;


import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.hkd.Bean")
public class SpringConfig {
}

这时候我们不再创建ClassPathXmlApplicationContext对象了

 除了创建的对象不一样之外,其余操作与XML写法一致。

三,AOP

 3.1:AOP中的名词解释

  • 连接点 Joinpoint :在程序的整个执⾏流程中,可以织⼊切⾯的位置。⽅法的执⾏前后,异常抛出之后等位置。
  • 切点 Pointcut: 在程序执⾏流程中,真正织⼊切⾯的⽅法。(⼀个切点对应多个连接点)。
  • 通知 Advice:通知⼜叫增强,就是具体你要织⼊的代码。 通知包括: 前置通知 后置通知 环绕通知 异常通知 最终通知。
  • 切⾯ Aspect:  切点 + 通知就是切⾯。
  • 织⼊ Weaving: 把通知应⽤到⽬标对象上的过程。
  • 代理对象 Proxy: ⼀个⽬标对象被织⼊通知后产⽣的新对象。
  • ⽬标对象 Target: 被织⼊通知的对象。

3.2切入点表达式

什么是切入点表达式:就是表明你想要在哪个方法上织入切面(添加代码,增强功能)

execution([访问控制权限修饰符] 返回值类型 [全限定类名]⽅法名(形式参数列表) [异常])
  •  访问控制权限修饰符: 可选项。 没写,就是4个权限都包括。 写public就表示只包括公开的⽅法。
  • 返回值类型: 必填项。 * 表示返回值类型任意。
  • 全限定类名: 可选项。 两个点“..”代表当前包以及⼦包下的所有类。 省略时表示所有的类。
  • ⽅法名: 必填项。 *表示所有⽅法。 set*表示所有的set⽅法。
  • 形式参数列表: 必填项 。表示没有参数的⽅法 (..) 参数类型和个数随意的⽅法 (*) 只有⼀个参数的⽅法 (*, String) 第⼀个参数类型随意,第⼆个参数是String的。
  • 异常: 可选项。 省略时表示任意异常类型。

 3.3 基于AspectJ的AOP注解式开发

先写一个目标类:

package com.hkd.AspectJ.Service;

import org.springframework.stereotype.Service;

@Service
public class orderService {
    public void Buy(){
        System.out.println("买东西...");
    }
}

再写一个切面类:

package com.hkd.AspectJ.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class myAspect {

    @Before("execution(* com.hkd.AspectJ.Service.*.*(..))")
    public void myBefore(){
        System.out.println("前置通知执行了...");
    }
}

Spring.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: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">
    
    <context:component-scan base-package="com.hkd.AspectJ"></context:component-scan>

    <aop:aspectj-autoproxy ></aop:aspectj-autoproxy>

</beans>

这里看到在spring.xml文件中多了这样的一行代码

<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>

这句的作用是开启aop的自动代理添加完这句代码后所有带有@Aspect注解的都会生成一个代理对象

测试:

 

 3.4 通知类型

通知类型包括:

  • 前置通知:@Before ⽬标⽅法执⾏之前的通知
  • 后置通知:@AfterReturning ⽬标⽅法执⾏之后的通知
  • 环绕通知:@Around ⽬标⽅法之前添加通知,同时⽬标⽅法执⾏之后添加通知。
  • 异常通知:@AfterThrowing 发⽣异常之后执⾏的通知
  • 最终通知:@After 放在finally语句块中的通知

@Pointut注解:可以写在一个方法上作为一个切点表达式

@Pointcut("execution(* com.hkd.AspectJ.Service.*.*(..))")
    public void pointCut(){}

此时pointCut()等同于execution(* com.hkd.AspectJ.Service.*.*(..))

3.4.1 通知的执行顺序

此时我们来看无异常时通知的执行顺序:

package com.hkd.AspectJ.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class myAspect {

    @Pointcut("execution(* com.hkd.AspectJ.Service.*.*(..))")
    public void pointCut(){}

    @Before("pointCut()")
    public void myBefore(){
        System.out.println("前置通知执行了...");
    }
    @AfterReturning("pointCut()")
    public void myAfterReturning(){
        System.out.println("后置通知执行了...");
    }
    @Around("pointCut()")
    public void myAround(ProceedingJoinPoint point) throws Throwable{
        System.out.println("环绕通知开始...");
        point.proceed();
        System.out.println("环绕通知结束...");
    }
    @After("pointCut()")
    public void myAfter(){
        System.out.println("最终通知执行了...");
    }

}

测试结果:

 由此可以看出最先执行的是环绕通知,然后是前置通知,在执行目标方法,紧接着后置通知,最终通知。

当有异常时:

我们在切面类中添加异常通知,这里不做演示

在Service类中添加一条异常语句如下图:

 此时再进行测试:

从结果中我们可以看到,程序到达错误语句时报错且除了最终通知外后面的通知不再执行, 由此可见最终通知的作用类似finally代码块,这里可以写一些必须执行的代码。

3.4.2 有多个切面时,切面的执行顺序

要搞清楚切面的执行顺序,我们先来看一个注解@Order,@Order注解可以指定一个整形的数字,数字值越小,优先级越高。

我们再编写一个切面类aspect2,aspect和aspect2:分别如下:

 除了@Order值不同外,其余都一致。

测试可得到结果:

 由此我们可以清楚地看到切面的执行顺序。

3.5全注解式开发AOP

与全注解开发IOC时相同,同样创建一个类作为spring容器的配置文件,但是AOP要多添加一条自动代理的语句,添加了这条语句spring才能自动的帮你创建切面类:

 

 只需要添加一条@EnableAspectJAutoProxy就可以实现自动代理,是不是很简单

这里简单的提一下,@EnableAspectJAutoProxy有一个ProxyTargetClass属性,为布尔类型,当其值为true的时候,表示使用cglib动态代理,为false时使用JDK动态代理,JDk动态代理只能代理接口,cglib动态代理既能代理接口又能代理了类,当值为false时,如果没有接口则还是会代理类,在Spring5中AOP的默认代理方式是JDK动态代理。在XML配置中自动代理的标签<aop:aspectj-autoproxy >也有这样的属性

 

3.6 AOP的XML开发(了解)

直接编写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"
       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">

    <!--切面类,上文有源码,但是去掉了注解-->
    <bean id="myAspect" class="com.hkd.AspectJ.aspect.myAspect"></bean>
    <bean id="orderService" class="com.hkd.AspectJ.Service.orderService"></bean>



<!--    AOP配置-->
    <aop:config>
<!--        配置切点-->
        <aop:pointcut id="pointCut" expression="execution(* com.hkd.AspectJ.Service.*.*(..))"/>
<!--        配置切面:切点+通知-->
<!--          这里ref的值为上面创建的切面类 -->
        <aop:aspect ref="myAspect">
<!--        method属性的值为切面类中的方法-->
            <aop:before method="myBefore" pointcut-ref="pointCut"></aop:before>
         </aop:aspect>
    </aop:config>

</beans>

进行测试: 

 成功得到结果:

那么到这里对IOC的AOP的大致内容便结束了,这只是我个人的一篇学习笔记,如果有错误,还希望大家能够指正,我们一起进步!!

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值