• 别再张口闭口高并发海量数据了,Spring这些东西都会了吗?


    1. 为啥要用 Spring

    张三是一个编程小白,他每次在 service 层写代码都要自己 new 一堆 Dao 接口的实现类。

    1. public class ProjectServiceImpl implements ProjectService {
    2.     UserDao userDao = new UserDaoImpl();
    3.     ProjectSectionDao projectSessionDao = new ProjectSessionDaoImpl();
    4.     ProjectDao projectDao = new ProjectDaoImpl();
    5.     SupplyDao supplyDao = new SupplyDaoImpl();
    6.     .......
    7. }

    有一天正 new 着对象,张三心想:"我这一个 service 都需要 new 好多 Dao ,那如果有一堆 service ,那我不得花费好长时间?"

    "有没有一个工具类或者什么框架能帮我管理这些对象?我只需要配置一下,需要的时候它就能自动帮我 new 个对象出来?"

    张三陷入了深深的沉思之中。

    张三的室友李四也是一个编程小白。

    李四呢想给自己的小项目增加一个功能:记录方法执行的时间。结果他脑子一热竟然给所有的方法都增加了一堆打印方法:

    1. System.out.println("项目开始执行");
    2. // 开始时间
    3. long start = System.currentTimeMillis();
    4. // 业务代码
    5. // 结束时间
    6. long end = System.currentTimeMillis();
    7. // 计算执行时间
    8. System.out.printf("执行时间:%d 毫秒.", (end - start));

    过了半个小时,李四终于给项目中所有的方法都复制粘贴上了打印语句。他长舒一口气:"我真是个大聪明!"

    张三看了一眼李四的代码,连连鼓掌:"妙啊!咱们宿舍的技术大神!"

    旁边的王五实在忍不住了,对张三说:"妙个屁!最近的 Spring 框架课你俩是不是都没去?光顾着打游戏了?我都替你俩答了三次到了!"

    李四问王五:"这个Spring 框架学了有用吗?"

    王五:"不仅能解决张三说的管理对象的问题,还能帮你解决记录日志的问题。配置完 Spring ,你只需要定义一个切面类,根本不需要在一堆类上面复制粘贴一堆代码。"

    张三摸摸后脑勺笑着说:"原来 Spring 框架那么好用,我以后再也不逃课了。我这就去翻课本学习 Spring 框架去。"

    2. Spring 简介

    Spring 是一个轻量级的 Java 开发框架。Spring 的核心是控制反转(IOC)和面向切面编程(AOP)。

    Spring 主要有如下优点:

    3. 环境搭建

    1.创建 Maven 项目

    File -> New -> Project -> Maven

    2.引入依赖

    1. <dependencies>
    2.     <dependency>
    3.         <groupId>org.springframework</groupId>
    4.         <artifactId>spring-context</artifactId>
    5.         <version>5.2.16.RELEASE</version>
    6.     </dependency>
    7. </dependencies>

    3.创建接口和实现类

    UserService

    1. public interface UserService {
    2.     void print();
    3. }

    UserServiceImpl

    1. public class UserServiceImpl implements  UserService{
    2.     @Override
    3.     public void print() {
    4.         System.out.println("hello world");
    5.     }
    6. }

    4.创建配置文件

    applicationContext.xml

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4.        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    5.     
    6.     <bean id="userService" class="com.xxl.service.impl.UserServiceImpl"/>
    7. </beans>

    5.测试

    1. @Test
    2. public void testSpring(){
    3.     // 1、获取工厂
    4.     ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    5.     // 2、通过工厂类获得对象
    6.     UserService userService = (UserService)act.getBean("userService");
    7.     // 3.调用方法
    8.     userService.print();
    9. }

    测试结果:

    4. IOC

    4.1 IOC 简介

    IOC,全称 Inversion of Control,意思是控制反转。它是 Spring 框架中的一种思想。

    控制反转就是将对象的控制权从程序中的代码转移到了 Spring 的工厂,通过 Spring 的工厂完成对象的创建以及赋值。

    也就是说之前是我们自己 new 对象、给对象中的成员变量赋值。现在是让 Spring 来帮助我们创建对象、给成员变量赋值。

    4.2 Spring 核心内容描述

    1.配置文件

    Spring 的配置文件可以放到项目中的任意一个地方,也可以随意命名,但是建议使用:applicationContext.xml。

    你可以将这个配置文件看成一个装有一堆 bean 标签的容器。

    2.bean 标签

    Spring 工厂创建的对象,叫做 bean,所以一个 bean 标签代表一个对象。

    <bean id="userService" class="com.xxl.service.impl.UserServiceImpl"/>

    bean 标签中必须要有 class 属性,它的值是一个类的全限定名(包名+类名)。

    除了 class 属性,bean 标签还可以设置 id 、name 、scope属性。

    id:

    (1)id 必须以字母开头,相当于这个 bean 的身份证号,是唯一的。

    (2)如果这个 bean 只使用一次,id 可以省略不写。

    (3)如果这个 bean 需要被其他 bean 引用,或者这个 bean 要使用很多次,则必须要有 id 属性。

    (4)如果只配置 class 属性,Spring 框架会给每一个 bean 配置一个默认的 id:"全限定名#1"。

    例如:

     
    

    com.xxl.service.impl.UserServiceImpl#1

    name:

    name 相当于这个 bean 的别名,它可以配置多个,例如:

     
    

    scope:

    scope 属性可以控制简单对象的创建次数,它有两个值:

    1.singleton:每次只会创建唯一⼀个简单对象,默认值。

    2.prototype:每⼀次都会创建新的对象。

    例如:

     
    

    3.ApplicationContext

    ApplicationContext 是 Spring 的工厂,主要用来创建对象。

    Spring 通过读取配置文件创建工厂。

    因为 Spring 的工厂会占用大量内存,所以一个程序一般只会创建一个工厂对象。

    4.工厂常用方法

    (1)根据 id 获取对象

     
    
    UserService userService = (UserService)act.getBean("userService");

    (2)根据 id 和类名获取对象

     
    
    UserService userService = (UserService)act.getBean("userService",UserService.class);

    (3)只根据类名获取对象

     
    
    UserService userService = (UserService)act.getBean(UserService.class);

    (4)获取配置文件中所有 bean 标签的 id 值

     
    
    1. String[] beanDefinitionNames = act.getBeanDefinitionNames();
    2. for (String beanDefinitionName : beanDefinitionNames) {
    3.     System.out.println(beanDefinitionName);
    4. }

    结果:

    (5)判断是否存在指定 id 或者 name 的 bean

     
    
    act.containsBean("userService")

    (6)判断是否存在指定 id 的 bean,只能用来判断 id

     
    
    act.containsBeanDefinition("userService")

    5.创建对象

    Spring 是如何创建对象的呢?

    工厂和反射

    首先说下反射,我们可以通过一个类的全限定名获取 Class 对象,然后再通过 Class 实例化一个对象:

    1. Class serviceClass = Class.forName("com.xxl.service.impl.UserServiceImpl");
    2. UserService userService = (UserService)serviceClass.newInstance();

    Spring 配置文件中 bean 标签的 id 和类的全限定名一一对应,所以 Spring 工厂的 getBean 方法其实就是先根据 bean 的 id 获取该类的全限定名,然后再利用反射根据类的全限定名创建对象并返回。

    4.3 IOC 优点

    解耦

    说起解耦之前先说下耦合:耦合是指代码之间的关联性太强,我如果改了这一段代码,可能会影响到一堆代码。

    那创建对象哪里有耦合了?其实就是new关键字带来的耦合。

    如果你发现一个接口的实现类需要修改,你需要手动改动程序中的代码,比如修改 new 关键字后面的实现类,这样可能会影响到其他的代码。

    但是使用了 Spring 之后,我们只需要修改配置文件中 bean 标签的 class 属性对应的类的全限定名,不用修改程序中的代码,这样就做到了解耦。

    解耦就是解除不同代码之间的关联性、依赖性。

    5. DI

    DI 全称 Dependency Injection,意思是依赖注入,它是 IOC 的具体实现。

    依赖就是说我需要你,比如 Service 层依赖 Dao 层,注入就是赋值

    依赖注入:使用 Spring 的工厂和配置文件为一个类的成员变量赋值。

    没有使用 Spring 的依赖注入我们是这样赋值的:

    1. User user = new User();
    2. user.setName("张三");

    如果设置有误,就需要手动修改代码,代码耦合度较高,而依赖注入的出现就是为了解耦。

    Spring 的依赖注入包含两种方式:

    5.1 set 注入

    set 注入:Spring 调用 Set 方法通过配置文件为成员变量赋值。

    1.创建对象,为属性添加 set/get 方法

    1. public class User {
    2.     private String name;
    3.     private int age;
    4.     public String getName() {
    5.         return name;
    6.     }
    7.     public void setName(String name) {
    8.         this.name = name;
    9.     }
    10.     public int getAge() {
    11.         return age;
    12.     }
    13.     public void setAge(int age) {
    14.         this.age = age;
    15.     }
    16. }

    2.修改配置文件

    1. <bean id="user" class="com.xxl.model.User">
    2.   <property name="name" value="知否君" />
    3.   <property name="age" value="18" />
    4. </bean>

    3.测试

    1. // 1、获取工厂
    2. ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    3. // 2、通过工厂类获得对象
    4. User user = (User)act.getBean("user");
    5. System.out.println("姓名:"+user.getName());
    6. System.out.println("性别:"+user.getAge());

    测试结果:

    从上面的例子可以看出 Set 注入就是在 property 标签中为属性赋值。spring 可以为 JDK 内置的数据类型进行赋值,也可以为用户自定义的数据类型进行赋值。

    5.1.1 JDK 内置数据类型

    1.基本类型

     
    
    1. <property name="name" value="知否君" />
    2. <property name="age" value="18" />

    2.List 集合

     
    
    1. <property name="phones">
    2.   <list>
    3.     <value>15799999918</value>
    4.     <value>15788888819</value>
    5.     <value>15766666620</value>
    6.   </list>
    7. </property>

    3.Set 集合

     
    
    1. <property name="phones">
    2.   <set>
    3.     <value>15799999918</value>
    4.     <value>15788888819</value>
    5.     <value>15766666620</value>
    6.   </set>
    7. </property>

    4.Map 集合

     
    
    1. <property name="mapInfo">
    2.     <map>
    3.         <entry>
    4.             <key><value>name</value></key>
    5.             <value>知否君</value>
    6.         </entry>
    7.         <entry>
    8.             <key><value>age</value></key>
    9.             <value>23</value>
    10.         </entry>
    11.     </map>
    12. </property>

    5.数组

     
    
    1. <property name="phones">
    2.     <list>
    3.         <value>15799999918</value>
    4.         <value>15788888819</value>
    5.         <value>15766666620</value>
    6.     </list>
    7. </property>

    6.Properites

     
    
    1. <property name="prop">
    2.     <props>
    3.         <prop key="key1">value1</prop>
    4.         <prop key="key2">value2</prop>
    5.     </props>
    6. </property>

    5.1.2 用户自定义数据类型

    1.为成员变量添加 set/get 方法

     
    
    1. public class UserServiceImpl implements UserService {
    2.     
    3.     private UserDao userDao;
    4.     public UserDao getUserDao() {
    5.         return userDao;
    6.     }
    7.     public void setUserDao(UserDao userDao) {
    8.         this.userDao = userDao;
    9.     }
    10.     @Override
    11.     public void print() {
    12.         userDao.print();
    13.     }
    14. }

    2.bean 标签使用 ref 属性

     
    
    1. <bean id="userDao" class="com.xxl.dao.impl.UserDaoImpl" />
    2. <bean id="userService" class="com.xxl.service.impl.UserServiceImpl">
    3.    <property name="userDao" ref="userDao"/>
    4. </bean>

    3.测试

     
    
    1. @Test
    2. public void testSpring(){
    3.     // 1、获取工厂
    4.     ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    5.     // 2、通过工厂类获得对象
    6.     UserService userService = (UserService)act.getBean("userService");
    7.     // 3.调用方法
    8.     userService.print();
    9. }

    测试结果:

    解释:

    上面的例子中,因为 userDao 是 userService 的一个成员变量,所以在配置文件中需要使用 property 标签,ref 指向了 userDao 这个对象,然后调用 userDao 的 set 方法为 userDao 赋值。

    4.自动注入

    我们还可以使用 bean 标签的 autowire 属性为自定义变量自动赋值。当类中引用类型的属性名和 bean 标签的 id 值相同时,我们可以使用 byName。例如:

     
    
    1. <bean id="userDao" class="com.xxl.dao.impl.UserDaoImpl" />
    2. <bean id="userService" autowire="byName" class="com.xxl.service.impl.UserServiceImpl" />

    当类中引用类型的全限定名和 bean 标签的 class 属性的值相同,或者是子类、实现类,我们可以使用 byType。例如:

     
    
    1. <bean id="userDao" class="com.xxl.dao.impl.UserDaoImpl" />
    2. <bean id="userService" autowire="byType" class="com.xxl.service.impl.UserServiceImpl" />

    5.2 构造注入

    构造注入:Spring 调用构造方法通过配置文件为成员变量赋值。

    1.为类添加构造方法

     
    
    1. public class User {
    2.     private String name;
    3.     private int age;
    4.     public User(String name, int age) {
    5.         this.name = name;
    6.         this.age = age;
    7.     }
    8.     public String getName() {
    9.         return name;
    10.     }
    11.     public void setName(String name) {
    12.         this.name = name;
    13.     }
    14.     public int getAge() {
    15.         return age;
    16.     }
    17.     public void setAge(int age) {
    18.         this.age = age;
    19.     }
    20. }

    2.修改配置文件

    在 bean 标签中使用 constructor-arg 标签。

     
    
    1. <bean class="com.xxl.model.User">
    2.     <constructor-arg value="张三"/>
    3.     <constructor-arg value="18"/>
    4. </bean>

    3.测试

     
    
    1. @Test
    2. public void testSpring(){
    3.     // 1、获取工厂
    4.     ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    5.     // 2、通过工厂类获得对象
    6.     User user= (User)act.getBean(User.class);
    7.     System.out.println("姓名:"+user.getName());
    8.     System.out.println("年龄:"+user.getAge());
    9. }

    测试结果:

    5.3 注入总结

    注入就是通过 Spring 的配置文件为类的成员变量赋值。在实际开发中,我们一般采用 Set 方式为成员变量赋值。

    6. Bean 的生命周期

    Bean 生命周期指的就是由 Spring 管理的对象从创建到销毁的过程,和人生老病死的过程一样。

    它主要分为三个阶段:创建 --> 初始化 --> 销毁

    6.1 创建阶段

    Spring 工厂创建对象的方式分两类:

    1. singleton 模式

    当 scope 属性为 singleton ,创建 Spring 工厂的同时创建所有单例对象。

    例如:

    新建 User 类:

     
    
    1. public class User {
    2.     String name;
    3.     int age;
    4.     public User() {
    5.         System.out.println("调用User的构造方法");
    6.     }
    7.     public String getName() {
    8.         return name;
    9.     }
    10.     public void setName(String name) {
    11.         this.name = name;
    12.     }
    13.     public int getAge() {
    14.         return age;
    15.     }
    16.     public void setAge(int age) {
    17.         this.age = age;
    18.     }
    19.     @Override
    20.     public String toString() {
    21.         return "User{" +
    22.                 "name='" + name + ''' +
    23.                 ", age=" + age +
    24.                 '}';
    25.     }
    26. }

    spring 配置文件注册 bean :

     
    
    1. <bean id="user" class="com.xxl.model.User">
    2.     <property name="name" value="知否君"/>
    3.     <property name="age" value="23"/>
    4. </bean>

    测试:

     
    
    1. @Test
    2.     public void testSpring(){
    3.         ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    4.     }

    执行结果:

    我们发现当创建 Spring 工厂的同时就会调用对象的构造方法。因为 spring 中 bean 默认的 scope 就是 singleton ,所以创建工厂的同时默认就会创建多个单例对象。

    如果想修改创建单例对象的方式为获取的时候才创建,只需要在 bean 标签上面添加如下属性:

     
    
    lazy-init="true"

    例如:

     
    
    1. <bean id="user" class="com.xxl.model.User" lazy-init="true">
    2.     <property name="name" value="知否君"/>
    3.     <property name="age" value="23"/>
    4. </bean>

    1. prototype 模式

    只有获取对象的时候才会创建对象。

    修改 bean 标签的 scope 属性:

     
    
    1. <bean id="user" class="com.xxl.model.User" scope="prototype">
    2.     <property name="name" value="知否君"/>
    3.     <property name="age" value="23"/>
    4. </bean>

    测试:

     
    
    1. @Test
    2.     public void testSpring(){
    3.         ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    4.         Object user = act.getBean("user");
    5.         System.out.println(user);
    6.     }

    执行结果:

    通过上面的例子我们发现只有当执行 getBean() 方法的时候才会调用该类的构造方法。

    6.2 初始化阶段

    spring 中 bean 的初始化操作指的是在创建对象的时候完成一些附加的功能。bean 的初始化操作有两种实现方式:

    1.实现 InitializingBean 接口

     
    
    1. public class 类名 implements InitializingBean {
    2.     public void afterPropertiesSet(){
    3.        // 初始化方法操作
    4.     }
    5. }

    例如:

     
    
    1. public class User implements InitializingBean {
    2.     String name;
    3.     int age;
    4.     public String getName() {
    5.         return name;
    6.     }
    7.     public void setName(String name) {
    8.         this.name = name;
    9.     }
    10.     public int getAge() {
    11.         return age;
    12.     }
    13.     public void setAge(int age) {
    14.         this.age = age;
    15.     }
    16.     @Override
    17.     public String toString() {
    18.         return "User{" +
    19.                 "name='" + name + ''' +
    20.                 ", age=" + age +
    21.                 '}';
    22.     }
    23.     // 初始化操作
    24.     @Override
    25.     public void afterPropertiesSet(){
    26.             this.name = "张无忌";
    27.             this.age = 30;
    28.     }
    29. }

    测试:

     
    
    1. @Test
    2.     public void testSpring(){
    3.         ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    4.         Object user = act.getBean("user");
    5.         System.out.println(user);
    6.     }

    执行结果:

    2.通过创建普通方法完成初始化

    在 User 类中创建一个方法

     
    
    1. // 初始化方法
    2.   public void initMethod() {
    3.       this.name = "张无忌";
    4.   }

    在配置文件中配置 init-method 属性

     
    
    1. <bean id="user" class="com.xxl.model.User" init-method="initMethod" >
    2.     <property name="name" value="知否君"/>
    3.     <property name="age" value="23"/>
    4. </bean>

    测试:

     
    
    1. @Test
    2.     public void testSpring(){
    3.         ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    4.         Object user = act.getBean("user");
    5.         System.out.println(user);
    6.     }

    执行结果:

    我们发现该初始化方法在创建对象之后修改了 user 对象的名字。

    总结

    初始化方法修改了注入的值,所以初始化方法一定在注入之后执行。

    6.3 销毁阶段

    Spring 销毁对象前,会调用对象的销毁方法,完成销毁操作。

    Spring 什么时候销毁所创建的对象?当 Spring 工厂关闭时,Spring 工厂会调用我们自定义的销毁方法。

    销毁方法的定义有两种方式:

    1.实现DisposableBean接口

    1. public class 类名 implements DisposableBean {
    2.     // 销毁操作
    3.     @Override
    4.     public void destroy(){
    5.         // 销毁操作业务
    6.     }
    7. }

    2.创建普通方法

    在 User 类中创建一个方法

    1. // 销毁方法
    2.   public void destroyMethod() {
    3.      // 销毁操作业务
    4.   }

    在配置文件中配置 destroy-method 属性

    1. <bean id="user" class="com.xxl.model.User" destroy-method="destroyMethod">
    2.       <property name="name" value="知否君"/>
    3.       <property name="age" value="23"/>
    4.   </bean>

    7. Bean 的后置处理

    Spring 工厂创建完对象后如果还想对对象干点别的事情,除了初始化阶段,还可以采用Bean的后置处理。

    Bean 的后置处理:对 Spring 工厂创建的对象进行二次加工处理,就是创建完对象后再干点别的事。

    Bean 后置处理的流程:

    1.实现 BeanPostProcessor 接口

     
    
    1. public class BeanProcessor implements BeanPostProcessor {
    2.     @Override
    3.     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    4.         System.out.println("后置bean:before 方法");
    5.         return bean;
    6.     }
    7.     
    8.     @Override
    9.     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    10.         System.out.println("后置bean:after 方法");
    11.         if (bean instanceof User) {
    12.             User user = (User) bean;
    13.             user.setName("亚里士多德");
    14.             return user;
    15.         }
    16.         return bean;
    17.     }
    18. }

    2.配置文件添加 bean

    <bean id="beanProcessor" class="com.xxl.config.BeanProcessor"/>

    3.测试

    1. @Test
    2.     public void testSpring(){
    3.         ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    4.         Object user = act.getBean("user");
    5.         System.out.println(user);
    6.     }

    执行结果:

    前面我们学习了对象的初始化方法,那么初始化方法和 Bean 的后置处理的执行顺序是什么?

    我们来修改一下 User 类,测试一下:

    1. public class User implements InitializingBean {
    2.     String name;
    3.     int age;
    4.     public String getName() {
    5.         return name;
    6.     }
    7.     public void setName(String name) {
    8.         this.name = name;
    9.     }
    10.     public int getAge() {
    11.         return age;
    12.     }
    13.     public void setAge(int age) {
    14.         this.age = age;
    15.     }
    16.     @Override
    17.     public String toString() {
    18.         return "User{" +
    19.                 "name='" + name + ''' +
    20.                 ", age=" + age +
    21.                 '}';
    22.     }
    23.     @Override
    24.     public void afterPropertiesSet(){
    25.         System.out.println("初始化方法");
    26.     }
    27. }

    测试执行顺序:

    其实在实际开发中,我们很少对 Spring 工厂创建的对象进行初始化操作,一般是采用 Bean 的后置处理的方式来加工对象。

    BeanPostProcessor 接口有两个方法,这里简称 before 和 after 方法。

    这两个方法都是先获取 Spring 创建的对象,然后再对其加工,加工完成后再交给 Spring。

    因为这两个方法的作用一样,所以我们一般采用其中的一个方法,这里建议采用 after 方法。

    从上面的例子中我们得到了Spring 操作 bean 的顺序:

    8. 代理设计模式

    8.1 为啥要用代理设计模式?

    咱们先来看一个需求:在所有方法的执行前后输出一段日志。

    程序小白可能会这样写:

    接口:

    1. public interface CalculateService {
    2.     // 加法
    3.     int add(int a,int b);
    4.     // 减法
    5.     int sub(int a,int b);
    6. }

    实现类:

    1. public class CalculateServiceImpl implements CalculateService {
    2.     @Override
    3.     public int add(int a, int b) {
    4.         System.out.println("方法执行前打印");
    5.         int result = a + b;
    6.         System.out.println("方法执行后打印");
    7.         return result;
    8.     }
    9.     @Override
    10.     public int sub(int a, int b) {
    11.         System.out.println("方法执行前打印");
    12.         int result = a - b;
    13.         System.out.println("方法执行后打印");
    14.         return result;
    15.     }
    16. }

    但是这样写有 3 个问题:

    • 1.代码混乱:业务代码和非业务代码混在一起,看着太乱了

    • 2.代码重复:如果有多个方法,那就要写一堆输出日志的代码片段,吃力不讨好。

    • 3.代码耦合:如果非业务代码(日志打印)要做修改,那所有相关的业务方法都要改一遍,代码耦合度太高。

    那有什么解决办法呢?使用代理

    生活中有关代理的例子无处不在,例如:一些大学可以面向全球招生,所以会衍生很多留学中介,这些中介可以帮学校招生。

    所以中介的作用就是帮助雇主做事,有了中介,雇主变得很轻松。而在 java 开发中,也存在这样的代理关系,它的专业术语是代理设计模式。

    代理设计模式可以很好解决上面开发中遇到的三个问题,帮助我们简化代码、提高工作效率。

    8.2 代理设计模式

    代理设计模式:通过代理类为目标类做一些额外(非业务)的功能。

    专业名词解释:

    • 1.目标类(原始类):指的是完成业务的核心类,一般指的是 service 层的各种实现类。

    • 2.目标方法(原始方法):目标类中的方法是目标方法(原始方法)。

    • 3.额外功能(附加功能):打印日志等非业务功能。

    代理设计模式开发步骤:

    • 1. 代理类和目标类实现相同的接口

    • 2. 代理类中除了要调用目标类的方法实现业务功能,还要实现额外功能。

    例如:

    1. // 接口
    2. public interface CalculateService {
    3.   业务方法
    4. }
    5. // 目标类
    6. public CalculateServiceImpl implements CalculateService {
    7.   业务方法
    8. }
    9. // 代理类:要实现目标类相同的接口
    10. public CalculateServiceProxy implements CalculateService {
    11.  // 业务方法
    12.  // 额外功能
    13. }

    8.3 静态代理

    静态代理:给每一个目标类手动开发一个代理类。

    例如:

    1. public interface CalculateService {
    2.    // 加法
    3.   int add(int a,int b);
    4.    // 减法
    5.   int sub(int a,int b);
    6. }
    7. // 目标类
    8. public CalculateServiceImpl implements CalculateService {
    9.    @Override
    10.     public int add(int a, int b) {
    11.         int result = a + b;
    12.         return result;
    13.     }
    14.     @Override
    15.     public int sub(int a, int b) {
    16.         int result = a - b;
    17.         return result;
    18.     }
    19. }
    20. // 代理类:要实现目标类相同的接口
    21. public CalculateServiceProxy implements CalculateService {
    22.   private CalculateService calculateService = new CalculateServiceImpl();
    23.   @Override
    24.     public int add(int a, int b) {
    25.         System.out.println("方法执行前打印");
    26.         int result = calculateService.add(a,b);
    27.         System.out.println("方法执行后打印");
    28.         return result;
    29.     }
    30.     @Override
    31.     public int sub(int a, int b) {
    32.         System.out.println("方法执行前打印");
    33.         int result = calculateService.sub(a,b);
    34.         System.out.println("方法执行后打印");
    35.         return result;
    36.     }
    37. }

    通过上面的例子我们发现静态代理也存在很多问题:

    • 1.如果存在很多目标类,我们就要手动创建一堆代理类,太繁琐。

    • 2.代理类中混杂着目标类方法和额外功能,代码耦合度高。

    那有没有这样一种代理模式?

    • 1.目标类和代理类互不干扰

    • 2.代码耦合度低,便于维护

    有的,动态代理闪亮登场!

    8.4 动态代理

    动态代理:也是通过代理类为目标类做一些额外的功能,但是不用手动写一堆代理类,而是动态地为目标类创建代理类。

    开发流程:

    1. 引入依赖

    1. <dependency>
    2.     <groupId>org.springframework</groupId>
    3.     <artifactId>spring-context</artifactId>
    4.     <version>5.2.16.RELEASE</version>
    5. </dependency>
    6. <dependency>
    7.     <groupId>org.aspectj</groupId>
    8.     <artifactId>aspectjrt</artifactId>
    9.     <version>1.9.5</version>
    10. </dependency>
    11. <dependency>
    12.     <groupId>org.aspectj</groupId>
    13.     <artifactId>aspectjweaver</artifactId>
    14.     <version>1.9.5</version>
    15. </dependency>

    这里我们主要是引入了 aspectj 这个技术,aspectj 是 spring 社区中非常流行的基于动态代理技术的框架。

    1. 创建目标类和目标方法

    接口:

    1. public interface CalculateService {
    2.     // 加法
    3.     int add(int a,int b);
    4.     // 减法
    5.     int sub(int a,int b);
    6. }

    实现类(目标类):

    1. public class CalculateServiceImpl implements CalculateService {
    2.      @Override
    3.     public int add(int a, int b) {
    4.         int result = a + b;
    5.         System.out.println("加法操作。。。");
    6.         return result;
    7.     }
    8.     @Override
    9.     public int sub(int a, int b) {
    10.         int result = a - b;
    11.         System.out.println("减法操作。。。");
    12.         return result;
    13.     }
    14. }

    3.在 spring 配置文件中注册 bean

    <bean id="calculateService"  class="com.xxl.service.impl.CalculateServiceImpl" />

    4.实现额外功能

    这里我们需要创建一个类实现 MethodInterceptor 接口:

    1. /**
    2.  * @Desc: 动态代理完成非业务功能
    3.  * @Author: 知否技术
    4.  * @date: 下午8:49 2022/5/4
    5.  */
    6. public class PrintLog implements MethodInterceptor {
    7.     @Override
    8.     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    9.         System.out.println("在目标方法执行之前打印。。。。。");
    10.         // 执行目标方法
    11.         Object object = methodInvocation.proceed();
    12.         System.out.println("在目标方法执行之后打印。。。。。");
    13.         return object;
    14.     }
    15. }

    5.注册完成额外功能的 bean

    <bean id="printLog" class="com.xxl.aop.PrintLog" />
    1. 定义切入点

    1. <aop:config>
    2.   <aop:pointcut id="pc" expression="execution(* * (..))"/>
    3. aop:config>
    1. 组装切入点和额外功能

     
    
    1. <!--切入点:给哪些方法加入额外功能-->
    2. <aop:config>
    3.   <aop:pointcut id="pc" expression="execution(* * (..))"/>
    4.   <aop:advisor advice-ref="printLog" pointcut-ref="pc"/>
    5. </aop:config>

    8.测试

    1. @Test
    2.   public void testSpring() {
    3.       // 1、获取工厂
    4.       ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    5.       // 2、通过工厂类获得对象
    6.       CalculateService calculateService = (CalculateService) act.getBean("calculateService");
    7.       // 3.调用方法
    8.       int result = calculateService.add(12);
    9.       System.out.println("result:" + result);
    10.   }

    讲解:

    1.上面的例子中我们定义了一个 PrintLog 打印日志的类,并实现了 MethodInterceptor 接口的 invoke 方法。invoke 方法里面实现了在目标方法执行前后打印日志的功能。

    2.invoke 方法的返回值就是原始方法的返回值,上个例子中的原始方法就是 add 方法。

    3.aop:config 这个标签用来配置切入点和额外功能。上面例子中额外功能就是在要执行的方法前后打印日志,而切入点就是额外功能要作用的位置:比如某些类上或者某些方法上。

    4.execution(* * (..)) 是切入点表达式,表示作用在所有类的所有方法上,这个后面会讲。

    5.上面的例子表示:你无论执行哪个方法,这个方法的前面和后面都会打印一段日志。

    8.5 动态代理实现原理

    我们通过 spring 的工厂获取的对象,其实是通过动态代理技术创建的代理类。那这个代理类在哪里?

    当程序运行的时候,spring 框架通过动态字节码技术在 JVM 内存中为目标类创建代理类。当程序运行结束的时候,这个代理类就会随之消亡。

    所以使用动态代理不需要手动创建多个代理类。

    9. AOP

    9.1 AOP 概念

    AOP: 全称 Producer Oriented Programing,即面向切面编程。

    那啥是面向切面编程?其实说白了还是 Spring 的动态代理,通过代理类为原始类增加一些 额外功能(例如打印等)。

    那啥是切面?

    切面 = 切入点 + 额外功能。

    切入点:额外功能作用的位置,在哪些类哪些方法上。

    额外功能作用在不同的类上面,我们都知道点连接起来构成面,所以不同的切入点连接起来构成了切面,这个切面就像刀切西瓜一样切在不同的类上面,所以额外功能就对这些类中的方法起了作用。

    9.2 AOP 底层实现原理

    AOP 的底层还是使用 Spring 的动态代理技术创建代理类对象。

    动态代理的方式分为两种:

    • 基于接口实现动态代理:JDK 动态代理

    • 基于继承实现动态代理:Cglib 动态代理

    9.2.1 JDK 动态代理

    创建代理对象的三个元素:

    • 1.原始对象

    • 2.额外功能

    • 3.原始对象实现的接口

    代码格式:

    Proxy.newPorxyInstance(classloader,interfaces,invocationHandler)

    讲解:

    (1)classloader:叫做类加载器,它可以用来创建代理对象。

    创建方式:

    类.class.getClassLOader()

    (2)interfaces:原始对象实现的接口

    创建方式

    接口.getClass().getInterfaces()

    (3)invocationHandler:额外功能

    创建方式:

    1. InvocationHandler handler = new InvocationHandler() {
    2.             @Override
    3.             public Object invoke(Object proxy, Method methodObject[] args) throws Throwable {
    4.                 System.out.println("---- 方法执行前打印 ----");
    5.                 // 执行原始方法
    6.                 Object ret = method.invoke(caculateService, args);
    7.                 System.out.println("---- 方法执行后打印 ----");
    8.                 return ret;
    9.             }
    10.         };

    完整代码:

    1. @Test
    2. public void testJDKProxy() {
    3.     // 1. 原始对象
    4.     CalculateService calculateService = new CalculateServiceImpl();
    5.     // 2. JDK 动态代理:包含额外功能
    6.     InvocationHandler handler = new InvocationHandler() {
    7.         @Override
    8.         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    9.             System.out.println("---- 方法执行前打印 ----");
    10.             // 执行原始方法
    11.             Object result = method.invoke(calculateService, args);
    12.             System.out.println("---- 方法执行后打印 ----");
    13.             return result;
    14.         }
    15.     };
    16.     // 3. 代理类
    17.     CalculateService calService = (CalculateService) Proxy.
    18.             newProxyInstance(CalculateService.class.getClassLoader(),
    19.                     calculateService.getClass().getInterfaces(),
    20.                     handler);
    21.     // 4. 执行方法
    22.     int result = calService.add(12);
    23.     System.out.println("result:" + result);
    24. }

    测试结果:

    9.2.2 Cglib 动态代理

    CGlib 创建动态代理的原理:原始类作为父类,代理类作为子类,通过继承关系创建代理类。

    代码格式:

    1. Enhancer enhancer = new Enhancer();
    2. enhancer.setClassLoader(classLoader);
    3. enhancer.setSuperclass(calculateService);
    4. enhancer.setCallback(interceptor);

    讲解:

    (1)classLoader:类加载器(了解即可)

    (2)Superclass:父类,就是原始类

    (3)interceptor:额外功能

    1. MethodInterceptor interceptor = new MethodInterceptor() {
    2.             @Override
    3.             public Object intercept(Object o, Method methodObject[] args, MethodProxy methodProxy) throws Throwable {
    4.             System.out.println("---- 方法执行前打印 ----");
    5.             // 执行原始方法
    6.             Object result = method.invoke(calculateService, args);
    7.             System.out.println("---- 方法执行后打印 ----");
    8.             return result;
    9.             }
    10.         };

    完整代码:

    1. @Test
    2.     public void testCglibProxy() {
    3.         // 1. 原始对象
    4.         CalculateService calculateService = new CalculateServiceImpl();
    5.         // 2. Cglib 动态代理:包含额外功能
    6.         MethodInterceptor interceptor = new MethodInterceptor() {
    7.             @Override
    8.             public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    9.                 System.out.println("---- 方法执行前打印 ----");
    10.                 // 执行原始方法
    11.                 Object result = method.invoke(calculateService, args);
    12.                 System.out.println("---- 方法执行后打印 ----");
    13.                 return result;
    14.             }
    15.         };
    16.         Enhancer enhancer = new Enhancer();
    17.         enhancer.setClassLoader(CalculateService.class.getClassLoader());
    18.         enhancer.setSuperclass(calculateService.getClass());
    19.         enhancer.setCallback(interceptor);
    20.         // 3. 创建代理类
    21.         CalculateService calService = (CalculateService)enhancer.create();
    22.         // 4. 执行方法
    23.         int result = calService.add(34);
    24.         System.out.println("result:" + result);
    25.     }

    执行结果:

    9.2.3 Spring 如何创建代理对象?

    Spring 是如何为原始对象创建目标对象的呢?是通过 BeanPostProcessor。

    前面我们讲过 BeanPostProcessor 可以对对象进行二次加工,所以可以用来创建代理对象。

    Spring 创建代理对象的流程:

    1. 实现 BeanPostProcessor 接口

     
    
    1. /**
    2.  * @Desc: 后置bean创建代理对象
    3.  * @Author: 知否技术
    4.  * @date: 上午11:59 2022/5/5
    5.  */
    6. public class ProxyBeanPostProcessor implements BeanPostProcessor {
    7.     @Override
    8.     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    9.         return bean;
    10.     }
    11.     @Override
    12.     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    13.         InvocationHandler handler = (proxy, method, args) -> {
    14.             System.out.println("--- 方法执行前打印6666666---");
    15.             Object ret = method.invoke(bean, args);
    16.             System.out.println("--- 方法执行后打印7777777---");
    17.             return ret;
    18.         };
    19.         return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler);
    20.     }
    21. }
    1. 注册 bean

     
    
    1. <bean id="calculateService"  class="com.xxl.service.impl.CalculateServiceImpl" />
    2. <bean id="proxyBeanPostProcessor" class="com.xxl.aop.ProxyBeanPostProcessor"/>
    1. 测试

     
    
    1. @Test
    2.     public void testSpring() {
    3.         // 1、获取工厂
    4.         ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    5.         // 2、通过工厂类获得对象
    6.         CalculateService calculateService = (CalculateService) act.getBean("calculateService");
    7.         // 3.调用方法
    8.         int result = calculateService.sub(72);
    9.         System.out.println("result:" + result);
    10.     }

    9.3 基于注解开发 AOP

    开发流程:

    1. 开发切面类

    1. @Aspect
    2. public class TestAspect {
    3.     // 前置通知:方法执行前添加额外功能
    4.     @Before("execution(* *(..))")
    5.     public void beforePrint(){
    6.         System.out.println("------before: 方法执行前打印~");
    7.     }
    8.     
    9.     //后置通知:方法执行后添加额外功能
    10.     @After("execution(* *(..))")
    11.     public void afterPrint(){
    12.         System.out.println("------after: 方法执行前打印~");
    13.     }
    14.     // 环绕通知:方法执行前后添加额外功能
    15.     @Around("execution(* *(..))")
    16.     public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    17.         System.out.println("方法执行前打印~");
    18.         Object result = joinPoint.proceed();
    19.         System.out.println("方法执行后打印~");
    20.         return result;
    21.     }
    22. }
    1. 配置切面类和扫描注解

    1. <bean id="testMyAspect"  class="com.xxl.aop.TestAspect" />
    2. <aop:aspectj-autoproxy/>

    1. 测试

    1. @Test
    2.   public void testSpring() {
    3.       // 1、获取工厂
    4.       ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    5.       // 2、通过工厂类获得对象
    6.       calculateService calculateService = (CalculateService) act.getBean("calculateService");
    7.       // 3.调用方法
    8.       int result = calculateService.add(1001);
    9.       System.out.println("result:" + result);
    10.   }

    讲解:

    1.我们新建了一个 TestMyAspect 类,然后添加 @Aspect 注解,表示这是一个切面类,专门用来完成非业务功能的。

    2.在这个类中,我们创建了三个方法,其中 @Before 注解标注的方法表示在目标方法操作前执行。@After 注解标注的方法表示在目标方法操作后执行。@Around 注解标注的方法表示在目标方法操作前后执行。

    3.在实际开发中一般使用 @Around 注解标注的方法完成非业务功能。

    4.我们新建了这个切面类,但是 spring 不知道啊,所以需要在 Spring 的配置文件中注册一下 bean。

    5.现在 Spring 工厂能够管理这个类了,但是 Spring 不知道他是切面类啊!所以需要配置一下扫描注解的标签。

    6.然后通过 Spring 获取创建的类,我们获取的其实是 Spring 通过后置 Bean 加工后的代理类。

    切入点复用

    我们可以在切面类中定义⼀个方法,方法上面标注 @Pointcut 注解。然后就可以重复使用切入点表达式了:

    1. @Aspect
    2. public class TestAspect {
    3.     @Pointcut("execution(* *(..))")
    4.     public void myPointcut() {}
    5.     
    6.     // 环绕通知:方法执行前后添加额外功能
    7.     @Around(value = "myPointcut()")
    8.     public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    9.         System.out.println("方法执行前打印~");
    10.         Object result = joinPoint.proceed();
    11.         System.out.println("方法执行后打印~");
    12.         return result;
    13.     }
    14. }

    9.4 切入点表达式

    切入点:额外功能加入的位置。

    1. <aop:pointcut id="pc" expression="execution(* * (..))"/>
    2. 复制代码
    • execution():切入点函数

    • (* * (..)):切入点表达式

     
    
    1. public int addint a, int b)
    2.    * * (..)

    第一个 * 表示方法的修饰符和返回值 第二个 * 是方法名 .. 表示方法中的参数

    1.(包.类.方法)切入点:

    1. 修饰符-返回值 包.类.方法(参数)
    2. expression="execution(* com.xxl.service.caculateServiceImpl.add(..))"

    2.指定切入点为某个包下的所有类中的所有方法:

    1. 修饰符-返回值 包.类.方法(参数)
    2. expression="execution(* com.xxl.service.*.*(..))"

    3.@annotation

    作用:用于匹配当前执行方法持有指定注解的方法,并为之加入额外的功能。

    例如我们自定义了一个注解:NoToken

    1. @Target(ElementType.METHOD)
    2. @Retention(RetentionPolicy.RUNTIME)
    3. public @interface NoToken {
    4. }

    方法中添加自定义注解:

    1. @Override
    2. @NoToken
    3. public int add(int a, int b) {
    4.     int result = a + b;
    5.     System.out.println("加法操作。。。");
    6.     return result;
    7. }

    然后我们要为包含 NoToken 注解的方法加入额外功能:

    1. @Aspect
    2. public class TestAspect {
    3.    // 环绕通知:方法执行前后添加额外功能
    4.     @Around("@annotation(com.xxl.annotion.NoToken)")
    5.     public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    6.         System.out.println("包含 NoToken 注解--------------");
    7.         Object result = joinPoint.proceed();
    8.         return result;
    9.     }
    10. }

    测试:

    1. @Test
    2. public void testSpring() {
    3.     // 1、获取工厂
    4.     ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    5.     // 2、通过工厂类获得对象
    6.     CalculateService calculateService = (CalculateService) act.getBean("calculateService");
    7.     // 3.调用方法
    8.     int result1 = calculateService.add(991);
    9.     System.out.println("-----------------------");
    10.     int result2 = calculateService.sub(991);
    11.     System.out.println("result1:" + result1);
    12.     System.out.println("result2:" + result2);
    13. }

    10. Spring 相关注解

    在讲注解之前我们先看一下啥是注解。

    代码格式:@英文单词,例如:

    作用位置:常用在类上或者方法上

    用处:简化代码、完成某些功能

    所以 Spring 引入注解也是为了简化我们的代码,通过使用简单的注解完成一些功能。

    10.1 创建对象相关注解

    我们前面在学 IOC 的时候知道如果想让 Spring 创建对象,必须要在配置文件中写 bean 标签。

    1. <bean id="calculateService"  class="com.xxl.service.impl.CalculateServiceImpl" />
    2. <bean id="proxyBeanPostProcessor" class="com.xxl.aop.ProxyBeanPostProcessor"/>
    3. <bean id="testMyAspect"  class="com.xxl.aop.TestAspect" />
    4. ......

    可是如果想让 Spring 管理一堆对象,我们就要写一堆 bean 标签。所以 Spring 为了简化代码,提供了一些与创建对象相关的注解。

    10.1.1 @Component

    作用:替换 bean 标签,用来创建对象。就是在类上面加了这个注解,就不用在配置文件上写 bean 标签了。

    位置:类上面

    id 属性:默认首单词首字母小写。

    1. // id 属性:默认首单词首字母小写。
    2. @Component("user")
    3. public class User{
    4. }

    10.1.2 @Component 衍生注解

    我们在开发程序的时候一般会将程序分层,例如分为控制层(controller),业务层(service),持久层(dao)。

    但是 @Component 注解并不能区分这些类属于那些层,所以 Spring 提供了以下衍生注解:

    1.@Controller:表示创建控制器对象

    1. @Controller
    2. public class UserController {
    3.     
    4. }

    2.@Service:表示创建业务层对象

    1. @Service
    2. public class UserServiceImpl implements UserService {
    3. }

    3.@Repository:表示创建持久层对象

    1. @Repository
    2. public class UserDaoImpl implements UserDao {
    3. }

    这三个注解的作用和 @Component 的作用一样,都是用来创建对象。

    10.1.3 @Scope

    我们知道 Spring 工厂创建的对象默认都是单例的,也就是 bean 标签中 scope 属性默认是 singleton。

    @Scope 注解可以用来修改创建对象的 scope 属性。

    默认:也就是说你不写 @Scope 注解,默认就是 singleton,所以可以省略。

    1. @Component
    2. // 可以省略不写
    3. @Scope("singleton")
    4. public class User {
    5.     
    6. }

    修改多例:

    1. @Component
    2. @Scope("prototype")
    3. public class User {
    4.     
    5. }

    10.1.4 生命周期相关注解

    1.@PostConstruct

    初始化方法注解,作用在方法上。用来替换 bean 标签的 init-method 属性。

    例如:

    1. @Component
    2. public class User {
    3.     private String name;
    4.     public String getName() {
    5.         return name;
    6.     }
    7.     public void setName(String name) {
    8.         this.name = name;
    9.     }
    10.     @Override
    11.     public String toString() {
    12.         return "User{" +
    13.                 "name='" + name + ''' +
    14.                 '}';
    15.     }
    16.     // 初始化方法
    17.     @PostConstruct
    18.     public void init(){
    19.         this.name = "王小波";
    20.     }
    21. }

    测试:

    1. @Test
    2. public void testSpring() {
    3.     // 1、获取工厂
    4.     ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    5.     // 2、通过工厂类获得对象
    6.     User user = (User) act.getBean("user");
    7.     System.out.println(user);
    8. }

    报错了!这是为什么?

    我们先看报错内容:

    No bean named 'user'

    也就是说找不到这个对象。我们虽然加了 @Component 注解,但是 Spring 不知道啊,所以需要在 Spring 的配置文件中配置注解扫描:

    <context:component-scan base-package="com.xxl"/>

    base-package: 添加注解的类所在的包位置。

    配置了注解扫描,当程序启动的时候 Spring 会先扫描一下相关的注解,这些注解才会生效。

    就比如你去快递驿站拿快递,你看到了自己的快递然后对快递小哥说:"这我的快递我拿走了啊。"但是人家小哥无法确认是你的啊!所以他需要用扫码枪扫一下才能出货!

    我们配置注解扫描之后再次测试:

    2.@PreDestory(了解即可)

    销毁方法注解,作用在方法上。用来替换 bean 标签的 destory-method 属性。

    10.2 注入相关注解

    10.2.1 @Autowired

    我们之前学 DI 的时候知道:注入就是赋值。

    @Autowired 主要是为自定义的类型赋值,例如 service、dao 层的各种类。

    1. @Controller
    2. public class UserController {
    3.     @Autowired
    4.     private UserService userService;
    5. }
    1. @Service
    2. public class UserServiceImpl implements UserService {
    3.     @Autowired
    4.     private UserDao userDao;
    5. }

    @Autowired 是基于类型进行注入,所注入对象的类型必须和目标 变量类型相同或者是他的子类、实现类。

    如果想基于名字注入,可以和 @Qualifier 注解连用:

    1. @Autowired
    2. @Qualifier("orderDAOImpl")
    3. private OrderDAO orderDAO;

    10.2.2 @Resource

    @Resource 注解是 JAVAEE 规范中提供的注解,他和 @Autowired 注解的作用一样, 但是他是基于名字进行注入:

    1. @Resource("orderDAOImpl")
    2. private OrderDAO orderDAO;

    在实际开发中,用 @Autowired 注解比较多一点。

    10.2.3 案例

    Product 类

    1. @Component
    2. public class Product {
    3.     private  String productName;
    4.     public String getProductName() {
    5.         return productName;
    6.     }
    7.     public void setProductName(String productName) {
    8.         this.productName = productName;
    9.     }
    10.     @Override
    11.     public String toString() {
    12.         return "Product{" +
    13.                 "productName='" + productName + ''' +
    14.                 '}';
    15.     }
    16.     @PostConstruct
    17.     public void init(){
    18.         this.productName = "西瓜";
    19.     }
    20. }

    User 类:

    1. @Component
    2. public class User {
    3.     private String name;
    4.     @Autowired
    5.     private Product product;
    6.     public String getName() {
    7.         return name;
    8.     }
    9.     public void setName(String name) {
    10.         this.name = name;
    11.     }
    12.     public Product getProduct() {
    13.         return product;
    14.     }
    15.     public void setProduct(Product product) {
    16.         this.product = product;
    17.     }
    18.     @PostConstruct
    19.     public void init(){
    20.         this.name = "小明";
    21.     }
    22.     @Override
    23.     public String toString() {
    24.         return "User{" +
    25.                 "name='" + name + ''' +
    26.                 ", product=" + product +
    27.                 '}';
    28.     }
    29. }

    测试:

    1. @Test
    2. public void testSpring() {
    3.     // 1、获取工厂
    4.     ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    5.     // 2、通过工厂类获得对象
    6.     User user = (User) act.getBean("user");
    7.     System.out.println(user);
    8. }

    10.3 Spring 配置文件相关注解

    10.3.1 @Configuration

    @Configuration 注解用于替换 xml 配置文件。

    1. @Configuration
    2. public class SpringConfig {
    3.     
    4. }

    意思就是说你在一个类上面加一个 @Configuration 注解,这个类就可以看成 Spring 的配置类,你就不用再写 xml 文件了。

    我们之前是根据 xml 文件创建 Spring 的工厂,那怎样根据配置类创建工厂呢?

    有两种方式:

    方式一:根据类.class

    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

    方式二:根据配置类所在的路径

    ApplicationContext ctx = new AnnotationConfigApplicationContext("com.xxl");

    10.3.2 @Bean

    @Bean 注解也是用来创建对象的,相当于spring 配置文件中的 bean 标签。

    1. @Configuration
    2. public class SpringConfig {
    3.     @Bean
    4.     public Product getProduct(){
    5.         return new Product();
    6.     }
    7. }

    自定义 id 值:

    1. @Configuration
    2. public class SpringConfig {
    3.     @Bean("product")
    4.     public Product getProduct(){
    5.         return new Product();
    6.     }
    7. }

    不过在实际开发中我们一般会用 @Bean 注解创建一些复杂的对象,例如 Redis、MQ 等一些组件对象。

    1. @Configuration
    2. public class RedisConfig {
    3.     @Bean
    4.     public RedisTemplate<StringObject> redisTemplate(RedisConnectionFactory factory) {
    5.         RedisTemplate<StringObject> redisTemplate = new RedisTemplate<>();
    6.         redisTemplate.setConnectionFactory(factory);
    7.         GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
    8.         StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    9.         redisTemplate.setKeySerializer(stringRedisSerializer);
    10.         redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
    11.         redisTemplate.setHashKeySerializer(stringRedisSerializer);
    12.         redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
    13.         redisTemplate.afterPropertiesSet();
    14.         return redisTemplate;
    15.     }
    16. }

    10.3.3 @ComponentScan

    @ComponentScan 注解相当于 xml 配置文件中的注解扫描标签:

    <context:component-scan base-package="com.xxl"/>

    作用:用来扫描@Component 等相关注解

    属性:

    basePackages:注解所在的包路径

    例如:

    1. @Configuration
    2. @ComponentScan(basePackages = "com.xxl")
    3. public class SpringConfig {
    4. }

    11. 注解小案例

    1.User 类

    1. @Component
    2. public class User {
    3.     private String name;
    4.     @Autowired
    5.     private Product product;
    6.     public String getName() {
    7.         return name;
    8.     }
    9.     public void setName(String name) {
    10.         this.name = name;
    11.     }
    12.     public Product getProduct() {
    13.         return product;
    14.     }
    15.     public void setProduct(Product product) {
    16.         this.product = product;
    17.     }
    18.     @PostConstruct
    19.     public void init(){
    20.         this.name = "渣渣辉";
    21.     }
    22.     @Override
    23.     public String toString() {
    24.         return "User{" +
    25.                 "name='" + name + ''' +
    26.                 ", product=" + product +
    27.                 '}';
    28.     }
    29. }

    2.Product 类

    1. @Component
    2. public class Product {
    3.     private  String productName;
    4.     public String getProductName() {
    5.         return productName;
    6.     }
    7.     public void setProductName(String productName) {
    8.         this.productName = productName;
    9.     }
    10.     @Override
    11.     public String toString() {
    12.         return "Product{" +
    13.                 "productName='" + productName + ''' +
    14.                 '}';
    15.     }
    16. }

    3.配置类:

    1. @Configuration
    2. @ComponentScan(basePackages = "com.xxl")
    3. public class SpringConfig {
    4.     @Bean
    5.     public Product product() {
    6.         Product product = new Product();
    7.         product.setProductName("草莓味的番茄");
    8.         return product;
    9.     }
    10. }

    4.测试

    别再张口闭口高并发海量数据了,Spring这些东西都会了吗?

  • 相关阅读:
    真能处,180公里的纯电续航,这款车居然一点都没亏
    谷歌验证码无法显示问题
    计算机竞赛 基于生成对抗网络的照片上色动态算法设计与实现 - 深度学习 opencv python
    LeetCode 0216.组合总和 III:回溯(剪枝) OR 二进制枚举
    预测市场赛道新机遇:Moonquiz生态基于FIFA WORLD CUP推出首个预测应用
    基础-MySQL
    BlockCanary
    近期面试128题汇总(有超详细答案)
    非科班菜鸡算法学习记录 | 代码随想录算法训练营第57天|| 647. 回文子串 516.最长回文子序列 动态规划总结篇
    centos脚本获取昨天日期删除指定文件之外的其他文件
  • 原文地址:https://blog.csdn.net/lililidahaoren/article/details/126955456