Spring学习详解笔记(持续完善……)

概述

SSM框架是目前常用的框架,那框架是什么勒,简单来说,框架就是别人已经写好的软件,已经写好了模板,简化我们开发。后续我们如果需要进行其他开发,只需要进行一些增加删除。

SSM框架分别为:
Spring:service处理框架,主要进行创建对象以及进行一些业务处理
SpringMVC:主要进行一些请求的处理
Mybatis:进行数据库的处理,JDBC的加强

一、Spring 框架

1.Spring简介

Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发
的复杂性而创建的。Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。Spring 是可
以在 Java SE/EE 中使用的轻量级开源框架。

2.Spring 优点

  1. 轻量
  2. 解耦合
  3. 支持AOP
  4. 容易集成其他框架

3.Spring 架构图

这里看看就好,混个眼熟,现在可能看不懂,到时候深入了解了可以在回来瞅一瞅
Spring架构图

4.IOC控制反转

控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代
码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对
象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,
依赖的管理。

简单来说:之前我们创建一个对象是通过new来创建的。例如:创建一个Data对象 new Data(),但是现在我们在框架中不需要自己来创建对象,框架的容器会自动来实现对象的创建和赋值,容器是啥,可以把他看成一个装对象的东东。

其中IOC是利用DI(Dependency Injection)依赖注入实现的。

依赖注入 DI 是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建
被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。

5.Spring 第一个程序

5.1 创建一个maven项目

首先创建一个maven项目,选择模板quickstart,其实也可以不选模板,创建一个空项目,但是选择模板的好处是能为我们创建相应的文件夹,如果不选模板,那我们就自己创建相应的目录
创建maven项目

5.2然后在maven中pom.xml文件中添加spring的依赖

<!--Spring 依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>

5.3定义接口以及实现接口实体类

接口:

public interface SomeService {
    public void doSome();
}

实体类

public class SomeServiceImpl implements SomeService {
    public void doSome() {
        System.out.println("dosome");
    }
}

5.4编写spring的配置文件

在resources目录下新建配置文件,配置文件一般取名applicationContext.xml
创建spring配置文件

5.5编写bean标签

id:对象的自定义名称,唯一值。 spring通过这个名称找到对象
name:对象的自定义名称,可以有多个值。
class:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类), spring能创建一个非自定义类的对象吗, 创建一个存在的某个类的对象。

 <bean name="doSomeService1,doSomeService2" class="com.springtest.service.impl.SomeServiceImpl"/>
 <bean id="doSomeService3" class="com.springtest.service.impl.SomeServiceImpl"/>
 <bean id="mydate" class="java.util.Date" />

5.6编写测试类

public class MyTest {
    @Test
    public void test1() {
        String config = "applicationContext.xml";
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(config);
        SomeService doSomeService = (SomeService) classPathXmlApplicationContext.getBean("doSomeService1");
        doSomeService.doSome();
    }
}

6. 基于xml的DI

bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。
初始化是由容器自动完成的,称为注入。
根据注入方式的不同,常用的有两类:set 注入、构造注入。

6.1set注入

set注入(设值注入) :spring调用类的set方法, 你可以在set方法中完成属性赋值

6.1.1 简单类型的set注入
 <bean id="xxx" class="yyy">
<property name="属性名称" value="属性值" />
</bean>
6.1.2 引用类型的set注入 : spring调用类的set方法

ref的值必须为某 bean 的 id 值。

 <bean id="xxx" class="yyy">
<property name="属性名称" ref="bean的id(对象的名称)" />
</bean>
6.1.3 set注入例子
1.创建实体类Student
public class Student {
    private String name;
    private int id;
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }

}
2.编写applicationContext.xml文件
    <bean id="student" class="domain.Student">
        <property name="id" value="1"/>
        <property name="name" value="张三"/>
        <!--set注入,如果属性为对象,使用ref=""进行赋值-->
    </bean>
3.编写测试类
public class MyTest {
    @Test
    public void test(){
        String config = "applicationContext.xml";
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(config);
        Student st = (Student) classPathXmlApplicationContext.getBean("student");
        System.out.println(st.toString());

    }
}

set注入使用property进行赋值操作时需要保证实体类有set方法

6.2构造注入

构造注入:spring调用类有参数构造方法,在创建对象的同时,在构造方法中给属性赋值。
构造注入使用 <constructor-arg> 标签
<constructor-arg> 标签:一个<constructor-arg>表示构造方法一个参数。
<constructor-arg> 标签属性:
name:表示构造方法的形参名
index:表示构造方法的参数的位置,参数从左往右位置是 0 , 1 ,2的顺序
value:构造方法的形参类型是简单类型的,使用value
ref:构造方法的形参类型是引用类型的,使用ref

6.3 引用类型自动注入

引用类型的自动注入: spring框架根据某些规则可以给引用类型赋值。·不用你在给引用类型赋值了
       使用的规则常用的是byName, byType.
       1.byName(按名称注入) : java类中引用类型的属性名和spring容器中(配置文件)<bean>的id名称一样,且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型。
         语法:
         <bean id="xx" class="yyy" autowire="byName">
            简单类型属性赋值
         </bean>
	  2.byType(按类型注入) : java类中引用类型的数据类型和spring容器中(配置文件)<bean>的class属性 是同源关系的,这样的bean能够赋值给引用类型
         同源就是一类的意思:
          1.java类中引用类型的数据类型和bean的class的值是一样的。
          2.java类中引用类型的数据类型和bean的class的值父子类关系的。
          3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的
         语法:
         <bean id="xx" class="yyy" autowire="byType">
            简单类型属性赋值
         </bean>

注意:在byType中, 在xml配置文件中声明bean只能有一个符合条件的,多余一个是错误的

6.4 为应用指定多个 Spring 配置文件

包含关系的配置文件:
         spring-total表示主配置文件 : 包含其他的配置文件的,主配置文件一般是不定义对象的。
         语法:<import resource="其他配置文件的路径" />
         关键字:"classpath:" 表示类路径(class文件所在的目录),
         在spring的配置文件中要指定其他文件的位置, 需要使用classpath,告诉spring到哪去加载读取文件。
         例如单独加载各个配置文件:
    <import resource="classpath:ba06/spring-school.xml" />
    <import resource="classpath:ba06/spring-student.xml" />
		使用通配符加载:
       在包含关系的配置文件中,可以通配符(*:表示任意字符)
       注意: 主的配置文件名称不能包含在通配符的范围内(不能叫做spring-total.xml)
       例如:
       <import resource="classpath:ba06/spring-*.xml" />

7. 基于注解开发

7.1创建对象相关注解

@Component: 创建对象的, 等同于<bean>的功能
      属性:value 就是对象的名称,也就是bean的id值,
          value的值是唯一的,创建的对象在整个spring容器中就一个
     位置:在类的上面
     注意:Component不指定对象名称,由spring提供默认名称: 类名的首字母小写
@Component(value = "myStudent")等同于
    <bean id="myStudent" class="com.test.ba01.Student" />
 
spring中和@Component功能一致,创建对象的注解还有:
   1.@Repository(用在持久层类的上面) : 放在dao的实现类上面,
                表示创建dao对象,dao对象是能访问数据库的。
   2.@Service(用在业务层类的上面):放在service的实现类上面,
               创建service对象,service对象是做业务处理,可以有事务等功能的。
   3.@Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,
               控制器对象,能够接受用户提交的参数,显示请求的处理结果。
   以上三个注解的使用语法和@Component一样的。 都能创建对象,但是这三个注解还有额外的功能。
  @Repository,@Service,@Controller是给项目的对象分层的。
  

7.2组件扫描器(component-scan)

声明组件扫描器(component-scan),组件就是java对象
base-package:指定注解(PS:创建对象相关注解)在你的项目中的包名。
component-scan工作方式: spring会扫描遍历base-package指定的包,把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值。

加入了component-scan标签,配置文件的变化:
	1.加入一个新的约束文件spring-context.xsd
	2.给这个新的约束文件起个命名空间的名称
	
指定多个包的三种方式:
    <!--第一种方式:使用多次组件扫描器,指定不同的包-->
    <context:component-scan base-package="com.test.ba01"/>
    <context:component-scan base-package="com.test.ba02"/>

    <!--第二种方式:使用分隔符(;或,)分隔多个包名-->
    <context:component-scan base-package="com.test.ba01;com.test.ba02" />

    <!--第三种方式:指定父包-->
    <context:component-scan base-package="com.test" />

7.3使用注解开发例子

7.3.1声明实体类,并使用创建对象相关注解

例子:实体类Student

//使用Component创建对象为myStudent的Student对象
@Component("myStudent")
public class Student {

    private String name;
    private Integer age;

    public Student() {
        System.out.println("==student无参数构造方法===");
    }

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

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

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

7.3.2声明组件扫描器
<context:component-scan base-package="com.test.ba01" />
7.3.3编写测试类
public class MyTest01 {

    @Test
    public void test01(){
        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //从容器中获取对象
        Student student = (Student) ctx.getBean("myStudent");
        System.out.println("student="+student);
    }
}

7.4其他注解 Value的使用

属性: value 是String类型的,表示简单类型的属性值
位置:
1.在属性定义的上面,无需set方法,推荐使用。
2.在set方法的上面
如果使用外部属性配置文件的值,那就需要在Spring配置文件中添加一个加载属性配置文件。

<context:property-placeholder location="classpath:test.properties" />
7.4.1注解 Value的使用例子:
@Component("myStudent")
public class Student {

    /**
     * @Value: 简单类型的属性赋值
     *   属性: value 是String类型的,表示简单类型的属性值
     *   位置: 1.在属性定义的上面,无需set方法,推荐使用。
     *         2.在set方法的上面
     */
    //@Value("李四" )
    @Value("${myname}") //使用属性配置文件中的数据
    private String name;

    @Value("${myage}")  //使用属性配置文件中的数据
    private Integer age;

    public Student() {
        System.out.println("==student无参数构造方法===");
    }

    public void setName(String name) {
        this.name = name;
    }
    //@Value("30")
    public void setAge(Integer age) {
        System.out.println("setAge:"+age);
        this.age = age;
    }

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

8.基于注解的DI

8.1spring框架提供的注解Autowired

@Autowired: spring框架提供的注解,实现引用类型的赋值。
spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType
@Autowired:默认使用的是byType自动注入。
 位置:
 1)在属性定义的上面,无需set方法, 推荐使用
  2)在set方法的上面
8.1.1Autowired之byName
如果要使用byName方式,需要做的是:
1.在属性上面加入@Autowired
2.在属性上面加入@Qualifier(value="bean的id") :表示使用指定名称的bean完成赋值。
8.1.2Autowired之required
属性:required ,是一个boolean类型的,默认true
     required=true:表示引用类型赋值失败,程序报错,并终止执行。
     required=false:引用类型如果赋值失败, 程序正常执行,引用类型是null
8.1.3jdk中提供的注解Resource
引用类型
@Resource: 来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值
           使用的也是自动注入原理,支持byName, byType .默认是byName
 位置:
  1.在属性定义的上面,无需set方法,推荐使用。
  2.在set方法的上面
  注意:默认是byName: 先使用byName自动注入,如果byName赋值失败,再使用byType
  
@Resource只使用byName方式,需要增加一个属性 name
name的值是bean的id(名称)
例如:
	 //只使用byName,不会使用byType
    @Resource(name = "mySchool")
    private School school;

9. JDK动态代理

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

代码示例:

9.1 业务接口
public interface SomeService {
    void doSome();
}
9.2 业务接口实现类
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome() {
        System.out.println("执行业务方法doSome");
    }
}
9.3 非业务工具类
public class ServiceTools {

    public static void doLog(){
        System.out.println("方法的执行时间:"+ new Date());
    }

    public static void doTrans(){
        //方法的最后,提交事务
        System.out.println("方法执行完毕后,提交事务");
    }
}
9.4 InvocationHandler实现类(重点)
public class MyIncationHandler implements InvocationHandler {

    //目标对象
    private Object target; //SomeServiceImpl类

    public MyIncationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //通过代理对象执行方法时,会调用执行这个invoke()
        Object res = null;
        ServiceTools.doLog(); //在目标方法之前,输出时间
        //执行目标类的方法,通过Method类实现
        res = method.invoke(target, args); //SomeServiceImpl.doSome()
        ServiceTools.doTrans(); //在目标方法执行之后,提交事务
        //目标方法的执行结果
        return res;
    }
}
9.5 测试类
public class MyApp {
    public static void main(String[] args) {
        //使用jdk的Proxy创建代理对象
        //1.创建目标对象
        SomeService target = new SomeServiceImpl();

        //2.创建InvocationHandler对象
        InvocationHandler handler = new MyIncationHandler(target);

        //3.使用Proxy创建代理
        SomeService proxy = (SomeService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),handler);
        //通过代理执行方法,会调用handler中的invoke()
        proxy.doSome();

    }
}

10.Aspectj AOP的实现

AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程
序运行过程。
AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB
的动态代理。
AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 Spring 框架中的一个重要内容。利用 AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

10.1 Aspectj maven依赖的添加

对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向
切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,
而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框
架中。
在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。

aspectj maven依赖的添加
    <!--aspectj依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

10.2 Aspectj 相关知识介绍

10.2.1切面(Aspect)

切面泛指交叉业务逻辑。例如事务处理、日志处理就可以理解为切面。常用的切面
是通知(Advice)。实际就是对主业务逻辑的一种增强。
简单来说:切面为要新添加的功能,要增强的功能

10.2.2 连接点(JoinPoint)

连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

10.2.3 切入点(Pointcut)(重点掌握

切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。
被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不
能被增强的。

AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
execution(modifiers-pattern? ret-type-pattern 
declaring-type-pattern?name-pattern(param-pattern)
 throws-pattern?)
解释:
modifiers-pattern] 访问权限类型
ret-type-pattern 返回值类型
declaring-type-pattern 包名类名
name-pattern(param-pattern) 方法名(参数类型和参数个数)
throws-pattern 抛出异常类型
?表示可选的部分

简单来说切入点表达式子表示:execution(访问权限 方法返回值 方法声明(参数) 异常类型)
其中着重符号标注的方法返回值和方法声明(参数)这些都是必须要有的

其中切入点表达式可以采用一些特殊的符号来代表一些特殊操作,具体如下所示
符号含义
*0至多个任意字符
. .在方法参数中,表示任意多参数。用在包名中,表示当前包及其子包
+用在类名后面,表示当前类和其子类。用在接口中,表示当前接口以及其实现类
10.2.4目标对象(Target)

目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象 。

10.2.5 通知(Advice)

通知表示切面的执行时间,Advice 也叫增强。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。

10.3 AspectJ 常用的五种通知类型

10.3.1Before: 前置通知注解

在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参
数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、
目标对象等。
不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该
参数。

示例
/**
 *  @Aspect : 是aspectj框架中的注解。
 *     作用:表示当前类是切面类。
 *     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *     位置:在类定义的上面
 */
@Aspect
public class MyAspect {
    /**
     * 定义方法,方法是实现切面功能的。
     * 方法的定义要求:
     * 1.公共方法 public
     * 2.方法没有返回值
     * 3.方法名称自定义
     * 4.方法可以有参数,也可以没有参数。
     *   如果有参数,参数不是自定义的,有几个参数类型可以使用。
     */


    /**
     * @Before: 前置通知注解
     *   属性:value ,是切入点表达式,表示切面的功能执行的位置。
     *   位置:在方法的上面
     * 特点:
     *  1.在目标方法之前先执行的
     *  2.不会改变目标方法的执行结果
     *  3.不会影响目标方法的执行。
     */
    /**
     * 指定通知方法中的参数 : JoinPoint
     * JoinPoint:业务方法,要加入切面功能的业务方法
     *    作用是:可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参。
     *    如果你的切面功能中需要用到方法的信息,就加入JoinPoint.
     *    这个JoinPoint参数的值是由框架赋予, 必须是第一个位置的参数
     */
    @Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(JoinPoint jp){
        //获取方法的完整定义
        System.out.println("方法的签名(定义)="+jp.getSignature());
        System.out.println("方法的名称="+jp.getSignature().getName());
        //获取方法的实参
        Object args [] = jp.getArgs();
        for (Object arg:args){
            System.out.println("参数="+arg);
        }
    }
}
10.3.2 AfterReturning:后置通知

在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。

示例

/**
 *  @Aspect : 是aspectj框架中的注解。
 *     作用:表示当前类是切面类。
 *     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *     位置:在类定义的上面
 */
@Aspect
public class MyAspect {
    /**
     * 后置通知定义方法,方法是实现切面功能的。
     * 方法的定义要求:
     * 1.公共方法 public
     * 2.方法没有返回值
     * 3.方法名称自定义
     * 4.方法有参数的,推荐是Object ,参数名自定义
     */

    /**
     * @AfterReturning:后置通知
     *    属性:1.value 切入点表达式
     *         2.returning 自定义的变量,表示目标方法的返回值的。
     *          自定义变量名必须和通知方法的形参名一样。
     *    位置:在方法定义的上面
     * 特点:
     *  1。在目标方法之后执行的。
     *  2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
     *      Object res = doOther();
     *  3. 可以修改这个返回值
     *
     *  后置通知的执行
     *    Object res = doOther();
     *    参数传递: 传值, 传引用
     *    myAfterReturing(res);
     *    System.out.println("res="+res)
     *
     */
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
                    returning = "res")
    public void myAfterReturing(  JoinPoint jp  ,Object res ){
        // Object res:是目标方法执行后的返回值,根据返回值做你的切面的功能处理
        System.out.println("后置通知:方法的定义"+ jp.getSignature());
        System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);

    }
}
10.3.3 Around: 环绕通知

在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并
且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个
proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法
的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

示例
@Aspect
public class MyAspect {
    /**
     * 环绕通知方法的定义格式
     *  1.public
     *  2.必须有一个返回值,推荐使用Object
     *  3.方法名称自定义
     *  4.方法有参数,固定的参数 ProceedingJoinPoint
     */

    /**
     * @Around: 环绕通知
     *    属性:value 切入点表达式
     *    位置:在方法的定义什么
     * 特点:
     *   1.它是功能最强的通知
     *   2.在目标方法的前和后都能增强功能。
     *   3.控制目标方法是否被调用执行
     *   4.修改原来的目标方法的执行结果。 影响最后的调用结果
     *
     *  环绕通知,等同于jdk动态代理的,InvocationHandler接口
     *
     *  参数:  ProceedingJoinPoint 就等同于 Method
     *         作用:执行目标方法的
     *  返回值: 就是目标方法的执行结果,可以被修改。
     *
     *  环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务
     */
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {

        String name = "";
        //获取第一个参数值
        Object args [] = pjp.getArgs();
        if( args!= null && args.length > 1){
              Object arg=  args[0];
              name =(String)arg;
        }

        //实现环绕通知
        Object result = null;
        System.out.println("环绕通知:在目标方法之前,输出时间:"+ new Date());
        //1.目标方法调用
        if( "zhangsan".equals(name)){
            //符合条件,调用目标方法
            result = pjp.proceed(); //method.invoke(); Object result = doFirst();

        }

        System.out.println("环绕通知:在目标方法之后,提交事务");
        //2.在目标方法的前或者后加入功能

        //修改目标方法的执行结果, 影响方法最后的调用结果
        if( result != null){
              result = "Hello AspectJ AOP";
        }

        //返回目标方法的执行结果
        return result;
    }
}

10.3.4AfterThrowing:异常通知

在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。
当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的
名称,表示发生的异常对象。

示例
@Aspect
public class MyAspect {
    /**
     * 异常通知方法的定义格式
     *  1.public
     *  2.没有返回值
     *  3.方法名称自定义
     *  4.方法有个一个Exception, 如果还有是JoinPoint,
     */

    /**
     * @AfterThrowing:异常通知
     *     属性:1. value 切入点表达式
     *          2. throwinng 自定义的变量,表示目标方法抛出的异常对象。
     *             变量名必须和方法的参数名一样
     * 特点:
     *   1. 在目标方法抛出异常时执行的
     *   2. 可以做异常的监控程序, 监控目标方法执行时是不是有异常。
     *      如果有异常,可以发送邮件,短信进行通知
     *
     *  执行就是:
     *   try{
     *       SomeServiceImpl.doSecond(..)
     *   }catch(Exception e){
     *       myAfterThrowing(e);
     *   }
     */
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",
            throwing = "ex")
    public void myAfterThrowing(Exception ex) {
        System.out.println("异常通知:方法发生异常时,执行:"+ex.getMessage());
        //发送邮件,短信,通知开发人员
    }

}
10.3.5After :最终通知

无论目标方法是否抛出异常,该增强均会被执行。

示例
@Aspect
public class MyAspect {
    /**
     * 最终通知方法的定义格式
     *  1.public
     *  2.没有返回值
     *  3.方法名称自定义
     *  4.方法没有参数,  如果还有是JoinPoint,
     */

    /**
     * @After :最终通知
     *    属性: value 切入点表达式
     *    位置: 在方法的上面
     * 特点:
     *  1.总是会执行
     *  2.在目标方法之后执行的
     *
     *  try{
     *      SomeServiceImpl.doThird(..)
     *  }catch(Exception e){
     *
     *  }finally{
     *      myAfter()
     *  }
     *
     */
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public  void  myAfter(){
        System.out.println("执行最终通知,总是会被执行的代码");
        //一般做资源清除工作的。
     }

}
10.3.6 Pointcut 定义切入点

当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。
AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。

示例
@Aspect
public class MyAspect {


    @After(value = "mypt()")
    public  void  myAfter(){
        System.out.println("执行最终通知,总是会被执行的代码");
        //一般做资源清除工作的。
     }

    @Before(value = "mypt()")
    public  void  myBefore(){
        System.out.println("前置通知,在目标方法之前先执行的");
    }

    /**
     * @Pointcut: 定义和管理切入点, 如果你的项目中有多个切入点表达式是重复的,可以复用的。
     *            可以使用@Pointcut
     *    属性:value 切入点表达式
     *    位置:在自定义的方法上面
     * 特点:
     *   当使用@Pointcut定义在一个方法的上面 ,此时这个方法的名称就是切入点表达式的别名。
     *   其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式了
     */
    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))" )
    private void mypt(){
        //无需代码,
    }

10.4 使用AspectJ实现AOP代码完整流程

10.4.1 导入maven依赖
    <!--spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--aspectj依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

10.4.2 定义业务接口与实现类

接口:

public interface SomeService {
    void doSome(String name,Integer age);
}

实现类:

//目标类
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name,Integer age) {
        //给doSome方法增加一个功能,在doSome()执行之前, 输出方法的执行时间
        System.out.println("====目标方法doSome()====");
    }
}
10.4.3 定义切面类
@Aspect
public class MyAspect {
    
    @Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(JoinPoint jp)
        //就是你切面要执行的功能代码
    }
}
10.4.4 声明目标对象切面类对象

在spring配置文件中配置:


    <!--把对象交给spring容器,由spring容器统一创建,管理对象-->
    <!--声明目标对象-->
    <bean id="someService" class="com.test.ba01.SomeServiceImpl" />
    <!--声明切面类对象-->
    <bean id="myAspect" class="com.test.ba01.MyAspect" />
10.4.5 注册 AspectJ 的自动代理

在spring配置文件中配置:

    <!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。
        创建代理对象是在内存中实现的, 修改目标对象的内存中的结构。 创建为代理对象
        所以目标对象就是被修改后的代理对象.
        aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象。
    -->
    <aop:aspectj-autoproxy />

11. Spring 集成 MyBatis

将 MyBatis 与 Spring 进行整合,主要解决的问题就是将 SqlSessionFactory 对象交由 Spring
来管理。所以,该整合,只需要将 SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注
册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合。
实现 Spring 与 MyBatis 的整合常用的方式:扫描的 Mapper 动态代理

11.1MySQL 创建数据库 springdb,新建表 Student

11.2maven 依赖 pom.xml

<dependencies>
    <!--单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--spring核心ioc-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--做spring事务用到的-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</version>
    </dependency>
    <!--mybatis和spring集成的依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.9</version>
    </dependency>
    <!--阿里公司的数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
  </dependencies>

  <build>
    <!--目的是把src/main/java目录中的xml文件包含到输出结果中。输出到classes目录中-->
    <resources>
      <resource>
        <directory>src/main/java</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
    <!--指定jdk的版本-->
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

11.3定义实体类 Student

public class Student {
    //属性名和列名一样。
    private Integer id;
    private String name;
    private String email;
    private Integer age;

    public Student() {
    }

    public Student(Integer id, String name, String email, Integer age) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getAge() {
        return age;
    }

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

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

11.4定义 StudentDao 接口

public interface StudentDao {

    int insertStudent(Student student);
    List<Student> selectStudents();

}


11.5定义映射文件 mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.dao.StudentDao">

    <insert id="insertStudent">
        insert into student values(#{id},#{name},#{email},#{age})
    </insert>

    <select id="selectStudents" resultType="Student">
        select id,name,email,age from student order by id desc
    </select>
</mapper>

11.6定义 Service 接口和实现类

//接口
public interface StudentService {

    int addStudent(Student student);
    List<Student> queryStudents();
}

//实现类
public class StudentServiceImpl implements StudentService {

    //引用类型
    private StudentDao studentDao;

    //使用set注入,赋值
    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    @Override
    public int addStudent(Student student) {
        int nums = studentDao.insertStudent(student);
        return nums;
    }

    @Override
    public List<Student> queryStudents() {
        List<Student> students = studentDao.selectStudents();
        return students;
    }
}

11.7定义 MyBatis 主配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--settings:控制mybatis全局行为-->
    <settings>
        <!--设置mybatis输出日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--设置别名-->
    <typeAliases>
        <!--name:实体类所在的包名
            表示com.test.domain包中的列名就是别名
            你可以使用Student表示com.bjpowenrode.domain.Student
        -->
        <package name="com.test.domain"/>
    </typeAliases>


    <!-- sql mapper(sql映射文件)的位置-->
    <mappers>
        <!--
          name:是包名, 这个包中的所有mapper.xml一次都能加载
        -->
        <package name="com.test.dao"/>
    </mappers>
</configuration>

11.7编写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"
       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">

    <!--
       把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
       spring知道jdbc.properties文件的位置
    -->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!--声明数据源DataSource, 作用是连接数据库的-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--set注入给DruidDataSource提供连接数据库信息 -->
        <!--    使用属性配置文件中的数据,语法 ${key} -->
        <property name="url" value="${jdbc.url}" /><!--setUrl()-->
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.passwd}" />
        <property name="maxActive" value="${jdbc.max}" />
    </bean>

    <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的
        SqlSessionFactory  sqlSessionFactory = new ..
    -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--set注入,把数据库连接池付给了dataSource属性-->
        <property name="dataSource" ref="myDataSource" />
        <!--mybatis主配置文件的位置
           configLocation属性是Resource类型,读取配置文件
           它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
        -->
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)
        MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。

    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--指定包名, 包名是dao接口所在的包名。
            MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行
            一次getMapper()方法,得到每个接口的dao对象。
            创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写
        -->
        <property name="basePackage" value="com.test.dao"/>
    </bean>

    <!--声明service-->
    <bean id="studentService" class="com.test.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao" />
    </bean>

</beans>

11.7编写测试类

    @Test
    public void testServiceInsert(){

        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //获取spring容器中的dao对象
        StudentService service = (StudentService) ctx.getBean("studentService");
        Student student  = new Student();
        student.setId(1015);
        student.setName("李胜利");
        student.setEmail("zhoufeng@qq.com");
        student.setAge(26);
        int nums = service.addStudent(student);
        //spring和mybatis整合在一起使用,事务是自动提交的。 无需执行SqlSession.commit();
        System.out.println("nums="+nums);
    }

12. Spring 事务的实现

待完善

13. Spring 与 Web

13.1使用 Spring 的监听器 ContextLoaderListener

对于 Web 应用来说,ServletContext 对象是唯一的,一个 Web 应用,只有一个ServletContext 对象,该对象是在 Web 应用装载时初始化的。若将 Spring 容器的创建时机,放在 ServletContext 初始化时,就可以保证 Spring 容器的创建只会执行一次,也就保证了Spring 容器在整个应用中的唯一性。
当 Spring 容器创建好后,在整个应用的生命周期过程中,Spring 容器应该是随时可以被访问的。即,Spring 容器应具有全局性。而放入 ServletContext 对象的属性,就具有应用的全局性。所以,将创建好的 Spring 容器,以属性的形式放入到 ServletContext 的空间中,就保证了 Spring 容器的全局性。
上述的这些工作,已经被封装在了如下的 Spring 的 Jar 包的相关 API 中

13.2使用 ContextLoaderListener步骤

13.2.1maven 依赖 pom.xml
    <!--为了使用监听器对象,加入依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
  </dependencies>
13.2.2注册监听器 ContextLoaderListener

若要在 ServletContext 初 始 化 时 创 建 Spring 容 器 , 就 需 要 使 用 监 听 器 接 口ServletContextListener 对 ServletContext 进行监听。在 web.xml 中注册该监听器。
Spring 为该监听器接口定义了一个实现类 ContextLoaderListener,完成了两个很重要的
工作:创建容器对象,并将容器对象放入到了 ServletContext 的空间中。

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
13.2.3指定 Spring 配置文件的位置
ContextLoaderListener 在对 Spring 容器进行创建时,需要加载 Spring 配置文件。其默认
的 Spring 配置文件位置与名称为:WEB-INF/applicationContext.xml。但,一般会将该配置文
件放置于项目的 classpath 下,即 src 下,所以需要在 web.xml 中对 Spring 配置文件的位置及
名称进行指定。
    <!--注册监听器ContextLoaderListener
        监听器被创建对象后,会读取/WEB-INF/spring.xml
        为什么要读取文件:因为在监听器中要创建ApplicationContext对象,需要加载配置文件。
        /WEB-INF/applicationContext.xml就是监听器默认读取的spring配置文件路径
        可以修改默认的文件位置,使用context-param重新指定文件的位置
        配置监听器:目的是创建容器对象,创建了容器对象, 就能把spring.xml配置文件中的所有对象都创建好。
        用户发起请求就可以直接使用对象了。
    -->
    <context-param>
        <!-- contextConfigLocation:表示配置文件的路径  -->
        <param-name>contextConfigLocation</param-name>
        <!--自定义配置文件的路径-->
        <param-value>classpath:spring.xml</param-value>
    </context-param>
13.2.4获取 Spring 容器对象
在 Servlet 中获取容器对象的常用方式有两种:
  1. 直接从 ServletContext 中获取(不推荐使用)

从对监听器 ContextLoaderListener 的源码分析可知,容器对象在 ServletContext 的中存
放的 key 为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。所以,可
以直接通过 ServletContext 的 getAttribute()方法,按照指定的 key 将容器对象获取到。

        //获取ServletContext中的容器对象,创建好的容器对象,拿来就用
        String key =  WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
        Object attr  = getServletContext().getAttribute(key);
        if( attr != null){
            ctx = (WebApplicationContext)attr;
        }
  1. 通过 WebApplicationContextUtils 获取

工具类 WebApplicationContextUtils 有一个方法专门用于从 ServletContext 中获取 Spring
容器对象:getRequiredWebApplicationContext(ServletContext sc)

 ServletContext sc = getServletContext();
        ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值