Spring框架

Spring框架讲解

文章目录

一. Spring IOC xml开发

1.1 Spring介绍

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

小结:Spring由Rod Johnson研发,是针对bean的生命周期进行管理的轻量级容器,其中核心技术是IOC和AOP

1.2 传统开发存在的问题

1.2.1 编辑Dog类
public class Dog implements Pet{
    @Override
    public void hello(){
        System.out.println("小狗,汪汪汪");
    }
}
1.2.2 编辑Cat类
public class Cat implements Pet{
    @Override
    public void hello(){
        System.out.println("小猫喵喵喵");
    }
}
1.2.3 编辑测试类
public class User {
    /*
    * 讨论:这样代码有什么问题
    * 问题描述:
    *   1.宠物对象与当前用户紧紧的绑定在一起,耦合性高
    *   2.对象和方法也紧紧的绑定在一起,耦合性高
    * */
//    private static Dog dog =new Dog();
//    private static Cat cat =new Cat();

    public static void main(String[] args) {
        dog.hello();
//        cat.hello();
    }
}
1.2.4 问题暴露
  1. 宠物对象与当前的用户紧紧的绑定在一起. 耦合性高
  2. 对象和方法也紧紧的绑定在一起,耦合性高

1.3 面向接口开发

1.3.1 业务说明
  1. Dog类中和Cat类中都有hello的方法. 可以将hello的方法进行抽取,代码逐步演化为面向接口的方式.
1.3.2 定义Pet接口
public interface Pet {
    void hello();
}
1.3.3 编辑测试类

面向接口开发: 优势是解决了属性与方法的耦合性问题

public class User {
    /*面向接口开发: 优势是解决了属性与方法的耦合性问题*/
//    private static Pet pet =new Dog();
    private static Pet pet=new Cat();

    public static void main(String[] args) {
        pet.hello();
    }
}
1.3.4 问题暴露

当前new Cat对象与当前的类紧紧的绑定在一起. 如果后期需要修改Cat 则每个类中都需要修改. 耦合性高.

1.4 Spring-IOC(Inversion of Control)

1.4.1 IOC介绍

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

Spring框架

小结:

  1. 原来的对象的创建都是由用户自己手动创建,这样的方式耦合性 肯定高. 如果类发生了变化,则代码都得修改.
  2. 现在所有的对象都交给Spring容器管理. 用户无需关心对象是如何实例化. 容器负责对象的注入即可. 以后几乎不用修改任何代码.降低了代码的耦合性
1.4.2 创建User类
public class User {

    public void say(){
        System.out.println("我是User对象,被Spring容器管理");
    }
}
1.4.3 编辑spring.xml配置文件

说明: 由于需要使用Spring的框架,所以需要准备spring的配置文件.

在resource中创建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">
    <!--
        该配置文件的作用是管理对象
        术语:bean 被spring容器管理的对象叫bean
        属性说明:
            id:是spring容器对象的唯一标识,不能重复,类似于css的id选择器
            class:对象的全路径
    -->
        <bean id="user" class="com.jt.demo.User"> </bean>
<!--        <bean id="user" class="com.jt.demo.User" />-->
   
   <!-- 在 Spring中提供了别名标签<alias>可以为配置的<bean>起一个别名,要注意的是这仅仅是对指定的<bean>起的一个额外的名字,并不会额外的创建对象存入map。 <alias name="要起别名的bean的id"  alias="要指定的别名"/>  -->
    <alias name="userController2" alias="uc2"></alias>

</beans>

补充:

  1. 默认情况下,多次获取同一个id的bean,得到的将是同一个对象。

  2. 不可以配置多个id相同的bean

  3. 可以配置多个id不同但class相同的bean

1.4.4 测试
/**
 * 获取对象的方式
 *  通过id获取bean
 *      如果找不到,抛异常NoSuchBeanDefinitionException
 *      如果找到唯一的,返回对象
 *      因为id不重复,不可能找到多个
 *  通过class获取bean
 *      如果找不到,抛出异常NoSuchBeanDefinitionException
 *      如果找到唯一,返回对象
 *      如果找到多个,抛出异常NoUniqueBeanDefinitionException
 */
public class TestSpring {
    @Test
    public void testDemo1(){
        String resource="spring.xml";
        /*创建spring容器对象,并且加载指定的配置为文件*/
        ApplicationContext context=new ClassPathXmlApplicationContext(resource);
        /*从容器中获取对象  根据ID获取对象*/
        User user1 =(User) context.getBean("user");
        /*根据类型获取对象 */
        User user2 =context.getBean(User.class);
        user1.say();
        /*容器中只创建一个对象,user1和user2拿到的都是一个地址值*/
        System.out.println(user1==user2);//true
        System.out.println(Thread.currentThread().getName());//main线程
    }
}

SpringIOC在通过class获取bean时,如果找不到该类型的bean还会去检查是否存在该类型的子孙类型的bean,如果有则返回,如果找不到或找到多个则抛出异常。这符合java面向对象思想中的多态的特性。

public class UserController2 {

}

public class UserController extends UserController2{
 public UserController(){
        System.out.println("创建对象:调用UserController构造函数创建对象");
    }
}
@Test
public void TestDemo3(){
    ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
    UserController userController =(UserController) context.getBean(UserController2.class);
}

1.5 关于Spring容器

解释:
Spring容器的数据结构是Map集合. Map<key,value>
key=“user” value=“通过反射机制实例化的对象”

	<bean id="user" class="com.jt.demo.User"></bean>

1.6 了解反射源码

​ 说明: 反射机制在框架中 一般使用比较多,给定类型的路径就可以获取其中的对象.==但是要求必须有无参构造. 否则程序运行必报异常.==因为其默认调用的是*.newInstance()方式创建对象.

  @Test
    public void testDemo2() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        User user =(User) Class.forName("com.jt.demo.User").newInstance();
        user.say();
    }
1.6.1 IOC实现原理

Spring框架

1.7 通过指定构造器创建对象

​ 配置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 id="user" class="com.jt.demo.User"> 
    <!--
name:参数名
type:参数类型
index:参数位置
value:参数值(直接赋值)
ref:参数值(引用其他bean)
-->
<constructor-arg name="name" type="java.lang.String" index="0" value="zs"/>
<constructor-arg name="age" type="int" index="1" value="19"/>
    </bean>
    <alias name="userController2" alias="uc2"></alias>

</beans>

1.8 通过静态工厂创建对象

​ 如果创建出来的对象需要经过若干设置后才能使用,spring也支持通过工厂创建bean。

​ 所谓的工厂设计模式就是通过一个工厂类将创建对象的细节封装起来,之后通过工厂创建对象,简化创建对象的过程。

1.8.1 准备目标类
public class User {
    public void addUser(){
        System.out.println("增加一个用户");
    }
}
1.8.2 静态工厂创建对象
public class StaticFactory {
    public static User getInstance(){
        return new User();
    }
}
1.8.4 配置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">

    <bean id="user" class="com.jt.factory.StaticFactory" factory-method="getInstance">
    </bean>

</beans>
1.8.5 测试
public class TestSpring {
    @Test
    public void TestDemo1(){
        ApplicationContext context=new ClassPathXmlApplicationContext("spring.xml");
        User user =(User) context.getBean("user");
        StaticFactory user2 =context.getBean(StaticFactory.class);//报错,容器中根本没存静态工厂的实例化对象
        user.addUser();
    }
}
1.8.6 原理图

Spring框架

1.9 通过实例工厂创建对象

​ 实例工厂和静态工厂类似,只不过实例工厂提供的方法不是静态的

​ Spring需要先创建出实例工厂的对象,在调用实例工厂对象上指定的普通方法来创建对象。

​ 所以实例工厂也需要配置到Spring中管理。

1.9.1 准备目标类
public class User {
    public void addUser(){
        System.out.println("增加一个用户");
    }
}
1.9.2 实例工厂创建对象
public class StaticFactory {
    public User getInstance(){
        return new User();
    }
}
1.9.3 配置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">
           
    <bean id="instanceFactory" class="com.jt.factory.InstanceFactory" ></bean>
    <bean id="user" factory-bean="instanceFactory" factory-method="getInstance"></bean>

</beans>
1.9.3 测试
   @Test
    public void TestDemo2(){
        ApplicationContext context=new ClassPathXmlApplicationContext("spring.xml");
        User user =(User) context.getBean("user");
        InstanceFactory instanceFactory =context.getBean(InstanceFactory.class);//不报错
        user.addUser();
    }
1.9.4 图解

Spring框架

二. Spring 注解开发

2.1 关于Spring注解开发的说明

大约在2015年以前 框架的开发需要大量的xml配置文件。导致项目配置比较臃肿,开发效率略低. 但是项目整合时的 报错概率很高. Spring与时俱进 从3开始逐步演化为注解开发. 到了SpringBoot框架的诞生,标志着进入了全注解时代.

2.2 注解开发的步骤

2.3.1 编辑User类
 public class User {
    public void say(){
        System.out.println("使用权注解方式管理对象");
    }
}
2.3.2 编辑配置类

说明: 原始的开发使用xxx.xml文件 用来管理对象, 现在都使用java类的形式当作配置文件则将该java类 称之为配置类.

package com.jt.config;

import com.jt.demo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration/*将当前类标识为配置类*/
public class SpringConfig {
    /*
    * 1.xml形式
    *           spring容器底层结构是维护一个 Map集合结构 Map<id,反射实例化出来的对象>
    *          <bean id="user" class="com.jt.demo.User"></bean>
    * 2.注解形式
    *          spring容器底层结构是维护一个 Map集合结构 Map<方法名(不带括号),方法的返回值>
    *           @Bean 将方法的返回值交给spring容器管理
    *  */

//    @Bean("user")  如果什么都不写,就是以方法名为key,写了标识就以标识为key
    @Bean /*将方法的返回值标记为被spring容器管理*/
    /*@Bean 该注解功能最强大,灵活度最高,因为里面的对象是自己创建的,可以传参数*/
    public User user(){
        return new User();//反射机制
    }
}
2.3.3 编辑测试类
public class TestSpring {
    @Test
    //利用注解的方式管理对象
    public void testDemo1(){
        /*ApplicationContext : spring容器的*接口   context实现类对象*/
        //1.利用注解启动spring容器
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        //2.从容器中获取对象
        User user = context.getBean(User.class);
        User user2 = context.getBean(User.class);
        System.out.println(user==user2);//true
//        User user1 =(User) context.getBean("user");  
        //麻烦,需要强制类型转换,且后面方法名改了这里也需要改,而用字节码对象则不用
        //3.对象调用方法
        user.say();

    }
}

2.4 关于IOC总结

  1. 什么是IOC: 由Spring容器管理对象的生命周期,降低代码耦合性

  2. xml配置文件管理对象
    1.准备xxx.xml配置文件

    2.准备bean标签

    3.spring容器管理对象
    ApplicationContext容器*接口
    ClassPathXmlApplicationContext 加载配置文件的实现类对象

  3. 全注解的方式管理对象

    1. 准备配置类 @Configuration + @Bean

    2. 要求方法 必须有返回值

    3. 容器对象
      ApplicationContext容器*接口
      AnnotationConfigApplicationContext

万能语法: 根据当前spring的配置规则,实例化接口对象. 我一般不写这些代码,如果想看也可以通过ApplicationContext 查找指定的实现类.

2.5 Spring创建对象—工厂模式(必会内容)

2.5.1 关于对象管理问题说明

问题: 任意对象都可以通过new的关键字 实例化吗?
答案: 当然不是, 抽象类对象 不可以直接实例化.

2.5.2 关于spring中注解说明
  1. @Component 将当前的类,交给Spring容器管理, 对象的创建是由Spring通过反射机制自动创建对象.
  2. @ComponentScan(“com.jt”) 指定扫描的包路径, 可以扫描它的子孙包,用在配置类中
2.5.3 代码结构

Spring框架

2.5.4 编辑配置类
package com.jt.config;

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

//准备一个配置类
@Configuration
@ComponentScan("com.jt")   /*该注解用在配置类中,Spring容器启动时(创建容器对象即启动),根据指定的包路径,扫描其子孙包,*/
public class SpringConfig {

}
2.5.5 编辑User类
package com.jt.demo;

import org.springframework.stereotype.Component;

/* Spring容器管理  Map<类名首字母小写user,反射出来的实例化对象>*/

@Component  /*将当前的类交给spring容器管理,对像的创建是由Spring通过反射的机制自动创建对象*/
public class User {
    public void say(){
        System.out.println("通过@Component注解实例化对象");
    }
}
2.5.6 编辑测试类
@Test
public void testDemo1(){
    ApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);
    User user = context.getBean(User.class);
    user.say();
}
2.5.7 利用工厂模式创建对象
2.5.7.1 业务说明

Spring中管理的对象,大部分可以通过new/反射进行对象的创建. 但是有些对象由于特殊的原因.不能直接new/实例化.这时需要考虑是否可以通过工厂模式实现.
例如: Calendar 该类是一个抽象类 所以不能直接实例化

2.5.7.2 创建工厂模式
package com.jt.factory;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

import java.util.Calendar;
/*
* FactoryBean是Spring提供的接口,Spring自动调用完成,获取指定的对象,
* 难点讲解
*   1.@Component  标识类  则将该类交给Spring容器管理
*   2.Spring中FactoryBean的讲解  如果Spring加载的时候,遇到FactoryBean的接口,则会自动执行重写的方法getObject()/getObjectType()
*   3.工厂模式说明: Map<Key=calendarFactory,value=Calendar对象>,对应无参注解
*                 Map<Key=calendar,value=Calendar对象>,对应有参注解
*        核心功能:
*               1.key: 就是当前类型首字母小写(如果自己编辑注解,以注解为准),当前类第二个字母也为大写,则和类名y
*               2.value: 调用getObject()的放回置对象
*               3.将上述的数据,交给Spring容器管理
*       啥时候用:
*               1.对某些对象不能直接实例化
*               2.整合第三方框架对象时候,经常使用
* */
//@Component
@Component("calendar")
public class CalendarFactory implements FactoryBean<Calendar> {

    public CalendarFactory(){
        /*构造函数还是被调用,因为Spring容器是先创建了CalendarFactory类的实例化对象,
        然后利用这个对象调用了getObject()/getObjectType()*/
        System.out.println("工厂模式的无参构造");
    }
    //现阶段 大家理解为主. 未来写结构设计的时候,作用很大!!!!!
    /*动态执行该方法获取返回值对象,几乎是万能的*/
    /*方法是spring容器自动调用*/
    @Override
    public Calendar getObject() throws Exception {
        //利用Calendar的工具API,实现对象的创建
        return Calendar.getInstance();
    }

    @Override
    public Class<?> getObjectType() {
       /*固定写法,一般直接xxx.class即可*/
        return Calendar.class;
    }
}
2.5.7.3 编辑测试API
@Test
public void testDemo2(){
    ApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);
    Calendar calendar = context.getBean(Calendar.class);
    System.out.println("获取当前时间"+ calendar.getTime());
    System.out.println("获取当前年"+ calendar.getWeekYear());
}

2.6 注解复习

  1. @Configuration 标识配置类
  2. @Bean 将自己方法的返回值交给Spring容器管理
  3. @Component 将该类交给spring容器管理. 通过反射自动实例化对象
  4. @ComponentScan(“com.jt”) 包扫描的注解 使Spring注解有效

三. 单例和多例

3.1 关于单例和多例说明

  1. 单例: 在内存中只有一份
  2. 多例: 在内存中可能有多份

3.2 编辑User类

package com.jt.demo;

import org.springframework.stereotype.Component;

public class User {
    public User(){
        System.out.println("我是无参构造,创建对象");
    }

    public void say(){
        System.out.println("测试对象是单例还是多例");
    }
}

3.3 关于单例多例的测试

规则1:Spring默认管理的对象都是单例的。
规则2: 通过@Scope注解,控制对象单例/多例

package com.jt.config;

import com.jt.demo.User;
import org.springframework.context.annotation.*;

@Configuration //标识这是配置类
@ComponentScan("com.jt")
public class SpringConfig {
    @Bean
//    @Scope("singleton") //默认值  单例模式
//    @Scope("prototype")//多例模式
    @Lazy
    public User user(){
        return new User();
    }
}

3.4 编辑测试类

@Test
public void testDemo1(){
    ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
    User user1 = context.getBean(User.class);
    User user2 = context.getBean(User.class);
    System.out.println(user1);

    /*单独的@component 注解的对象地址值com.jt.demo.User@942a29c
    @component 和@Bean共存的时候 com.jt.demo.User@e350b40  说明@component先执行,后执行@Bean,然后@component值被覆盖*/

    System.out.println(user1==user2);//true
    user1.say();
    user2.say();

​ singleton作用域:当把一个Bean定义设置为singleton作用域是,Spring IoC容器中只会存在一个共享的Bean实例,并且所有对Bean的请求,只要id与该Bean定义相匹配,则只会返回该Bean的同一实例。值得强调的是singleton作用域是Spring中的缺省作用域。
​ prototype作用域:prototype作用域的Bean会导致在每次对该Bean请求(将其注入到另一个Bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的Bean实例。根据经验,对有状态的Bean应使用prototype作用域,而对无状态的Bean则应该使用singleton作用域。对于具有prototype作用域的Bean,有一点很重要,即Spring不能对该Bean的整个生命周期负责。具有prototype作用域的Bean创建后交由调用者负责销毁对象回收资源。
​ 简单的说:
​ singleton 只有一个实例,也即是单例模式。(默认)
​ prototype访问一次创建一个实例,相当于new。
​ 应用场合:
​ 1.需要回收重要资源(数据库连接等)的事宜配置为singleton,如果配置为prototype需要应用确保资源正常回收。
​ 2.有状态的Bean配置成singleton会引发未知问题,可以考虑配置为prototype。

四. 懒加载机制

4.1 懒加载说明

说明: 如果Spring容器创建,对象立即创建则该加载方式为 “立即加载”, 容器启动创建
如果Spring容器创建,对象在被使用的时候才创建, 则称为"懒加载" 用时才创建

注解: @Lazy 添加表示改为懒加载
测试说明: 主要测试对象中的无参构造什么时候执行!!!

4.2 懒加载用法

package com.jt.config;

import com.jt.demo.User;
import org.springframework.context.annotation.*;

@Configuration //标识这是配置类
@ComponentScan("com.jt")
public class SpringConfig {
    @Bean
//    @Scope("singleton") //默认值  单例模式
//    @Scope("prototype")//多例模式
    @Lazy     //懒加载
    public User user(){
        return new User();
    }
}

4.3 多例与懒加载的关系

说明: 只要对象是多例模式,则都是懒加载!, 在单例模式中控制懒加载才有效.
规则说明:
lazy true lazy false
单例模式: 有效 懒加载 有效 立即加载
多例模式: 无效 懒加载 无效 懒加载

4.4 关于lazy 使用场景的说明

场景1: 服务器启动时,如果加载太多的资源,则必然导致服务器启动慢, 适当的将不重要的资源设置为懒加载.
场景2: 有时用户会需要一些特殊的"链接",而这些链接的创建需要很长的时间.可以使用懒加载.

五. Spring对象生命周期管理

5.1 关于对象生命周期说明

说明: 一个对象从创建到消亡,可以划分为四个阶段. 如果需要对程序进行干预.则可以通过周期方法进行干预. (回调函数/钩子函数/接口回调)
图解说明生命周期函数的作用: 主要作用可以在各个时期对对象进行干预

Spring框架

5.2 生命周期函方法的使用

package com.jt.demo;

import org.springframework.stereotype.Component;

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

@Component
public class Person {
    public Person(){
        System.out.println("张三出生了,资质拉满");
    }
    @PostConstruct  /*对象创建后立即调用*/
    public void init(){
        System.out.println("张三成为少年奇才");
    }
    //业务方法,一般都是手动调用
    public void doWork(){
        System.out.println("迎娶美人鱼");
    }
    @PreDestroy  //对象消亡时进行调用
    public void destory(){
        System.out.println("销毁,全世界哀悼");
    }
}

5.3 测试案例

@Test
public void testDemo3init(){
    /*启动Spring容器(容器启动 对象创建)*/
    AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
    //从容器中获取对象
    Person person = context.getBean(Person.class);
    person.doWork();
    //将容器关闭
    context.close();
    /*注意要将context对象从ApplicationContext接口转为子实现类AnnotationConfigApplicationContext(此类继承了其爷爷类的close方法)的对象,*/
}

3.4 使用注解

  1. @PostConstruct //在对象创建之后立即调用
  2. @PreDestroy //对象消亡时 进行调用,需要调用close方法

六. 依赖注入(Dependency Injection,简称DI)

解决面向接口开发得弊端:属性对应的对象和当前类耦合性高

6.1 @Autowired注解

说明: 在对象中如果需要使用属性注入.一般使用@Autowired注解.
功能: 可以将Spring容器中的对象,自动注入到属性中.

注入方式:

  1. 默认按照类型注入. 如果注入的属性是接口,则自动注入实现类
  2. 按照名称注入(key). 一般不用

重要前提: 如果需要依赖注入.则对象必须交给Spring容器管理.

6.1.2 编辑Pet接口
public interface Pet {
    void hello();
}
6.1.3 编辑Cat类
package com.jt.demo;

import org.springframework.stereotype.Component;

@Component //将对象交给spring容器管理 key:cat value:反射Cat对象
public class Cat implements Pet{

    @Override
    public void hello() {
        System.out.println("我是喵喵喵");
    }
}
6.1.4 编辑User类
package com.jt.demo;

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

import javax.annotation.Resource;

@Component
public class User {
//    private Pet pet=new Cat();  老方法,耦合性高,Cat对象和User类绑定在一起了

    @Autowired          /*效果:当前接口的实现类自动注入,但如果当前接口有多个实现类,会报错,需要配合 @Qualifier使用*/
    @Qualifier("dog")      /*该注解不能单独使用,必须配合@Autowired使用,根据key进行注入*/
    @Resource(name="cat")  /*尽量不要使用   功能上等于@Autowired+ @Qualifier("dog")*/
    private Pet pet;   //2选1就报错  得加@Qualifier("dog") 选择对象具体得key值
    public void say(){
        //调用宠物的方法
        pet.hello();
    }
}
6.1.5 编辑配置类
package com.jt.config;

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

@Configuration
@ComponentScan("com.jt")
public class SpringConfig {

}
6.1.6 编辑配置类
package com.jt;

import com.jt.config.SpringConfig;
import com.jt.demo.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

class TestSpring {

    @Test
    public void testDemo1(){
        ApplicationContext context =
                new AnnotationConfigApplicationContext(SpringConfig.class);
        User user = context.getBean(User.class);
        user.say();
    }

}

6.2 接口多实现的情况说明

6.2.1 代码说明(Pet接口再添加一个猫类)
package com.jt.demo;

import org.springframework.stereotype.Component;

@Component
public class Cat implements Pet{

    @Override
    public void hello() {
        System.out.println("我是喵喵喵");
    }
}
6.2.2 报错说明

说明: 一个接口应该只有一个实现类,否则spring程序无法选择.

6.3.3 决解方案
package com.jt.demo;

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

import javax.annotation.Resource;

@Component
public class User {
//    private Pet pet=new Cat();  老方法,耦合性高,Cat对象和User类绑定在一起了

    @Autowired          /*效果:当前接口的实现类自动注入,但如果当前接口有多个实现类,会报错,需要配合 @Qualifier使用*/
    @Qualifier("dog")      /*该注解不能单独使用,必须配合@Autowired使用,根据key进行注入*/
    @Resource(name="cat")  /*尽量不要使用   功能上等于@Autowired+ @Qualifier("dog")*/
    private Pet pet;   //2选1就报错  得加@Qualifier("dog") 选择对象具体得key值
    public void say(){
        //调用宠物的方法
        pet.hello();
    }
}

七. MVC 设计思想

7.1 MVC思想说明

经典MVC模式中,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。其中,View的定义比较清晰,就是用户界面。

M: model 业务模型
V: view 用户界面
C: control 控制层

历史说明:
JSP动态页面 html代码 + java代码 写到一起 xxx.jsp
不方便后期维护. 页面和业务执行紧紧的绑定在一起耦合性高.

Spring框架

小结:

  1. MVC是一种设计思想,编码中降低代码的耦合性.
  2. 前端专注于开发页面 view
  3. 后端专注于开发后端 model
  4. 2者通过control 进行控制

7.2 层级代码结构

说明: MVC设计思想,实现了前端和后端的松耦合.但是根据实际的开发情况,很多的业务逻辑比较复杂.如果后端将所有的代码都写到同一个java类中.这样的代码结构很臃肿. 为了很好的实现MVC设计思想.所以后端代码也应该分层.

分层说明:

  1. 控制层 Controller 与前端页面交互的. @Controller
  2. 业务层 Service 编辑业务逻辑. @Service
  3. 持久层 Mapper 实现数据库的相关操作 暂时:@Repository
    MVC > 三层代码结构!!!

7.3 三层代码结构

7.3.1 编辑Mapper层
package com.jt.mapper;

public interface UserMapper {
    void addUser();
}
package com.jt.mapper;

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

@Repository
@PropertySource(value="classpath:/user.properties",encoding = "UTF-8")  /*classpath:/  代表resources的根目录*/
public class UserMapperImpl implements UserMapper{
    /*表达式:固定写法 &{}  springel表达式 取值方式  缩写spel表达式
    * 规则:通过表达式动态获取spring容器中的value
    * 通过key获取value
    * */
    @Value("${user.userName}") /*可以为基本类型直接赋值,但是引用类型能直接赋值*/
    private String userName;
    @Override
    public void addUser() {
        System.out.println("新增一个用户"+userName);
    }
}
7.3.3 编辑Service层
package com.jt.service;

public interface UserService {
    void addUser();
}
package com.jt.service;

import com.jt.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    //@Qualifier("userMapperImpl")
    private UserMapper userMapper;
    @Override
    public void addUser() {
        userMapper.addUser();
    }
}
7.3.4 编辑Controller层
package com.jt.controller;

import com.jt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

/*程序之间的互相调用为了降低耦合性,会使用接口+实现类的方式进行创建属性形式的对象互相调用,Controller层没有人调用,只需要创建类就可以了
    * 1.(面向接口)解决了属性和对象方法之间的耦合性问题,2.(动态注入)解决了属性对应的对象和当前类的耦合性问题*/

@Controller
public class UserController {
    @Autowired
    private UserService userService;
    public void addUser(){
        userService.addUser();
    }
}
7.3.5 编辑配置类
package com.jt.config;

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

@Configuration
@ComponentScan("com.jt")
public class SpringConfig {
}
7.3.6 编辑测试类
package com.jt;

import com.jt.config.SpringConfig;
import com.jt.controller.UserController;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestSpring {
    @Test
    public void TestDemo1(){
        ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
        UserController userController = context.getBean(UserController.class);
        userController.addUser();
    }
}
7.3.7 请谈一下你对IOC/DI的看法(开放)

历史: 传统代码其中的属性对象一般都是通过new关键字手动创建,这样的代码耦合性高,不方便扩展
功能:

  1. IOC: 由Spring容器管理对象的生命周期.
  2. 使得对象与对象之间的耦合性降低.
  3. DI是依赖注入. 只有被spring容器管理的对象才可以被依赖注入. 默认的条件下采用类型注入.如果有特殊需求也可以采用名称注入(@Qualifier(“cat”))
  4. Spring中 IOC和DI相互配合,可以极大程度上降低耦合性.

意识:
Spring由于采用了IOC/DI的设计方式,可以整合其它的第三方框架.使得程序的调用"浑然一体"

7.4 @Value注解说明

7.4.1 注解赋值

说明: @Value注解 可以直接为基本类型赋值和String类型
问题: 如果像图中赋值,则耦合性依然很高,不通用. 需要优化!!!

7.4.2 编辑user.properties

说明: 对象中的属性一般都是业务数据,如果需要为业务数据赋值,则一般采用properties文件 更加灵活.
位置: 在resources目录下

#1.key=value  等号连接  2.中间不要添加多余的空格
#2.说明:windows系统中有环境变量username=系统用户名,以后写业务数据时,最好绕开这个关键字username
#3.编码规则: 程序默认读取properties文件时,采用ISO-8859-1的编码表读取,所以在读取该文件的时候要设置成utf-8的编码表读取
user.userName=葫芦娃
7.4.3 @value为属性赋值
package com.jt.mapper;

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

@Repository
@PropertySource(value="classpath:/user.properties",encoding = "UTF-8")  /*classpath:/  代表resources的根目录*/
public class UserMapperImpl implements UserMapper{
    /*表达式:固定写法 &{}  springel表达式 取值方式  缩写spel表达式
    * 规则:通过表达式动态获取spring容器中的value
    * 通过key获取value
    * */
    @Value("${user.userName}") /*可以为基本类型+String直接赋值,但是引用类型不能直接赋值*/
    private String userName;
    @Override
    public void addUser() {
        System.out.println("新增一个用户"+userName);
    }
}

八. 代理模式

8.1 代理模式特点

说明: 一般采用代理模式,主要的目的就是为了解耦.将公共的通用的方法(功能/业务)放到代理对象中. 由业务层专注于业务执行即可.

8.2 代理特点
  1. 为什么使用代理? 因为自己不方便(没有资源)
  2. 代理的作用? 代理要解决(扩展)某些实际问题.
  3. 用户最终执行目标方法!!!

Spring框架

8.3 静态代理模式

8.3.1 静态代理设计模式特点:

​ 优点 : 结构清晰 易于理解

​ 缺点:如果被代理者有多个方法,则代理者也需要开发多个方法,其中往往存在大量重复代,码,仍然存在代码重复。

静态代理设计模式解决了软件分层过程中 额外的功能代码侵入模块的问题,将额外的功能代码提取到了代理者中进行,但是静态代 理实现的代理者中存在大量重复的代码,并没有解决代码重复问题。所以在真正开发中–包括spring的底层,基本不会使用静态代 理。

package com.jt.staticproxy;

public interface ActorSkill {
    void sing();
    void act();
}
package com.jt.staticproxy;

public class 杨紫 implements ActorSkill{
    @Override
    public void sing() {
        System.out.println("杨紫在唱歌");
    }
    @Override
    public void act() {
        System.out.println("杨紫在演习");
    }
}
package com.jt.staticproxy;

public class Agent implements ActorSkill{
    private 杨紫 yz=new 杨紫();
    @Override
    public void sing() {
        System.out.println("沟通时间,地点");
        yz.sing();
        System.out.println("叫专车回酒店");
    }

    @Override
    public void act() {
        System.out.println("沟通剧本");
        System.out.println("沟通签合同");
        System.out.println("沟通时间和地点");
        yz.act();
        System.out.println("叫车回酒店");
    }
}
package com.jt;

import com.jt.staticproxy.Agent;

public class TestStaticProxy {
    public static void main(String[] args) {
        Agent agent=new Agent();
        agent.sing();
        agent.act();
    }
}

8.4 动态代理- jdk内置的动态代理

8.4.1 JDK动态代理的说明:
  1. JDK代理模式是java原生提供的API,无需导包
  2. JDK代理要求: 被代理者必须 要么是接口,要么实现接口
  3. 灵活: 代理对象应该看起来和被代理者 一模一样!!! (方法相同)

​ 在jdk中提供了动态代理实现的工具类,直接使用该工具类就可以创建出代理者,并且可以通过内置的回调函数指定代理在工作时的执行逻辑,从而实现基于jdk原生api的动态代理机制。

java.lang.reflect 
类 Proxy
java.lang.Object
static Object	newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
8.4.2 java动态代理的特点:

​ 优点:

​ 不需要像静态代理一样被代理方法都要实现一遍,而只需要在回调函数中进行处理就可以了,重复代码只需编写一次。

​ 缺点:

​ java的动态代理是通过代理者实现和被代理者相同的接口来保证两者具有相同的方法的,如果被代理者想要被代理的方法不属于任何接口,则生成的代理者自然无法具有这个方法,也就无法实现对该方法的代理。所以java的动态代理机制是基于接口进行的,受制于要代理的方法是否有接口的支持。

案例:

package com.jt.jdkproxy;

public interface ActorSkill {
    void sing();
    int act(int i);
}

被代理的类

package com.jt.jdkproxy;

public class 杨幂 implements ActorSkill{

    @Override
    public void sing() {
        System.out.println("杨幂在唱歌");
    }

    @Override
    public int act(int i) {
        System.out.println("杨幂在演戏"+i+"次");
        return i;
    }

    public void dance(){
        System.out.println("杨幂在跳舞");
    }
}

测试

package com.jt;

import com.jt.jdkproxy.ActorSkill;
import com.jt.jdkproxy.杨幂;
import org.junit.jupiter.api.Test;

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

public class TestJDKProxy {
    @Test
    public void testDemo(){
        //被代理对象
        杨幂 ym = new 杨幂();
        /*
         * classLoader:用来生成代理者类的类加载器,通常可以传入被代理者类的类加载器
         * interfaces: 要求生成的代理者实现的接口们,通常就是实现和被代理者相同的接口,保证具有和被代理者相同的方法
         * invocationHandler: 用来设定回调函数的回调接口,使用者需要写一个类实现此接口,从而实现其中的invoke方法,
         * 在其中编写代码处理代理者调用方法时的回调过程,通常在这里调用真正对象身上的方法,并且在方法之前或之后做额外操作。
         */
        //java动态代理方式,生成ym的代理对象
        //返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
        ActorSkill o = (ActorSkill)Proxy.newProxyInstance(杨幂.class.getClassLoader(), 杨幂.class.getInterfaces(), new InvocationHandler() {
            @Override
            /*
            * proxy:代理对象
            * method:当前调用的方法对象
            * args:当前调用的方法的参数数组
            * */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if ("act".equals(method.getName())) {
                    System.out.println("沟通剧本");
                    System.out.println("沟通签合同");
                }
                System.out.println("沟通时间地点");
                //inovke传入的对象拥有的方法必须拥有当前调用此方法的Method对象底层所表示的方法
                //如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。
                //如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null。
                Object obj = method.invoke(ym,args);
                System.out.println("叫个车回酒店");
                return obj;
            }
        });
        /*可以拿到方法的返回值,证明调用了回调函数*/
        int act = o.act(1);
        System.out.println(act);//1
        o.sing();
        o.dance();//报错,无法调用杨幂专属方法
    }

}

动态代理图解

Spring框架

8.5 动态代理 - 第三方包cglib实现的动态代理

​ CGLIB是第三方提供的动态代理的实现工具,不管有没有接口都可以实现动态代理。

​ CGLIB实现动态代理的原理是 生成的动态代理是被代理者的子类,所以代理者具有和父类即被代理者 相同的方法,从而实现代理,这种方式基于继承,不再受制于接口。

CGLIB动态代理的特点:

​ 优点:无论是否有接口都可以实现动态代理,使用场景基本不受限

​ 缺点:第三方提供的动态代理机制,不是原生的,需要导入第三方开发包才可以使用。

案列:

package com.jt;

import com.jt.jdkproxy.ActorSkill;
import com.jt.jdkproxy.杨幂;
import org.junit.jupiter.api.Test;
import org.springframework.cglib.proxy.Callback;
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 TestCGLIBProxy {
    @Test
    public void testDemo1(){
        杨幂 ym = new 杨幂();
        //首先要获取增强器
        Enhancer enhancer = new Enhancer();
//        System.out.println(ActorSkill.class.getInterfaces());//[Ljava.lang.Class;@33b37288
//        System.out.println(杨幂.class.getInterfaces());//[Ljava.lang.Class;@77a57272
//        System.out.println(ym.getClass().getInterfaces());//[Ljava.lang.Class;@7181ae3f
        /*可以使用下面两个获取接口的方式,第一种获取接口的方式是错误的*/

        //设定接口 -- 此方法要求生成的动态代理额外实现指定接口们 ,单cglib动态代理不是靠接口实现的,所以可以不设置
        /*不设置也可以执行,底层类似于继承原理,直接获取了杨幂类的所有方法*/
        enhancer.setInterfaces(杨幂.class.getInterfaces());
        //设定父类 -- 此处要传入被代理者的类,cglib是通过集成被代理者的类来持有和被代理者相同的方法的,此方法必须设置
        enhancer.setSuperclass(杨幂.class);
        //设定回调函数 -- 为增强器设定回调函数,之后通过增强器生成的代理对象调用任何方法都会走到此回调函数中,实现调用真正被代理对象的方法的效果
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                if("act".equals(method.getName())){
                    System.out.println("沟通剧本");
                    System.out.println("沟通签合同");
                }
                System.out.println("沟通时间和地点");
                Object invoke = method.invoke(ym, objects);
                System.out.println("叫车回酒店");
                return invoke;
            }
        });
        杨幂 agent = (杨幂)enhancer.create();
        int act = agent.act(1);
        System.out.println(act);
        agent.sing();
        //可以调用杨幂私有的方法
        agent.dance();
        /*沟通时间和地点
          杨幂在跳舞
          叫车回酒店
        */
    }
}

优化练习:

package com.jt.cglibpropxy;

public class Star {
    private String name;
    public Star(){}

    public Star(String name) {
        this.name = name;
    }
    public void act(){
        System.out.println(name+"在演戏");
    }
    public void sing(){
        System.out.println(name+"在唱歌");
    }
}
package com.jt;

import com.jt.cglibpropxy.Star;
import org.junit.jupiter.api.Test;
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 TestCGLIBProxy2 {
    @Test
    public void TestDemo1(){
        Star dlrb = new Star("迪丽热巴");
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Star.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                if("act".equals(method.getName())){
                    System.out.println("安排谈剧本");
                    System.out.println("安排签合同");
                }
                System.out.println("安排时间和地点");
                Object invoke = method.invoke(dlrb, objects);
                System.out.println("安排专车回酒店");
                return invoke;
            }
        });
        //默认调用父类的无参构造函数,没有无参构造函数会报错
        Star agent =(Star) enhancer.create();
        agent.act();
        agent.sing();
    }
}

8.6 关于JDK代理和CGlib代理总结(高层/架构)!!!

  1. JDK要求必须有或者实现接口, cglib有无接口都可以创建代理对象.代理对象是目标对象的子类
  2. JDK代理工具API: Proxy.newProxyInstance(类加载器,接口数组,invocationHandler接口)
  3. CGlib代理工具API: Enhancer 增强器对象 获取代理对象 enhancer.create(); 回调接口
    MethodInterceptor接口
  4. JDK中执行目标方法
    -method.invoke(target,args);
    CGlib中执行目标方法
    -method.invoke(target,args);

九. Spring AOP介绍

9.1 pom文件中引入AOPjar包文件

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

9.2 AOP

9.2.1 AOP介绍

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

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

Spring框架

9.2.2 入门案例
9.2.2.1 配置类
package com.jt.config;

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

@Configuration
@ComponentScan("com.jt")
@EnableAspectJAutoProxy /*意思:启动切面自动代理 : 开启AOP*/ 
//此测试只能配合父类/父接口用,不能直接获取子类/实现类对象去加载任务,否则会报错
public class SpringConfig {
}
9.2.2.2 编辑切面类
package com.jt.aop;

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

@Component //将该类交给容器管理
@Aspect   //表示该类是一个切面
public class SpringAop {
    /*
    * z回顾:AOP利用动态代理扩展目标方法
    * 公式:切面 = 切入点表达式+通知方法
    * 切入点表达式 : 如果目标对象满足切入点表达式的判断(if),则Spring自动为其创建代理对象
    * 通知方法 : 对目标方法进行扩展的封装方法(将来程序运行的就是通知方法,类似动态代理的invoke和intercept方法)
    * 目标对象bean的ID : userServiceImpl
    * 切入点表达式 :
    *           1.bean("bean的ID")
    * AOP规则:如果目标对象满足了切入点表达式,则执行通知方法
    * */
    @Pointcut("bean(userServiceImpl)") /*判断当前目标对象bean的id等于userServiceImpl如果满足,则创建代理对象,并执行通知方法*/
    public void pointcut(){

    }
    //1.前置通知,在目标方法执行之前执行
    /*如果目标对象满足了切入点表达式,则执行通知方法*/
    @Before("pointcut()")
    public void before(){
        System.out.println("我是前置通知!!!");
    }
    //2.后置通知,在目标方法执行之后执行
    @AfterReturning("pointcut()")
    public void afterReturn(){
        System.out.println("我是后置通知");
    }
    //3.异常通知,目标方法执行报错时,执行该通知
    @AfterThrowing("pointcut()")
    public void afterThrowing(){
        System.out.println("我是异常的通知");
    }
    //4.最总通知,目标方法之后,都要执行的通知
    @After("pointcut()")
    public void after(){
        System.out.println("最终通知 都要执行");
    }
    //5.重点掌握 环绕通知,因为在目标方法执行前后都要执行,控制目标方法
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint JoinPoint) throws Throwable {
        System.out.println("环绕通知执行前!!!");
        //底层调用动态代理的invoke方法,执行目标方法
//        Object result = JoinPoint.proceed();
        System.out.println("环绕通知执行后");
//        return result;
        return null;
    }
}
9.2.2.3 测试
package com.jt;

import com.jt.config.SpringConfig;
import com.jt.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestSpring {
    @Test
    public void TestDemo1(){
        ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
        //只能通过接口类型获取对象,多态,按照实现类类型或者id获取对象都会报错
        //面向接口开发  AOP规则内部不能直接通过实现列创建代理对象(除非不写接口和实现类),只写一个类的话默认是cglib代理
        UserService userService = context.getBean(UserService.class);
        /*本来是目标对象,然而当前对象key和切入点表达式的key匹配,则spring动态生成代理对象,
        否则就是class com.jt.service.UserServiceImpl*/
        System.out.println(userService.getClass());//class com.sun.proxy.$Proxy24
        /*由于是代理对象,所以方法可以扩展*/
        userService.addUser();
    }
}

9.3 切入点表达式

9.3.1 bean表达式

说明: 根据bean的ID拦截指定的对象.

@Pointcut("bean(userServiceImpl)") 
/*判断当前目标对象bean的id等于userServiceImpl,如果满足,则创建代理对象,并执行通知方法*/
public void pointcut(){
    }
9.3.2 within表达式

说明: 按照类型匹配. 可以使用通配符*号

	1. @Pointcut("within(com.jt.service.UserServiceImpl)")//必须使用实现类,使用接口不能触发
			   只拦截UserServiceImpl的类
	2.  @Pointcut("within(com.jt.service.*)")
			   拦截com.jt.service下的一级的类.
	3.  @Pointcut("within(com.jt.service..*)")
				拦截com.jt.service下所有包下的所有类
	4.  @Pointcut("within(com.*.service..*)")
				拦截com.任意包.service下所有包下的所有类

说明: 上述的2种操作方法 粒度较粗, 一般情况下不用.

9.3.3 execution表达式

作用:可以按照我们的方法参数进行匹配

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

	1.按照类型匹配方法
	@Pointcut("execution(* com.jt.service.UserServiceImpl.addUser())")
    2.要求返回值类型任意,com.jt.service包下的所有的子孙类的任意方法的任意参数
    @Pointcut("execution(* com.jt.service..*.*(..))")  /*括号类的..表示任意参数类型*/
    3. 要求返回值任意, com.jt.service包下的所有的子孙类中的add开头的方法并且参数1个是int类型 进行拦截
	@Pointcut("execution(* com.jt.service..*.add*(int))")
9.3.4 @annotation表达式

作用:可以根据用户的自定义注解进行拦截

9.3.4.1 完成自定义注解ss
package com.jt.annotation;

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

@Retention(RetentionPolicy.RUNTIME)  //注解标识运行存货时期
@Target({ElementType.METHOD})     //注解表示方法
public @interface ss {

}
9.3.4.2 标记注解
package com.jt.service;

import com.jt.annotation.ss;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{

    @Override
    @ss  //注解主要起标记作用
    public void addUser() {
//        int a=1/0;
        System.out.println("新增一个用户");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除一个用户");
    }
}
9.3.4.3 AOP拦截注解
@Pointcut("@annotation(com.jt.annotation.ss)")
public void pointcut(){

}

9.4 关于通知的讲解

9.4.1 关于AOP 通知的用法

第一类:

  1. @Before(“pointcut()”)
  2. @AfterReturning(“pointcut()”)
  3. @AfterThrowing(“pointcut()”)
  4. @After(“pointcut()”)
    可以记录程序执行的各个过程. 为日志提供记录.,前4个通知只有记录功能,不能改变result值,只能做记录,要想改值,需在环绕通知中

第二类:

  1. @around环绕通知 可以控制目标方法是否执行. 环绕通知可以控制业务流转的过程!!!
    例子:
    1. 权限的校验
    2. 缓存系统
    3. 异常的处理等
9.4.2 通知中常用API

需求:

  1. 获取当前的目标对象的类型.
  2. 获取当前的方法名称
  3. 获取当前传递的参数.

常见报错: ProceedingJoinPoint is only supported for around advice ProceedingJoinPoint 只能用到环绕通知中.

//1.前置通知,在目标方法执行之前执行
/*如果目标对象满足了切入点表达式,则执行通知方法*/
@Before("pointcut()")
//引用切入点方法,其实就是引用切入点表达式,单独使用的话,也可以@Before("@annotation(com.jt.annotation.ss)")
public void before(JoinPoint joinPoint){ //连接点:用来获取方法中的数据
    Class<?> aClass = joinPoint.getTarget().getClass();//获取被代理类的类型
        System.out.println("目标类型"+aClass);//目标类型class com.jt.Service.UserServiceImpl
        Signature signature = joinPoint.getSignature();//获取方法
        System.out.println(signature);//void com.jt.Service.UserService.deleteUser()
        Class classname = joinPoint.getSignature().getDeclaringType();//获取接口类型
        System.out.println("接口类型"+classname);//接口类型 interface com.jt.Service.UserService
        String declaringTypeName = joinPoint.getSignature().getDeclaringTypeName();//获取类名
        System.out.println("接口"+declaringTypeName);//接口名com.jt.Service.UserService
        String methodName = joinPoint.getSignature().getName();//获取方法名
        System.out.println("方法名"+methodName);//方法名deleteUser
        Object[] args = joinPoint.getArgs();
        System.out.println("参数"+ Arrays.toString(args));
}
9.4.3 后置通知用法

需求: 记录一下用户目标方法的返回值
说明: 通过属性returning 获取方法的返回值

//2.后置通知,在目标方法执行之后执行
//通过returning = "result"属性获取目标方法的返回值,当作参数传递给afterReturn(Object result)中的result,必须联动,否则报错
@AfterReturning(value = "pointcut()",returning = "result")
/*这里的result不和around中的return result挂钩,直接从方法中获取,return result和测试方法中的获取返回值挂钩*/
public void afterReturn(Object result){
    System.out.println("我是后置通知");
    System.out.println("用户的返回值为"+result);
}
9.4.4 异常通知的用法

说明: 如果用户执行业务方法时,报错了,则可以使用异常通知记录日志.
用法:

//3.异常通知,目标方法执行报错时,执行该通知
@AfterThrowing(value = "pointcut()",throwing = "exception")
public void afterThrowing(Exception exception){
    System.out.println("我是异常的通知");
    System.out.println("获取异常信息"+exception.getMessage());//获取异常信息
    exception.printStackTrace();//打印所有异常详情信息
}

Spring框架

9.5 切面排序

说明: 根据@Order注解 实现切面的排序

@Order(数字) : 数字越小,越靠前执行

package com.jt.aop;

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

import java.util.Arrays;

@Component //将该类交给容器管理
@Aspect   //表示该类是一个切面
@Order(2)  //当前Aop第二个执行
public class SpringAop {
    //5.重点掌握 环绕通知,因为在目标方法执行前后都要执行,控制目标方法
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint JoinPoint) throws Throwable {
        //底层调用动态代理的invoke方法,执行目标方法,如果有下一个通知,则执行通知方法,没有通知,则执行目标方法
        System.out.println("环绕通知A执行前!!!");
        Object result = JoinPoint.proceed();
        System.out.println("环绕通知A执行后");
        return result;
    }
}
package com.jt.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Order(1)   //Aop第一个执行,数字越小越靠前
public class SpringAop2 {
    @Around("@annotation(com.jt.annotation.ss)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知B开始");
        Object result = joinPoint.proceed();
        System.out.println("环绕通知B结束");
        return result;
    }
}

结果是嵌套效果

环绕通知B开始
环绕通知A执行前!!!
添加了一个用户
环绕通知A执行后
环绕通知B结束

9.6 关于代理对象生成策略说明

默认策略:

  1. Spring中默认采用的动态代理的规则是JDK代理,如果被代理者没有接口.则自动使用CGLIB
  2. 如果需要修改为cglib代理,则添加如下代码
package com.jt.config;

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

@Configuration
@ComponentScan("com.jt")
@EnableAspectJAutoProxy(proxyTargetClass = true) /*意思:启动切面自动代理 : 开启AOP*/ //开启cglib代理
public class SpringConfig {
}

SpringBoot中默认代理模式采用CGLIB代理.如果需要修改为JDK代理则需要修改配置文件即可

十. 关于Spring总结

  1. 为什么学习spring框架 让程序设计实现松耦合.

  2. 什么是面向接口编程 以后对象中的属性一般写接口. java中多态的体现. 属性类型更加的灵活 松耦合.

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

  4. Spring容器启动方式 1. xml方式 2.注解方式

  5. 什么时候使用工厂模式: 1.对象不能直接实例化的时候. 2.spring框架整合其它第三方框架时使用. FactoryBean

  6. 单例多例问题: 默认条件下是单例模式 @Scope(“prototype”)

  7. 懒加载规则: 1. 默认条件下 懒加载无效 添加注解@Lazy 有效. 只对单例模式有效.

  8. spring生命周期管理 4个过程: 1.对象创建 2. 对象初始化 @PostConstruct 3. 业务调用 4.对象销毁 @PreDestroy

  9. Spring中依赖注入的注解@Autowired 1.默认按照类型注入 2.可以按照名称注入 @Qualifier(“cat”) @Resource java中的注解.

  10. MVC 设计思想 View 视图层 Model业务层 Control 控制层

    ​ 根据MVC设计思想: 层级代码结构 Controller/Service/Mapper|Dao

  11. @Value spring为属性动态赋值 基本类型和String和集合(几乎不用)

  12. 动态代理 JDK动态代理/CGLib动态代理

  13. AOP 面向切面编程 在不改变源码的条件下对方法进行扩展.

  14. AOP常见注解

    1. @Aspect 标识切面
    2. @Pointcut 标识切入点表达式 4种写法 2种常用
    3. 五个通知注解
    4. @EnableAspectJAutoProxy(proxyTargetClass = true) //开启AOP
    5. 排序注解 @Order
  15. 代理对象只要调用方法都会走回调函数

十一. SpringAOP作业

service
package com.jt.service;

public interface UserService {
    void addUser();
    void  deleteUser();
}
package com.jt.service;

import com.jt.annotion.Pri;
import com.jt.annotion.ss;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{
    @ss
    @Pri(name = "add")//只有add的权限
    @Override
    public void addUser(){
        System.out.println("新增一个用户");
    }

    @Override
    @Pri(name="asss")
    public void deleteUser() {
        System.out.println("删除一个用户");
    }
}
config
package com.jt.config;

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

@Configuration
@ComponentScan("com.jt")
@EnableAspectJAutoProxy/*(proxyTargetClass = true)*/
public class SpringConfig {
}
annotation
package com.jt.annotion;

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 Pri {
    String name();//要求用户必填写
}
package com.jt.annotion;

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 ss {
}
aop
package com.jt.aop;

import com.jt.annotion.Pri;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Component
@Aspect
public class SpringAop1 {
    private List<String> list=new ArrayList<>();//单机版本
//    ArrayList是线程不安全的add方法未加同步锁  Vector集合是安全的  原因在于里面的add方法上加了同步锁
    @PostConstruct
    public void init(){
        list.add("add");
        list.add("update");
        System.out.println("初始化集合");
    }
    /*
    * 1.获取方法中的注解@Pri(name="add")
    * 2.获取权限名称 name="add"
    * 3.获取list集合中是否有该权限
    *           true : 有权限,执行目标方法
    *           false: 输出 没有权限,直接跳过
    * 4.规则:
    *       1.如果有多个参数,则joinPoint必须位于第一位!!!!args[0]
    *       变态写法:
    *           可以直接写注解的名称,将切点属性变成参数传入around中,其他的通知类型也可以这么x
    * */
//    @Around(value = "@annotation(com.jt.annotion.Pri)")//当前→1.拦截注解Pri这个类   目标→2.获取注解的数据
    @Around(value = "@annotation(pri)")//1.从方法参数中的注解对象拦截注解类  2.获取注解的数据
    public Object around2(ProceedingJoinPoint joinPoint, Pri pri) throws Throwable {
        String name=pri.name();
        System.out.println("用户的权限"+name);
        //判断list集合是否有权限
        if(list.contains(name)){
            Object result = joinPoint.proceed();
            return result;
        } else {
            System.out.println("该用户没有权限");
        }
        return null;
    }

//    @Around(value = "@annotation(com.jt.annotion.ss)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        Class<?> aClass = joinPoint.getTarget().getClass();
        String name = joinPoint.getTarget().getClass().getName();
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("耗时:"+(endTime-startTime));
        System.out.println("类型:"+aClass);//类型:class com.jt.service.UserServiceImpl
        System.out.println("类的名称(反射):"+name);//类的名称(反射):com.jt.service.UserServiceImpl  得到子实现类的名称
        System.out.println("类的名称:"+className);//类的名称:com.jt.service.UserService  得到父接口的名称
        System.out.println("方法名:"+methodName);//方法名:addUser
        System.out.println("参数:"+ Arrays.toString(args));//参数:[]
        return result;
    }
}
test
package com.jt;

import com.jt.aop.SpringAop1;
import com.jt.config.SpringConfig;
import com.jt.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestSpring {
    @Test
    public void TestDemo1(){
        ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean(UserService.class);
        userService.addUser();
        userService.deleteUser();
    }
}

十二. 补充点

1.为何要将SpringAOP这个切面类交给Spring容器管理

类似于动态代理

static Object	newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。

​ 细看这个API最后一个参数,InvocatioanHandler是一个回调函数接口,实际需要传入的是它的一个实现类的对象(后称呼为a),在代理对象执行目标方法的时候,会过来执行回调函数,而此时代理对象并不能直接调用这个接口的实现类的回调函数,而是代理对象驱动着a去调用invoke方法,所以正真干活的应该是a;

​ 回归正题:SpringAop这个类就相当于InvocatioanHandle这个接口(或者实现类),spring容器必须先拿到它的对象,才能去调用里面的相关方法,比如切入点表达式,如果满足才会创建代理对象,否则就不是代理对象.

上一篇:题解 尽梨了


下一篇:MySQL数据库引擎介绍、区别