Spring开发

171 阅读20分钟

一.Spring

一.Spring介绍

Spring框架是一个开放源码的J2EE应用程序框架,是针对bean生命周期进行管理的轻量级容器

知识点:

1.J2EE JAVA企业级开发

2.bean:由Spring容器进行管理的对象称之为bean

3.轻量级:快捷,简单,方便

4.容器:Spring在内存中开辟出来一大块空间,来管理对象

二.Spring-IOC介绍

Ioc全称Inversion of Control,即“控制反转”,这是一种设计思想。对象创建的权利由Spring框架完成.由容器管理对象的生命周期.

image.png

如果不使用Spring进行管理的话,那么普通的对象与USer类之间耦合性高

入门案例

准备一个Dog类:

package com.example.demo4;

public class Dog implements Put{
    public void eat(){
        System.out.println("小狗在吃饭...");
    }

    @Override
    public void hello() {
        System.out.println("小狗高兴的摇尾巴...");
    }
}

还有一个接口

package com.example.demo4;

public interface Put {
    public void hello();
}

配置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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1. IOC的机制管理Dog对象
        id: 对象的唯一标识符. 不能重复 见名知意 首字母小写
        class: 类路径
    -->
    <bean id="dog" class="com.jt.demo3.Dog"></bean>

</beans>

编辑测试类

package com.example.demo4;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class User {
    public static void main(String[] args) {
        //1.创建容器对象,加载配置文件
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        //2.从容器中获取对象
        Dog dog = (Dog)context.getBean(Dog.class);
        //3.调用对象的方法
        dog.eat();
        dog.hello();
    }
}

IOC对象创建-反射机制

package com.jt.demo3;

public class NewDog {

    /**
     *  1.该方法是通过反射的机制实例化对象 !!!框架中的重点!!!
     *  2.反射机制:
     *      java中创建对象的常用的一种方式,指定类型的路径,
     *      之后通过关键方法.newInstance()实例化对象
     *      类比:  xxx.newInstance() ~~ new Dog();
     *  3. 精髓: 为什么用反射/而不是new?
     *      3.1 因为第三方通过反射实例化对象 可以实现松耦合!!
     *      3.2 一般多用于框架,因为框架不清楚用户到底需要啥,只有运行期
     *          才清楚用户到底需要什么对象. 扩展性更好!!!
     *  4. 异常说明:
     *         1.运行时异常   运行期间出错了
     *         2.编译异常/检查异常  编译时提示报错
     */

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Dog dog = (Dog) Class.forName("com.jt.demo3.Dog")
                .newInstance();
        dog.hello();
    }
}

三.IOC的实现原理

容器的数据结构:K-V Map<K,V> Key相当于bean中的id,Value相当于实例化的对象

程序的执行过程:

  • 指定配置文件的名称
  • 当Spring加载配置文件时,当按照顺序执行bean标签,开始创建对象
  • Spring通过bean标签中class属性获取类型的路径,之后通过反射机制,实例化对象必须有无参构造
  • bean标签中的id当作Map中的key键,将实例化的对象保存到Map中当Value,至此Spring容器启动成功...
  • 当用户需要获取对象时,可以通过Key或类型来获取对象

四.Spring注解开发

4.1关于注解开发说明

如果是2010年,那么还需要对xml配置文件有所了解,但是引入springboot之后,基本不需要使用xml配置文件了,一般都使用注解开发.

4.2注解开发

效果:类似于配置Spring.xml文件

注解作用:标记/标识

注解1: @Configuration 标识当前类是配置类
注解2: @Bean
注解3: @ComponentScan(“com.jt.demo4”) //包扫描注解: 让spring注解生效!!!

配置类:

package com.example.demo4;

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

@Configuration  //告诉Spring当前是一个配置类
@ComponentScan("com.example.demo4") //包扫描注解:让Spring生效
public class SpringConfig {
    @Bean //告诉Spring容器,当前方法的名称,为Key,方法的返回值为Vaule
    //通常情况下:Spring通过反射来实例化,也可以由用户自己new的方式创建
    public Dog dog(){
        return new Dog();
    }
    @Bean
    public Cat cat(){
        return new Cat();
    }
}

测试类:

package com.example.demo4;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class User {
    public static void main(String[] args) {
        //1.创建容器对象,加载配置类
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        //2.从容器中获取对象
        Dog dog = context.getBean(Dog.class);
        //3.调用对象的方法
        dog.eat();
        dog.hello();
    }
}

4.3Spring中单例和多例问题

概念: 单例模式:Spring中只保存一份对象,为了节约内存空间 多例模式:Spring容器负责创建多次对象,内存中对象多份,一般用于数据库连接,资源连接等功能

单例多例测试

注解:@Scope(“singleton”)/@Scope(“prototype”) 默认:单例模式

image.png

4.4Spring中的懒加载

注解:@Lazy
懒汉式: 用户使用时创建.
饿汉式: 不管需不需要都要创建.
懒加载: 默认条件下Spring容器启动时都会实例化对象. 如果用户使用时创建对象,则使用懒加载机制!!!

规则:

  1. spring容器启动,对象创建 不是懒加载
  2. 当用户使用时创建对象. 是懒加载.

注意事项:
1.如果当前对象是多例模式, 则都是懒加载规则
2.懒加载的控制,只对单例对象有效!!!

package com.example.demo4;

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

@Configuration  //告诉Spring当前是一个配置类
@ComponentScan("com.example.demo4") //包扫描注解:让Spring生效
public class SpringConfig {
    @Bean //告诉Spring容器,当前方法的名称,为Key,方法的返回值为Vaule
    //通常情况下:Spring通过反射来实例化,也可以由用户自己new的方式创建
    public Dog dog(){
        return new Dog();
    }
    @Bean
    @Lazy//开启懒汉式
    public Cat cat(){
        return new Cat();
    }
}

4.5 Spring的生命周期

4.5.1生命周期的说明

  1. 创建对象
  2. 初始化数据
  3. 调用方法,完成功能
  4. 对象销毁

核心:生命周期的方法都是自动调用!!!

4.5.2

package com.example.demo4.POJO;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class Dog implements Pue{
    public Dog(){
        System.out.println("----构造方法...");
    }
    //初始化操作
    @PostConstruct
    public void init(){
        System.out.println("为属性赋值");
    }
    @Override
    public void hello() {
        System.out.println("小狗子再叫...");
    }
    //销毁方法
    @PreDestroy
    public void destroy(){
        System.out.println("调用销毁方法,释放资源...");
    }

}

4.5.3注解说明

  1. @PostConstruct 标识初始化方法

  2. @PreDestroy 标识销毁方法.

    知识点: 容器的接口,没有提供关闭容器的操作. 如果需要关闭容器则使用实现类中的方法
    

4.5.4 测试类

package com.example.demo4;

import com.example.demo4.Config.SpringConfig;
import com.example.demo4.POJO.Dog;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class UserTest {
    @Test
    public void Test1(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        Dog bean = context.getBean(Dog.class);
        bean.hello();
        context.close();
    }
}

4.6 Spring管理对象–@Component

4.6.1 @Component注解说明

Spring自动为该注解标识的类通过反射实例化对象,交给Spring容器管理.
Key: 类名首字母小写 user
value: 反射的对象
类似于@Bean注解
@Component/@Bean区别:
1.@Component spring容器通过反射自动创建对象
@Bean 是用户自己手动创建对象
2.@Component 标识类的
@Bean 标识配置类中的方法
3.@Component 对象的Id是类名首字母小写
@Bean 对象的Id是方法名

4.6.2 @ComponentScan注解说明

为了让Spring的注解生效. 需要单独使用包扫描注解

二.DI(Dependency Injection)

2.1 Spring中依赖注入

依赖注入就是将Spring容器中管理的对象,赋值给对象的属性

2.2 xml配置文件写法

Person类

package com.jt.demo7_di;

public class Person {
    //定义属性
    private Integer id;
    private String name;

    //为属性赋值,必须有set方法!!!!!!!
    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 void sayHi(){
        System.out.println("你好:"+id+":"+name);
    }
}

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">

    <!--xml配置文件 为属性赋值-->
    <bean id="person" class="com.jt.demo7_di.Person">
        <property name="id" value="200"/>
        <property name="name" value="元宵节"/>
    </bean>
</beans>

测试类

package com.jt.demo7_di;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestPerson {

    public static void main(String[] args) {
        String resource = "spring_di.xml";
        ApplicationContext context =
                new ClassPathXmlApplicationContext(resource);
        Person person = context.getBean(Person.class);
        person.sayHi();
    }
}

2.3 Spring 为对象赋值

2.3.1 构建Car对象

package com.jt.demo7_di;

public class Car {

    public void go(){
        System.out.println("我可以带你去任何地方!!!");
    }
}

2.3.2 编辑xml配置文件

 <!--2.xml配置文件 为属性赋值-->
    <bean id="person" class="com.jt.demo7_di.Person">
        <property name="id" value="200"/>
        <property name="name" value="元宵节"/>
        <!--3.如果将对象赋值给属性,使用ref属性
            ref:引用的id-->
        <property name="car" ref="car"/>
    </bean>

2.3.3 构建Person类

package com.jt.demo7_di;

public class Person {
    //定义属性
    private Integer id;   //注入简单的数据int String
    private String name;
    //private Car car = new Car(); //耦合性高
    private Car car;    //spring容器为属性赋值.

    //为属性赋值,必须有set方法!!!!!!!
    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 Car getCar() {
        return car;
    }
    public void setCar(Car car) {
        this.car = car;
    }

    public void sayHi(){
        System.out.println("你好:"+id+":"+name);
    }

    public void toGo(){
        car.go();
    }
}

2.3.4 编辑测试类

package com.jt.demo7_di;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestPerson {

    public static void main(String[] args) {
        String resource = "spring_di.xml";
        ApplicationContext context =
                new ClassPathXmlApplicationContext(resource);
        Person person = context.getBean(Person.class);
        person.sayHi();
        //调用car对象的方法
        person.toGo();
    }
}
	

2.3.4 IOC/DI意义

理解: Spring中的IOC(控制反转)/DI(依赖注入) 两种方式相互配合,实现了代码的松耦合!!!.

2.4 注入方式说明

2.4.1 Set方法注入

说明: 通过set方法为属性赋值.

image.png

2.4.2 构造方法注入

说明: 通过构造方法,为属性赋值

 <!--1.让spring容器管理car 参数的个数 决定调用的构造方法 -->
    <bean id="car" class="com.jt.demo7_di.Car">
        <constructor-arg name="name" value="奔驰大G"/>
        <constructor-arg name="color" value="绿色"/>
    </bean>

image.png 总结: Set和构造都是为属性赋值的常规用法,没有优劣之分.根据习惯调用即可. Set注入方式以后使用更多.

2.5 Spring属性赋值-注解用法@Autowired

package com.jt.demo8_anno;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 1.该类主要测试 注解的注入方式.
 * 2.练习对象的嵌套关系
 * 3.注解的写法与xml写法的原理相同,形式不同
 */
@Component //key:user value:反射后的对象
public class User { //用户
    /**
     * 注解说明:
     *  1.@Autowired 自动注入
     *  规则:
     *      1.默认条件下使用set方式注入.按照类型匹配.
     *      2.set方式注入也可以按照name名称进行匹配
     *      3.只要使用注解,默认自动生成SetXX方法
     *  实现原理:
     *      1.类型匹配: 如果是接口,则自动匹配其实现类对象
     *      2.name名称匹配: 根据spring中的key进行注入.
     */
    @Autowired
    private Pet pet;

    public void hello(){
        pet.hello();
    }
}

2.6 多实现类情况处理

2.6.1 异常情况说明

Spring中的DI注入,一般情况下 采用按照类型的方式匹配.但是如果遇到多实现类的情况,则会出现如下的报错.
报错信息: 一个接口匹配了多个实现类,程序异常!!!

image.png

多实现解决办法

package com.jt.demo8_anno;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * 1.该类主要测试 注解的注入方式.
 * 2.练习对象的嵌套关系
 * 3.注解的写法与xml写法的原理相同,形式不同
 */
@Component //key:user value:反射后的对象
public class User { //用户
    /**
     * 注解说明:
     *  1.@Autowired 自动注入
     *  规则:
     *      1.默认条件下使用set方式注入.按照类型匹配.
     *      2.set方式注入 可以按照name名称进行匹配
     *      3.只要使用注解,默认自动生成SetXX方法
     *  实现原理:
     *      1.类型匹配: 如果是接口,则自动匹配其实现类对象
     *      2.name名称匹配: 根据spring中的key进行注入.
     *  注解说明:
     *      @Qualifier必须和@Autowired联用.
     *      并且需要指定value的名称,就是spring中的key
     *
     *  准则: 一般条件下,Spring都是单实现!!!!
     */
    @Autowired
    @Qualifier(value = "pig")
    private Pet pet;

    public void hello(){
        pet.hello();
    }
}

2.7 Spring属性赋值-@Value注解

2.7.1 编辑properties配置文件

#1.数据结构 key=value
#2.其中的数据默认都是String类型,数值可以切换为数值类型
#3.程序读取时,默认的字符集ISO-8859-1 不支持中文,
#  如果支持中文,使用utf-8
dept.id=101
dept.name=行政部

2.7.2 为属性赋值操作

package com.jt.demo9;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
//当spring容器启动时,加载指定的配置文件,将数据保存到容器中!!!
@PropertySource(value = "classpath:/dept.properties",encoding = "UTF-8")
public class Dept {

    //取值方式: 从spring容器中获取数据 ${pro中的key}
    @Value("${dept.id}")
    private Integer id;
    @Value("${dept.name}")
    private String name;

    //@Value注解可以为简单属性赋值.
    //@Value("财务部")
    //private String name;

    public void hello(){
        System.out.println("获取数据:"+id+":"+name);
    }
}

三. 项目开发思想介绍

3.1 MVC设计思想

历史问题: 如果将大量的代码都写到一个方法中,后期维护不易.
MVC说明:\

  1. M Model 数据层 数据处理/数据的持久化\
  2. V View 视图层 前端页面相关的内容 (看到的内容)\
  3. C Control 控制层 控制数据的流转的过程

image.png

3.2 层级代码结构

前提: 在项目中代码需要松耦合. 根据MVC指导思想.后端代码进行了优化,演变为现在的层级代码结构. 层级结构:

  1. Controller层 控制层 主要实现与前端页面的交互!!!
  2. Service层 业务层 主要实现数据的处理
  3. Dao层/Mapper层 持久层 与数据库进行交互.实现数据CURD操作.

image.png

3.3 Spring层级代码结构

3.3.1 层级的划分

1.Controller层 @Controller
2.Service层 @Service
3.Dao/Mapper层 @Repository
4.POJO 根据数据表定义的实体对象

3.3.2层级代码流程图

image.png

3.3.3层级代码结构加载流程

Map<UserMapperImpl,对象>
<UserServiceImpl,对象>
<UserController,对象>

  1. 用户执行main()方法,程序开始调用
  2. Spring容器开始执行 ApplicationContext
  3. Spring容器按照指定的规则 加载配置类. 注意包路径
  4. 根据配置类的注解,开始完成包扫描 @ComponentScan(“com.jt.demo10”) 针对@Component注解有效
  5. 先扫描UserController,但是现在内存中没有依赖项UserService.所以当先线程先挂起(跳过!!!).继续执行后续动作.
  6. 当程序解析到Mapper层时 ,可以直接实例化对象交给Spring容器保存.
  7. 当程序解析到Service层时, 根据依赖注入的规则,注入Mapper的实现类.之后Service对象实例化成功
  8. 当程序加载完成之后,再次回到之前的Controller进行对象的注入. 注入UserService.之后UserController实例化成功. 至此Spring容器启动成

image.png

3.3.4框架参数传递流程图

image.png

四.AOP

4.1 AOP介绍

在软件业,AOP为Aspect Oriented Programming的缩写,意为: 面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术 AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性,同时提高了开发的效率。

AOP主要作用: 在不修改原有代码的条件下对方法进行扩展

难点词语:

  1. 切面
  2. 预编译
  3. 动态代理

4.2 IOC-DI/AOP之间的区别

IOC-DI 利用第三方容器将对象统一管理.利用依赖注入为属性赋值. 解决了对象与对象之间的耦合性的问题. 升华: SpringMVC框架(对象)/Mybatis框架(对象)类似的框架都可以交给Spring容器管理.Spring可以以一种统一的方式管理其它第三方的框架,使得调用浑然一体.耦合性低. 解决了框架和框架之间耦合性问题.

AOP 名称为面向切面编程, 使得业务逻辑各部分之间的耦合度降低.

4.2.1动态代理机制

总结: 使用代理机制,可以有效降低代码的耦合性,将公共的代码/重复的代码,写到代理机制中,通过代理机制调用目标方法.使得真实的业务被调用. 通过代理机制,可以有效的降低业务的耦合性!!!.

image.png

4.2.2 动态代理分类

代理特点: 使用代理对象和使用目标对象看起来 一模一样

4.2.2.1 动态代理-JDK动态代理

  1. jdk动态代理是JDK源码提供的,无需导入额外的jar包.
  2. JDK代理对象要求实现和被代理者相同的接口 必须有接口
  3. JDK代理创建速度快,运行时稍慢

image.png

package com.example.demo1.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class TimeProxy {
    public static Object getProxy(Object o){
        //通过类型获取加载器
        ClassLoader classLoader = o.getClass().getClassLoader();
        //获取接口数组
        Class<?>[] interfaces = o.getClass().getInterfaces();
        //当代理对象执行业务操作时,通过InvocationHandler进行业务的扩展
        //将代理对象扩展的内容写入到处理器中
        return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("开启事务...");
                //调用 让目标方法执行 o:目标对象!!!! 其它都是固定写法!!!
                Object invoke = method.invoke(o, args);
                System.out.println("事务提交...");
                return invoke;
            }
        });
    }
}

4.2.2.2 动态代理-CGLIB动态代理

  1. cglib动态代理需要导入额外的包,才能使用.
  2. cglib 要求被代理者有无接口都可以, 但是cglib代理对象是目标对象的子类. (继承)
  3. cglib创建代理对象时慢,但是运行期速度快
package com.example.demo1.proxy;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGproxy {
    public static Object getCGproxy(Object o){
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(o.getClass());
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                long l = System.currentTimeMillis();
                System.out.println("事务开启...");
                Object o1 = methodProxy.invokeSuper(o, objects);
                System.out.println("事务提交...");
                long l1 = System.currentTimeMillis();
                System.out.println(l1-l);
                return o1;
            }
        });
        return enhancer.create();
    }
}

4.3 Spring中的AOP

2.1 AOP介绍

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

总结: Spring中的AOP 利用代理对象在不修改源代码的条件下,对方法进行扩展.

2.2 AOP中专业术语(难点)

1).连接点: 用户可以被扩展的方法
2).切入点: 用户实际扩展的方法
3).通知: 扩展方法的具体实现
4).切面: 将通知应用到切入点的过程

4.4 AOP的入门案例

4.4.1导入jar包

<!--引入AOPjar包文件-->
        <dependency>
  	          <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

4.4.2 切入点表达式

2.切入点表达式
2.1 bean(“对象的Id”) 每次拦截,只拦截1个
2.2 within(“包名.类名”)
2.3 execution(返回值类型 包名.类名.方法名(参数列表))
2.4 @annotation(注解的路径)

4.4.3 定义切面类

package com.jt.demo2.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

@Component  //将当前类交给Spring容器管理
@Aspect     //我是一个切面类
public class SpringAOP {

    /**
     *   切面 = 切入点表达式 + 通知方法
     *   1.切入点:
     *      理解: 可以理解为就是一个if判断
     *      判断条件: 切入点表达式
     *      规则:
     *          如果满足表达式 则判断为true,则执行通知方法
     *          如果不满足表达式 则判断为false 则不执行通知方法
     *
     *   2.切入点表达式
     *   2.1 bean("对象的Id")
     *   2.2 within("包名.类名")
     *   2.3 execution(返回值类型 包名.类名.方法名(参数列表))
     *   2.4 @annotation(注解的路径)
     */
    @Pointcut("bean(userServiceImpl)")
    public void pointcut(){

    }

    /**
     * 定义通知方法:
     *   1.前置通知  在目标方法执行之前执行.
     *   2.后置通知  在目标方法执行之后执行.
     *   3.异常通知  在目标方法执行之后抛出异常时执行.
     *   4.最终通知  都要执行的通知
     *   5.环绕通知  在目标方法执行前后都要执行的通知
     */
    @Before("pointcut()")
    public void before(){
        System.out.println("你好,我是前置通知");
    }
}

4.4.4 让AOP生效

说明: 编辑配置类,添加@EnableAspectJAutoProxy,让AOP机制有效

image.png

5.切入点表达式

作用: 配置AOP为谁创建代理对象

5.1 bean标签

语法: bean(“对象的ID”)  只能匹配固定的类/对象 一个

	@Pointcut("bean(userServiceImpl)")
    public void pointCut(){

    }

5.2 within表达式

语法: @Pointcut(“within(包名.类名)”)
功能: 可以匹配多个类

 	@Pointcut("within(com.jt.demo2.service.UserServiceImpl)")
    public void pointCut(){

    }

 	@Pointcut("within(com.jt.demo2.service.*)") //多个
    public void pointCut(){

    }

5.3 execution表达式

语法: @Pointcut(“execution(返回值类型 包名.类名.方法名(参数列表))”)

	@Pointcut("execution(void com.jt.demo2.service.UserServiceImpl.addUser())")
    public void pointCut(){

    }
	//要求: 拦截service.包下的所有的类,任意方法, 任意参数  返回值类型任意!!
	@Pointcut("execution(* com.jt.demo2.service..*.*(..))")
    public void pointCut(){

    }

5.4 @annotation自定义注解

package com.jt.demo2.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD) //注解对方法有效
@Retention(RetentionPolicy.RUNTIME) //运行期有效
public @interface TX {
    
}

image.png 使用自定义注解:

@Pointcut("@annotation(com.jt.demo2.anno.TX)")
  public void pointCut(){
 }

6. 通知方法

作用: 当用户满足了切入点表达式,则才会执行扩展方法.
注意事项: 通知必须与切入点表达式绑定!!!

6.1 通知类型-前置通知

前置通知: @Before 在目标方法运行前执行.

//通知方法: 对原有方法的扩展
    @Before("pointCut()")
    public void before(){
        System.out.println("AOP入门案例");
    }

6.2 通知类型-后置通知

  1. 后置通知 :在目标方法执行之后执行
	@AfterReturning("pointCut()")
    public void afterReturning(){
        System.out.println("AOP后置通知");
    }
  1. 动态获取返回值
 /**
     *  记录目标方法的返回值结果
     *  returning: 后置通知获取返回值的属性
     */
    @AfterReturning(value = "pointCut()",returning = "result")
    public void afterReturning(Object result){
        System.out.println("方法的返回值:"+result);
    }

6.3 通知类型-异常通知

1.异常通知: 目标方法执行时,抛出异常时,异常通知执行.

	@AfterThrowing("pointCut()")
    public void afterThrowing(){
        System.out.println("抛出异常!!!");
    }

2.获取异常信息

 /**
     * 说明: 如果程序执行抛出异常,则可以由异常通知进行记录
     *      throwing:抛出异常的属性
     */
    @AfterThrowing(value = "pointCut()",throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint,Exception exception){
        //exception.printStackTrace();
        System.out.println("异常信息"+exception.getMessage());
    }

6.4 通知类型-最终通知

最终通知: 不管目标方法执行是否正确,都要执行. 一般不用

 @After("pointCut()")
    public void after(){
        System.out.println("我是最终通知!!");
    }

6.5 通知类型-环绕通知

说明:

  1. 前四大通知类型不能控制目标方法的运行,所以在使用时一般记录程序的运行状态.\
  2. 在目标方法执行前后都要运行, 只有环绕通知才可以控制目标方法是否运行. 使用最多的通知方法.
 /**
     * 注意事项: 环绕通知中必须添加参数,并且必须位于第一位
     * 用法:
     *  Proceed with the next advice or target method invocation
     *  1.如果有下一个通知,则执行下一个通知,
     *  2.如果没有下一个通知则执行目标方法
     * @return
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知开始");
        Object result = joinPoint.proceed();
        System.out.println("环绕通知结束");
        return result;
    }

7.关于连接点joinPoint的用法

说明: 当用户调用方法时,如果该方法被切入点表达式匹配.则Spring会为其对象创建代理 则将这个方法称之为连接点

7.1 连接点的作用

连接点的作用: 通过JoinPoint对象可以获取当前方法的所有的参数,可以供用户进行记录.

 //通知方法: 对原有方法的扩展
    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        //1.获取目标对象的类型
        Class targetClass = joinPoint.getTarget().getClass();
        //2.获取目标对象名称
        String targetName = joinPoint.getSignature().getDeclaringTypeName();
        //3.获取目标方法的名称
        String methodName = joinPoint.getSignature().getName();
        //4.获取参数数据
        Object[] args = joinPoint.getArgs();
        System.out.println("目标对象类型:"+targetClass);
        System.out.println("目标对象名称:"+targetName);
        System.out.println("目标方法名称:"+methodName);
        System.out.println("参数名称:"+ Arrays.toString(args));
    }

7.2 关于JoinPoint和ProceedingJoinPoint区别

说明: 1.ProceedingJoinPoint只适用于环绕通知,因为只有环绕通知,才能控制目标方法的运行.
2.JoinPoint 适用于其它的四大通知类型,可以用来记录运行的数据.
3. ProceedingJoinPoint 中有特殊的方法proceed();
4. 如果使用"JoinPoint" 则必须位于参数的第一位

8.关于切面顺序研究

说明: spring中的AOP如果多个环绕通知参与,则采用嵌套的结构进行调用. 执行的顺序与代码的顺序有关.

image.png

image.png

8.1控制AOP执行顺序: Order注解

image.png

9.Spring中默认代理策略

9.1 知识回顾:

  1. 动态代理方式
    1.JDK动态代理 被代理者必须有接口
    2.CGLib代理 不管有无接口都可以为其创建代理对象. 代理对象是被代理者的子类(继承)

9.2 Spring创建代理的规则

如果目标对象有接口, 则默认采用JDK动态代理模式,
如果目标对象没有接口, 则默认采用CGLIB代理方式.

image.png

9.3 代理模式的修改

说明: 如果需要改变默认的模式,则只能强制使用cglib代理模式.

image.png

总结

1.1 IOC-DI

1. IOC: 控制反转 对象的生命周期由Spring容器管理.
2. xml配置文件的写法 bean标签 id/class xml容器的实现类
3. 注解写法 @bean 配置类
4. 单例多例问题 默认单例对象 @scope(‘prototype’) 多例对象
5. 懒加载 @Lazy 用户使用时创建
6. 生命周期方法 对象创建(无参构造)/对象初始化(@PostXXXX)/业务调用/对象销毁(@PreXXXXX)
7. DI: 依赖注入 为对象的属性赋值!!!
8. 简单属性赋值: @Value注解
9. 引用赋值: @Autowired 1.默认按照类型匹配 2.按照名称匹配
10. 注入的方式: 1.Set注入 2.构造方法注入 3.工厂模式注入

1.2 AOP

  1. 什么是AOP 面向切面编程 在不修改源码的条件下,对方法进行扩展!!! 解决了业务的耦合性.
  2. 底层核心机制: 动态代理机制 JDK/CGLIB 动态代理.
  3. 关键词:
    3.1 切入点表达式: 如果用户调用满足切入点表达式 则创建代理对象,执行AOP的通知
    3.2 通知: 对业务的扩展 5个通知 环绕通知最为强大.
    3.3.连接点: 用户调用的方法满足切入点表达式 则该方法称作连接点.

1.3 Spring框架优势

1.Spring框架的设计让程序从层级到业务实现了松耦合
2.Spring基于这样的设计思想,可以整合其它的第三方框架, 使得程序可以以一种统一的方式进行调用.