Spring

Spring

Spring 是一个开源框架,广泛用于 Java 应用程序的开发。Spring 的核心是提供了一个全面的编程和配置模型,旨在简化 Java 应用的开发和维护。这个框架主要通过以下几个方面来实现这一目标:

  1. 依赖注入(Dependency Injection, DI):Spring 使用依赖注入来实现松耦合。通过控制反转(Inversion of Control, IoC)的容器,Spring 管理应用组件之间的依赖关系,而不是由组件自行管理。
  2. 面向切面编程(Aspect-Oriented Programming, AOP):Spring 支持 AOP,允许定义横切关注点(如日志、事务管理等),从而增强模块化。
  3. 事务管理:Spring 提供了一种抽象层来管理事务,使得单个事务可以跨多个后端资源统一管理。
  4. 模型视图控制器(MVC):Spring Web MVC 是一个基于 Servlet API 构建的富有表现力的 web 框架,支持 REST API 的构建。
  5. 数据访问与集成:Spring 提供了对 JDBC、JPA、JMS 等技术的抽象,简化了数据库操作和消息服务的使用。
  6. 全栈解决方案:Spring Boot 是 Spring 的一个子项目,它通过提供默认配置帮助开发者快速搭建应用,实现“约定大于配置”的原则。

官网:https://spring.io/

1. IoC

IoC,全称Inversion of Control,中文名为“控制反转”,是一种设计思想,主要用于降低代码间的耦合度。

在IoC思想中,对象的创建和生命周期的管理被交给了IoC容器,而代码只需要声明其需要哪些对象(依赖),然后由IoC容器注入这些依赖,从而实现对象之间的解耦。

1.1 快速入门

1)引入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.8</version>
</dependency>

2)创建javaBean

package com.lhs.bean;

public class Monster {
    public String name;
    public Integer id;
    public String skill;

    public Monster(String name, Integer id, String skill) {
        this.name = name;
        this.id = id;
        this.skill = skill;
    }

    public Monster() {
    }

    public String getName() {
        return name;
    }

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

    public Integer getId() {
        return id;
    }

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

    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }

    @Override
    public String toString() {
        return "Monster{" +
            "name='" + name + '\'' +
            ", id=" + id +
            ", skill='" + skill + '\'' +
            '}';
    }
}

3)创建bean.xml并配置bean

<?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 definitions go here -->
    <bean class="com.lhs.bean.Monster" id="monster">
        <property name="id" value="1"></property>
        <property name="name" value="牛魔王"></property>
        <property name="skill" value="芭蕉扇"></property>
    </bean>

</beans>

4)获取bean

@Test
public void test01(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    System.out.println();
    Monster monster = context.getBean("monster", Monster.class);
    System.out.println(monster);
}

1.2 自定义Bean容器

IoC底层采用ConcurrentHashMap来存储bean对象

模拟实现简化版容器:

public class MyApplicationContext {
    // 存储Bean实例的容器
    private Map<String,Object> concurrentHashMap = new ConcurrentHashMap();

    public MyApplicationContext() throws Exception {
        // 获取bean.xml文件路径
        String path  = this.getClass().getResource("/").getPath();
        // 创建SAXReader对象进行XML解析
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(new File(path + "/bean.xml"));
        Element rootElement = document.getRootElement();

        // 遍历bean.xml中的每一个bean元素
        Iterator iterator = rootElement.elementIterator();
        while (iterator.hasNext()) {
            Element element = (Element)iterator.next();
            // 获取bean的id和class属性
            String id = element.attribute("id").getValue();
            String clazz = element.attribute("class").getValue();
            // 获取bean元素下的所有property元素
            List<Element> elements = element.elements();
            // 通过反射创建Bean实例
            Class<?> aClass = Class.forName(clazz);
            Object o = aClass.newInstance();
            // 遍历每一个property元素,获取其name和value属性,并通过反射调用setter方法设置到Bean实例中
            for (Element element1 : elements) {
                String name = element1.attribute("name").getValue();
                Type type = aClass.getField(name).getGenericType();
                Method method = aClass.getMethod(getSetterName(name),Class.forName(type.getTypeName()));

                String value = element1.attribute("value").getValue();
                method.invoke(o,convertStringToType(value,Class.forName(type.getTypeName())));
            }
            // 将Bean实例存入容器
            concurrentHashMap.put(id,o);
        }
    }

    // 通过id获取Bean实例
    public Object getBean(String id){
        return concurrentHashMap.get(id);
    }

    // 将属性名转换为对应的setter方法名
    private String getSetterName(String name){
        char[] charArray = name.toCharArray();
        charArray[0] = (char) (charArray[0] - 32);
        String name2 = new String(charArray);
        return "set" + name2;

    }
    // 将字符串值转换为对应的类型
    private Object convertStringToType(String str, Class<?> type) throws Exception {
        if (type == Integer.class) {
            return Integer.parseInt(str);
        } else if (type == Double.class) {
            return Double.parseDouble(str);
        } else if (type == Boolean.class) {
            return Boolean.parseBoolean(str);
        } else if (type == Float.class) {
            return Float.parseFloat(str);
        } else if (type == Long.class) {
            return Long.parseLong(str);
        } else if (type == Short.class) {
            return Short.parseShort(str);
        } else if (type == Byte.class) {
            return Byte.parseByte(str);
        } else if (type == Character.class) {
            // 特别处理字符类型,确保字符串长度为1
            if (str.length() != 1) {
                throw new IllegalArgumentException("String length must be 1 to convert to Character.");
            }
            return str.charAt(0);
        } else if (type == String.class) {
            return str;
        }
        // 若类型不支持,则抛出异常
        throw new IllegalArgumentException("Unsupported type: " + type);
    }
}

1.3 获取Bean

通过id获取
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Monster monster = context.getBean("monster", Monster.class);
// 若不传类型则返回Object     
Object monster1 = context.getBean("monster");
通过类名获取
Monster bean = context.getBean(Monster.class);

该方法要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常。

静态工厂获取对象

1)创建静态工厂:

public class MyStaticFactory {
    private static Map<String, Monster> monsterMap;
    static {
        monsterMap = new HashMap<String, Monster>();
        monsterMap.put("monster_01", new Monster(100, "黄袍怪", "一阳指"));
        monsterMap.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
    }
    public static Monster getMonster(String key) {
        return monsterMap.get(key);
    }
}

2)修改bean.xml:

<bean id="my_monster" class="com.lhs.factory.MyStaticFactory"
      factory-method="getMonster">
    <!-- constructor-arg 标签提供 key -->
    <constructor-arg value="monster_01"/>
</bean>

3)测试:

@Test
public void getBeanByStaticFactory() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    Monster my_monster = ioc.getBean("my_monster", Monster.class);
    System.out.println(my_monster);
}
通过实例工厂获取对象

通过静态工厂获取的对象永远都是同一个,而通过不同实例工厂获取到的对象是不同的。

1)创建实例工厂:

public class MyInstanceFactory {
    private Map<String, Monster> monster_map;
    //非静态代码块
    {
        monster_map = new HashMap<String, Monster>();
        monster_map.put("monster_01", new Monster(100, "猴子精", "吃人"));
        monster_map.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
    }
    public Monster getMonster(String key) {
        return monster_map.get(key);
    }
}

2)修改bean.xml:

<bean id="myInstanceFactory" class="com.lhs.factory.MyInstanceFactory"/>
<bean id="my_monster2" factory-bean="myInstanceFactory"
      factory-method="getMonster">
    <constructor-arg value="monster_02"/>
</bean>

3)测试:

@Test
public void getBeanByInstanceFactory() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    Monster my_monster = ioc.getBean("my_monster2", Monster.class);
    System.out.println(my_monster);
}
通过FactoryBean获取对象

1)创建工厂继承FactoryBean接口:

public class MyFactoryBean implements FactoryBean<Monster> {
    private String keyVal;
    private Map<String, Monster> monster_map;
    {
        monster_map = new HashMap<String, Monster>();
        monster_map.put("monster_01", new Monster(100, "黄袍怪", "一阳指"));
        monster_map.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
    }
    public void setKeyVal(String keyVal) {
        this.keyVal = keyVal;
    }
    @Override
    public Monster getObject() throws Exception {
        // TODO Auto-generated method stub
        return this.monster_map.get(keyVal);
    }
    @Override
    public Class getObjectType() {
        // TODO Auto-generated method stub
        return Monster.class;
    }
    @Override
    public boolean isSingleton() {
        // TODO Auto-generated method stub
        return true;
    }
}

2)配置bean.xml:

<bean id="myFactoryBean" class="com.lhs.factory.MyFactoryBean">
    <property name="keyVal" value="monster_01"/>
</bean>

3)测试:

public void getBeanByFactoryBean() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    Monster monster = ioc.getBean("myFactoryBean", Monster.class);
    System.out.println(monster);
}

1.4 基于XML文件配置Bean

通过Setter方法配置bean
<bean class="com.lhs.bean.Monster" id="monster">
    <property name="id" value="1"></property>
    <property name="name" value="牛魔王"></property>
    <property name="skill" value="芭蕉扇"></property>
</bean>

底层会调用该类的setter方法给属性赋值

通过构造器配置bean

方式一:按照构造器参数索引指定参数

<bean class="com.lhs.bean.Monster" id="monster02">
    <constructor-arg value="蜘蛛精" index="0"/>
    <constructor-arg value="2" index="1"/>
    <constructor-arg value="吐口水" index="2"/>
</bean>

方式二:按照类型指定参数

<bean class="com.lhs.bean.Monster" id="monster03">
    <constructor-arg value="3" type="java.lang.Integer"/>
    <constructor-arg value="白骨精" type="java.lang.String"/>
    <constructor-arg value="白骨鞭" type="java.lang.String"/>
</bean>
通过p名称空间配置bean

在bean.xml文件中增加p名称空间配置

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean class="com.lhs.bean.Monster" id="monster04"
          p:id="4"
          p:name="红孩儿"
          p:skill="喷火"
          />

</beans>
通过ref来引用bean对象

在ServiceImpl中引入DAOImpl

方式一:

<bean id="memberServiceImpl" class="com.lhs.service.MemberServiceImpl">
    <property name="memberDAO" ref="memberDAOImpl"/>
</bean>
<bean id="memberDAOImpl" class="com.lhs.dao.MemberDAOImpl"/>

方式二:内部注入

<bean id="memberServiceImpl02" class="com.lhs.service.MemberServiceImpl">
    <property name="memberDAO">
        <bean class="com.lhs.dao.MemberDAOImpl"/>
    </property>
</bean>
引入集合/数组类型

1)创建Master类:

package com.lhs.bean;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class Master {
    private String name;
    private List<Monster> monsterList;
    private Map<String, Monster> monsterMap;
    private Set<Monster> monsterSet;
    private String[] monsterName;
    private Properties pros;

    public Master(String name, List<Monster> monsterList, Map<String, Monster> monsterMap, Set<Monster> monsterSet, String[] monsterName, Properties pros) {
        this.name = name;
        this.monsterList = monsterList;
        this.monsterMap = monsterMap;
        this.monsterSet = monsterSet;
        this.monsterName = monsterName;
        this.pros = pros;
    }

    public Master() {
    }

    public String getName() {
        return name;
    }

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

    public List<Monster> getMonsterList() {
        return monsterList;
    }

    public void setMonsterList(List<Monster> monsterList) {
        this.monsterList = monsterList;
    }

    public Map<String, Monster> getMonsterMap() {
        return monsterMap;
    }

    public void setMonsterMap(Map<String, Monster> monsterMap) {
        this.monsterMap = monsterMap;
    }

    public Set<Monster> getMonsterSet() {
        return monsterSet;
    }

    public void setMonsterSet(Set<Monster> monsterSet) {
        this.monsterSet = monsterSet;
    }

    public String[] getMonsterName() {
        return monsterName;
    }

    public void setMonsterName(String[] monsterName) {
        this.monsterName = monsterName;
    }

    public Properties getPros() {
        return pros;
    }

    public void setPros(Properties pros) {
        this.pros = pros;
    }
}

2)配置bean.xml:

<!-- 给集合属性注入值-->
<bean id="master01" class="com.lhs.bean.Master">
    <property name="name" value="太上老君"/>
    <!-- 给 bean 对象的 list 集合赋值 -->
    <property name="monsterList">
        <list>
            <ref bean="monster03"/>
            <ref bean="monster02"/>
        </list>
    </property>
    <!-- 给 bean 对象的 map 集合赋值 -->
    <property name="monsterMap">
        <map>
            <entry>
                <key>
                    <value>monsterKey01</value>
                </key>
                <ref bean="monster01"/>
            </entry>
            <entry>
                <key>
                    <value>monsterKey02</value>
                </key>
                <ref bean="monster02"/>
            </entry>
        </map>
    </property>
    <!-- 给 bean 对象的 properties 集合赋值 -->
    <property name="pros">
        <props>
            <prop key="k1">Java 工程师</prop>
            <prop key="k2">前端工程师</prop>
            <prop key="k3">大数据工程师</prop>
        </props>
    </property>
    <!-- 给 bean 对象的数组属性注入值 -->
    <property name="monsterName">
        <array>
            <value>银角大王</value>
            <value>金角大王</value>
        </array>
    </property>
    <!-- 给 bean 对象的 set 属性注入值 -->
    <property name="monsterSet">
        <set>
            <ref bean="monster01"/>
            <bean class="com.lhs.bean.Monster">
                <property name="id" value="10"/>
                <property name="name" value="玉兔精"/>
                <property name="skill" value="钻地洞"/>
            </bean>
        </set>
    </property>
</bean>
通过util名称空间创建list
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
    <!--
通过 util 名称空间来创建 list 集合,可以当做创建 bean 对象的工具来使用
-->
    <util:list id="myListBook">
        <value>三国演义</value>
        <value>西游记</value>
        <value>红楼梦</value>
        <value>水浒传</value>
    </util:list>

    <bean id="bookStore" class="com.lhs.bean.BookStore">
        <property name="bookList" ref="myListBook"/>
    </bean>
</beans>
级联属性赋值

spring 的 ioc 容器, 可以直接给对象属性的属性赋值, 即级联属性赋值

1)创建Dept类:

public class Dept {
    private String name;
    public Dept() {
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

2)创建Emp类:

public class Emp {
    private String name;
    private Dept dept;
    public Emp() {
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Dept getDept() {
        return dept;
    }
    public void setDept(Dept dept) {
        this.dept = dept;
    }
}

3)bean.xml:

<bean id="emp" class="com.lhs.bean.Emp">
    <property name="name" value="jack"/>
    <property name="dept" ref="dept"/>
    <property name="dept.name" value="Java 开发部"/>
</bean>
<bean id="dept" class="com.lhs.bean.Dept"/>
配置信息的重用
<!-- 继承的方式来实现 bean 配置信息的重用 -->
<bean id="monster10" class="com.lhs.bean.Monster">
    <property name="monsterId" value="10"/>
    <property name="name" value="蜈蚣精"/>
    <property name="skill" value="蜇人"/>
</bean>

<!-- parent="monster10" 就是继承使用了 monster10 的配置信息 -->
<bean id="monster11" class="com.lhs.bean.Monster" parent="monster10"/>

<!-- 当我们把某个bean设置为 abstract="true" 这个bean只能被继承,而不能实例化了 -->
<bean id="monster12" class="com.lhs.bean.Monster" abstract="true">
    <property name="monsterId" value="12"/>
    <property name="name" value="美女蛇"/>
    <property name="skill" value="吃人"/>
</bean>

<!-- parent="monster12" 就是继承使用了 monster12 的配置信息 -->
<bean id="monster13" class="com.lhs.bean.Monster" parent="monster12"/>
通过属性文件给 bean 注入值

1)创建 my.properties文件:

name=\u9EC4\u888D\u602A
id=10
skill=\u72EE\u5B50\u543C

2)配置bean.xml:

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

    <!--
    1. 通过属性文件给 bean 注入值
    2. 需要导入: xmlns:context 名字空间,并指定属性文件路径
	-->
    <context:property-placeholder location="classpath:my.properties"/>
    <bean id="monster100" class="com.lhs.bean.Monster">
        <property name="monsterId" value="${id}"/>
        <property name="name" value="${name}"/>
        <property name="skill" value="${skill}"/>
    </bean>
</beans>

3)测试:

@Test
public void setProByProFile() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    Monster monster100 = ioc.getBean("monster100", Monster.class);
    System.out.println(monster100);
}

1.5 bean对象的单例和多例

在spring的ioc容器,在默认是按照单例创建的,即配置一个 bean 对象后,ioc 容器只会创建一个 bean 实例。

如果,我们希望 ioc 容器配置的某个 bean 对象,是以多个实例形式创建的则可以通过配置scope=“prototype” 来指定

bean.xml:

<bean name="car" scope="prototype" class="com.lhs.bean.Car"/>

注意:

1)默认是单例 singleton, 在启动容器时, 默认就会创建 , 并放入到 singletonObjects 集合

2)当 <bean scope="prototype" > 设置为多实例机制后, 该 bean 是在 getBean()时才创建

3)如果是单例 singleton, 同时希望在 getBean 时才创建 , 可以指定懒加载lazy-init="true" (注意默认是 false)

4)通常情况下, lazy-init 就使用默认值 false , 在开发看来, 用空间换时间是值得的, 除非有特殊的要求

5) 如果 scope=“prototype” 这时的 lazy-init 属性的值不管是 ture, 还是 false 都是在getBean 时候,才创建对象

1.6 bean的生命周期

bean 对象创建是由 JVM 完成的,然后按顺序执行如下方法:

1)执行构造器

2)执行 set 相关方法

3)调用 bean 的初始化的方法(需要配置)

4)使用 bean

5)当容器关闭时候,调用 bean 的销毁方法(需要配置)

1)创建bean:

public class House {
    private String name;
    public House() {
        System.out.println("House() 构造器");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        System.out.println("House setName()...");
        this.name = name;
    }
    public void init() {
        System.out.println("House init()..");
    }
    public void destory() {
        System.out.println("House destory()..");
    }
}

2)bean.xml:

<!-- 配置 bean 的初始化方法和销毁方法 -->
<bean id="house" class="com.lhs.bean.House"
      init-method="init" 
      destroy-method="destory">
    <property name="name" value="北京豪宅"/>
</bean>

3)测试:

@Test
public void beanLife() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    House house = ioc.getBean("house", House.class);
    System.out.println(house);
    // 执行销毁方法
    ((ConfigurableApplicationContext) ioc).close();
}

1.7 bean的后置处理器

在 spring 的 ioc 容器,可以配置 bean 的后置处理器,该处理器/对象会在 bean 初始化方法调用前和初始化方法调用后被调用。

我们可以在后置处理器中编写自己的代码,可以对 IOC 容器中所有的对象进行统一处理,比如 日志处理/权限的校验/安全的验证/事务管理。

实例:

1)创建后置处理器继承BeanPostProcessor接口:

public class MyBeanPostProcessor implements BeanPostProcessor {
    /**
    * 在 bean 初始化之前完成某些任务
    * @param bean : 就是 ioc 容器返回的 bean 对象, 如果这里被替换会修改,则返
    回的 bean 对象也会被修改
    * @param beanName: 就是 ioc 容器配置的 bean 的名称
    * @return Object: 就是返回的 bean 对象
*/
    public Object postProcessBeforeInitialization(Object bean, String beanName)
        throws BeansException {
        // TODO Auto-generated method stub
        System.out.println("postProcessBeforeInitialization 被 调 用 " + beanName + " bean= " + bean.getClass());
        return bean;
    }
    
    /**
    * 在 bean 初始化之后完成某些任务
    * @param bean : 就是 ioc 容器返回的 bean 对象, 如果这里被替换会修改,则返
    回的 bean 对象也会被修改
    * @param beanName: 就是 ioc 容器配置的 bean 的名称
    * @return Object: 就是返回的 bean 对象
*/
    public Object postProcessAfterInitialization(Object bean, String beanName)
        throws BeansException {
        System.out.println("postProcessAfterInitialization 被调用 " + beanName + " bean= " + bean.getClass());
        return bean;
    }
}

2)配置bean.xml:

<!-- 配置 bean 的初始化方法和销毁方法 -->
<bean id="house" class="com.lhs.beans.House"
      init-method="init" destroy-method="destory">
    <property name="name" value="北京豪宅"/>
</bean>
<!-- bean 后置处理器的配置 -->
<bean id="myBeanPostProcessor" class="com.lhs.bean.MyBeanPostProcessor" />
</beans>

3)测试:

@Test
public void testBeanPostProcessor() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans02.xml");
    House house = ioc.getBean("house", House.class);
    System.out.println(house);
    //关闭容器
    ((ConfigurableApplicationContext) ioc).close();
}

1.8 bean的自动装配

根据类型进行自动组装
<bean id="orderAction" autowire="byType" class="com.lhs.action.OrderAction" />
<bean id="orderService" autowire="byType" class="com.lhs.service.OrderService"/>
<bean id="orderDao" class="com.lhs.dao.OrderDao"/>
根据id进行自动组装
<!-- 
1.autowire = "byName" 会自动去找 id 为 setXxxx 后面 Xxxx 的 bean 自动组装.
,如果找到就装配,如果找不到就报错
2. <bean id="orderAction" autowire="byName" class="com.lhs.bean.OrderAction" />
就 会 去 找 OrderAction 类 中 定 义 的 setOrderService 的 id 为 orderService 的
OrderService bean 组装,找到就阻装,找不到就组装失败
-->
<bean id="orderAction" autowire="byName" class="com.lhs.action.OrderAction"/>
<bean id="orderService" autowire="byName" class="com.lhs.service.OrderService"/>
<bean id="orderDao" class="com.lhs.dao.OrderDao"/>

1.9 基于注解配置bean

组件注解的形式有:

  • @Component 表示当前注解标识的是一个组件
  • @Controller 表示当前注解标识的是一个控制器,通常用于 Servlet
  • @Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于 Service 类
  • @Repository 表示当前注解标识的是一个持久化层的类,通常用于 Dao 类

示例:

1)创建dao层

@Repository
public class UserDao {
}

2)创建service层

@Service
public class UserService {
}

3)创建controller

@Controller
public class UserAction {
}

4)创建普通组件

@Component
public class MyComponent {
}

5)配置bean.xml

<!-- 配置自动扫描的包,注意需要加入 context 名称空间 -->
<context:component-scan base-package="com.lhs.component" />

6)测试

@Test
public void getBeanByAnnotation() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    UserAction userAction = ioc.getBean(UserAction.class);
    System.out.println(userAction);
    UserDao userDao = ioc.getBean(UserDao.class);
    System.out.println(userDao);
    MyComponent myComponent = ioc.getBean(MyComponent.class);
    System.out.println(myComponent);
    UserService userService = ioc.getBean(UserService.class);
    System.out.println(userService);
}

还可以通过@Component(value = “xx”) @Controller(value = “yy”) @Service(value = “zz”)中指定的 value, 给 bean 分配 id

排除指定注解
<context:component-scan base-package="com.lhs.component">
    <context:exclude-filter type="annotation"expression="org.springframework.stereotype.Service"/>
</context:component-scan>

注意:

  • <context:exclude-filter> 放在<context:component-scan>内,表示扫描过滤掉当前包
    的某些类
  • type=“annotation” 按照注解类型进行过滤.
  • expression :就是注解的全类名,比如 org.springframework.stereotype.Service 就是 @Service 注解的全类名,其它比@Controller @Repository 等 依次类推
  • 上面表示过滤掉 com.lhs.component 包下,加入了@Service 注解的类
指定扫描注解
<context:component-scan base-package="com.lhs.component" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

注意:

  • use-default-filters=“false”: 不再使用默认的过滤机制
  • context:include-filter: 表示只是扫描指定的注解的类
  • expression=“org.springframework.stereotype.Controller”: 注解的全类名
自定义容器
public class MyApplicationContext02 { 
    // 创建bean容器
    private Map<String,Object> concurrentHashMap = new ConcurrentHashMap();

    public  MyApplicationContext02(){
        // 模拟要扫描的包的名称
        String packetName = "com/lhs/component";
        // 获取当前线程的类加载器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // 获取包的URL资源
        URL resource = classLoader.getResource(packetName);
        // 根据资源路径创建文件对象
        File file = new File(resource.getPath());
        // 判断文件对象是否为目录
        if(file.isDirectory()){
            // 获取目录下的所有文件
            File[] files = file.listFiles();
            // 遍历所有文件
            for (File file1 : files) {
                try {
                    // 加载文件对应的类
                    Class<?> aClass = Class.forName(packetName.replace("/",".")+ "." + file1.getName().replace(".class",""));
                    // 判断类是否被指定的注解标记
                    if(aClass.isAnnotationPresent(Primary.class) || aClass.isAnnotationPresent(Service.class)
                       || aClass.isAnnotationPresent(Controller.class) || aClass.isAnnotationPresent(Component.class)){
                        // 如果被标记,则创建类的实例并存入哈希表
                        concurrentHashMap.put(file1.getName().replace(".class",""),aClass.newInstance());
                    }
                } catch (Exception e) {
                    // 如果出现异常,抛出运行时异常
                    throw new RuntimeException(e);
                }
            }
        }
        // 打印哈希表内容
        System.out.println(concurrentHashMap);
    }

    // 根据名称获取哈希表中的对象
    public Object getBean(String name){
        return concurrentHashMap.get(name);
    }
}

自动装配
@AutoWired

在 IOC 容器中查找待装配的组件的类型,如果有唯一的 bean 匹配,则使用该 bean 装配

如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找, 找到就装配,找不到就抛异常

示例:

@Controller("userAction01")
public class UserAction {
    //自动装配 UserService, 这时是以 id=userService 的 UserService 对象进行组装.
    @Autowired
    private UserService userService;
    
    public void sayOk(){
        System.out.println("UserAction.userService= " + userService);
        userService.hi();
    }
}
@Autowired
@Qualifier(value = "userService02") //指定 id=userService02 的 UserService 对象进行组装
private UserService userService;

public void sayOk(){
    System.out.println("UserAction.userService= " + userService);
    userService.hi();
}
@Resource

@Resource 有两个属性是比较重要的,分是 name 和 type,Spring 将@Resource 注解的name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型,所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略

如果@Resource 没有指定 name 和 type ,则先使用byName注入策略, 如果匹配不上,再使用 byType 策略, 如果都不成功,就会报错

示例:

1)导入依赖

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

2)完成装配

@Controller
public class UserController {
    @Resource
    UserService userService;
}
泛型依赖注入

Spring可以根据泛型类型来自动装配bean

假设我们有一个泛型接口:

public interface MyRepository<T> {
    T getById(String id);
}

然后我们有两个实现类:

@Repository
public class StringRepository implements MyRepository<String> {
    @Override
    public String getById(String id) {
        return "StringRepository";
    }
}

@Repository
public class IntegerRepository implements MyRepository<Integer> {
    @Override
    public Integer getById(String id) {
        return 123;
    }
}

我们有两个实现了MyRepository接口的bean,一个是StringRepository,另一个是IntegerRepository

现在,如果我们在另一个类中需要注入一个MyRepository的实例,我们可以这样做:

@Service
public class MyService {
    @Autowired
    private MyRepository<String> stringRepository;

    @Autowired
    private MyRepository<Integer> integerRepository;
}

Spring会根据泛型类型来自动装配stringRepositoryintegerRepository。也就是说,stringRepository会被装配为StringRepository的实例,integerRepository会被装配为IntegerRepository的实例。

2. AOP

AOP 的全称(aspect oriented programming) ,面向切面编程,它允许开发者在不修改原有代码结构的情况下,增加额外的功能。

这种编程方式主要通过“切面”来实现,切面可以定义为跨越多个类或方法的关注点(比如日志记录、事务管理等),从而使这些关注点的模块化成为可能。

请添加图片描述

在AOP中,有以下几个核心概念:

  1. 切点(Pointcut):定义了何处需要执行切面的代码。它通过匹配方法签名来指定切面可以应用的一个或多个连接点。
  2. 通知(Advice):切面必须在指定的切点上执行的具体操作。这些操作包括:
    • 前置通知(Before advice):在目标方法执行之前执行。
    • 后置通知(After advice):在目标方法执行之后执行,无论其结果如何。
    • 返回后通知(After-returning advice):仅当目标方法成功完成后执行。
    • 异常后通知(After-throwing advice):仅当目标方法通过抛出异常退出时执行。
    • 环绕通知(Around advice):在目标方法执行前后执行,能够阻止目标方法的执行。
  3. 切面(Aspect):通知和切点的结合。切面既包含了具体的操作定义,也规定了这些操作应当在何处执行。
  4. 目标对象(Target Object):被一个或多个切面所通知的对象。
  5. 代理(Proxy):为目标对象提供方法拦截并将调用委托到通知的中间代理类。在 Spring AOP 中,代理通常是基于 JDK 动态代理或 CGLIB 代理。

2.1 动态代理

我们来看一个案例:

  • 有 Vehicle(交通工具接口, 有一个 run 方法), 下面有两个实现类 Car 和 Ship
  • 当运行 Car 对象 的 run 方法和 Ship 对象的 run 方法时,使用动态代理的方式在方法执行前和执行后添加输出语句

代码演示:

1)Vehicle.java

public interface Vehicle {
    public void run();
    public String fly(String height);
}

2)Car.java 和 Ship.java

public class Car implements Vehicle{
    @Override
    public void run() {
        System.out.println("car is running");
    }

    @Override
    public String fly(String height) {
        return "car is flying=" + height;
    }
}
public class Ship implements Vehicle{
    @Override
    public void run() {
        System.out.println("ship is running");
    }

    @Override
    public String fly(String height) {
        return "ship is flying=" + height;
    }
}

3)创建代理:

package com.lhs.Proxy;

import com.lhs.bean.Vehicle;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import java.lang.reflect.Method;

public class MyProxy {
    public static Vehicle getProxy(Vehicle vehicle) {
        Vehicle res = (Vehicle) Proxy.newProxyInstance(MyProxy.class.getClassLoader(),
                new Class[]{Vehicle.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                        System.out.println("开始执行");
                        Object res = method.invoke(vehicle, objects);
                        System.out.println("结束执行");
                        return res;
                    }
                });
        return res;
    }
}

通过调用Proxy.newProxyInstance方法动态地创建代理实例

newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):

  • ClassLoader loader:类加载器用于定义代理类。
  • Class<?>[] interfaces:代理类需要实现的接口列表。
  • InvocationHandler h:调用处理器实例,用于处理方法调用。

4)测试:

@Test
public void test04(){
    Car car = new Car();
    Ship ship = new Ship();
    Vehicle proxy = MyProxy.getProxy(car);
    Vehicle proxy1 = MyProxy.getProxy(ship);
    proxy.run();
    proxy1.run();
}

2.2 自制简易AOP

1)将上述代理在方法执行前和执行后插入的代码封装成一个方法

public class MyAOP {
    public static void before(Object o, Method method, Object[] objects){
        System.out.println("执行方法="+method.getName()+"参数="+ Arrays.toString(objects));
    }
    public static void after(Method method, Object result){
        System.out.println("返回结果为="+result);
    }
}

2)修改代理类

public class MyProxy {
    public static Vehicle getProxy(Vehicle vehicle) {
        Vehicle res = (Vehicle) Proxy.newProxyInstance(MyProxy.class.getClassLoader(),
                new Class[]{Vehicle.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                        MyAOP.before(o,method,objects);
                        Object res = method.invoke(vehicle, objects);
                        MyAOP.after(method,res);
                        return res;
                    }
                });
        return res;
    }
}

3)测试

@Test
public void test04(){
    Car car = new Car();
    Ship ship = new Ship();
    Vehicle proxy = MyProxy.getProxy(car);
    Vehicle proxy1 = MyProxy.getProxy(ship);
    proxy.fly("100");
    proxy1.fly("1000");
}

2.3 AOP编程快速入门

1)导入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.8</version>
</dependency>

2)配置bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- Bean definitions go here -->
    <context:component-scan base-package="com.lhs" />
    <aop:aspectj-autoproxy/>

</beans>

3)创建切面类

@Component
@Aspect
public class MyAspect {
    @Before(value = "execution(public String com.lhs.bean.Car.fly(String))")
    public void showBeginLog(JoinPoint joinPoint){
        System.out.println("前置通知");
    }

    @AfterReturning(value = "execution(public String com.lhs.bean.Car.fly(String))")
    public void showSuccessEndLog(JoinPoint joinPoint){
        System.out.println("返回通知");
    }

    @AfterThrowing(value = "execution(public String com.lhs.bean.Car.fly(String))")
    public void showExceptionLog(JoinPoint joinPoint){
        System.out.println("异常通知");
    }

    @After(value = "execution(public String com.lhs.bean.Car.fly(String))")
    public void showFinallyEndLog(JoinPoint joinPoint){
        System.out.println("最终通知");
    }
}

4)测试

@Test
public void test04(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
    Vehicle car = ioc.getBean("car", Vehicle.class);
    car.fly("100");
    System.out.println(car.getClass());
}

输出结果:

前置通知
car的飞行高度为:100米
返回通知
最终通知
class jdk.proxy2.$Proxy22

2.4 切入表达式

execution用于匹配方法执行:execution(修饰符 放回类型 包名.方法名(参数类型))

表达式:

  1. 匹配所有公有方法

    execution(public * *(..))
    

    这个表达式匹配所有返回任何类型,名为任何名称,参数为任何类型和数量的公有方法。

  2. 匹配特定包下的所有方法

    execution(* com.example.service.*.*(..))
    

    这匹配 com.example.service 包下任何类的所有方法,不论返回类型、方法名、参数。

  3. 匹配特定类的特定方法

    execution(String com.example.service.MyService.*(..))
    

    这匹配 com.example.service.MyService 类中返回类型为 String 的所有方法。

  4. 匹配抛出特定异常的方法

    execution(* *(..) throws java.io.IOException)
    

    这个表达式匹配所有返回任何类型,参数为任何类型和数量,且声明抛出 IOException 的方法。

  5. 匹配所有setter方法

    execution(public void set*(String))
    

    这匹配所有公有的、返回类型为 void、名字以 “set” 开始、并接受一个 String 类型参数的方法。

注意:切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效

2.5 JoinPoint

通过 JoinPoint 可以获取到调用方法的签名

@Before(value = "execution(public String com.lhs.bean.Car.fly(String))")
public void showBeginLog(JoinPoint joinPoint){
    joinPoint.getSignature().getName(); // 获取目标方法名
    joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属类的简单类名
    joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的类名
    joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)
    Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组
    joinPoint.getTarget(); // 获取被代理的对象
    joinPoint.getThis(); // 获取代理对象自己
}

2.6 返回通知获取结果

在returning参数中可以指定返回结果的名称,并在接收时要保证名称一致。

@AfterReturning(value = "execution(public String com.lhs.bean.Car.fly(String))",returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint,Object res){
    System.out.println("返回通知");
    System.out.println("返回结果为:"+res);
}

2.7 异常通知中获取异常

在throwing参数中可以指定异常的名称,并在接收时要保证名称一致。

@AfterThrowing(value = "execution(public String com.lhs.bean.Car.fly(String))",throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint,Throwable throwable){
    System.out.println("异常通知");
    System.out.println("异常信息:"+throwable);
}

2.8 环绕通知

环绕通知可以完成其它四个通知要做的事情

示例:

@Around(value = "execution(public String com.lhs.bean.Car.fly(String))")
public void around(ProceedingJoinPoint joinPoint){
    System.out.println("前置通知");
    try {
        // 执行目标方法
        joinPoint.proceed();
        System.out.println("返回通知");
    } catch (Throwable e) {
        System.out.println("异常通知");
        throw new RuntimeException(e);
    } finally {
        System.out.println("最终通知");
    }
}

2.9 切入点表达式重用

为了统一管理切入点表达式,可以使用切入点表达式重用技术。

public class MyAspect {
    // 创建切入点,指定要切入的方法
    @Pointcut(value = "execution(public String com.lhs.bean.Car.fly(String))")
    public void myPointcut(){}
    
    // 使用切入点指定方法
    @Before(value = "myPointcut()")
    public void showBeginLog(JoinPoint joinPoint){
        System.out.println("前置通知");
    }

    @AfterReturning(value = "myPointcut()",returning = "res")
    public void showSuccessEndLog(JoinPoint joinPoint,Object res){
        System.out.println("返回通知");
        System.out.println("返回结果为:"+res);
    }

    @AfterThrowing(value = "myPointcut()",throwing = "throwable")
    public void showExceptionLog(JoinPoint joinPoint,Throwable throwable){
        System.out.println("异常通知");
        System.out.println("异常信息:"+throwable);
    }

    @After(value = "myPointcut()")
    public void showFinallyEndLog(JoinPoint joinPoint){
        System.out.println("最终通知");
    }
}

2.10 切面优先级问题

使用 @order(value=n) 可以来控制切面的优先级,n 值越小,优先级越高。

通知的调用顺序类似 Filter 的过滤链式调用机制。

Aspect01:

@Component
@Aspect
@Order(1)
public class MyAspect {
    @Pointcut(value = "execution(public String com.lhs.bean.Car.fly(String))")
    public void myPointcut(){}

    @Before(value = "myPointcut()")
    public void showBeginLog(JoinPoint joinPoint){
        System.out.println("前置通知01");
    }

    @AfterReturning(value = "myPointcut()",returning = "res")
    public void showSuccessEndLog(JoinPoint joinPoint,Object res){
        System.out.println("返回通知01");
        System.out.println("返回结果01为:"+res);
    }

    @AfterThrowing(value = "myPointcut()",throwing = "throwable")
    public void showExceptionLog(JoinPoint joinPoint,Throwable throwable){
        System.out.println("异常通知01");
        System.out.println("异常信息01:"+throwable);
    }

    @After(value = "myPointcut()")
    public void showFinallyEndLog(JoinPoint joinPoint){
        System.out.println("最终通知01");
    }
}

Aspect02:

@Component
@Aspect
@Order(2)
public class MyAspect01 {
    @Pointcut(value = "execution(public String com.lhs.bean.Car.fly(String))")
    public void myPointcut(){}

    @Before(value = "myPointcut()")
    public void showBeginLog(JoinPoint joinPoint){
        System.out.println("前置通知02");
    }

    @AfterReturning(value = "myPointcut()",returning = "res")
    public void showSuccessEndLog(JoinPoint joinPoint,Object res){
        System.out.println("返回通知02");
        System.out.println("返回结果02为:"+res);
    }

    @AfterThrowing(value = "myPointcut()",throwing = "throwable")
    public void showExceptionLog(JoinPoint joinPoint,Throwable throwable){
        System.out.println("异常通知02");
        System.out.println("异常信息02:"+throwable);
    }

    @After(value = "myPointcut()")
    public void showFinallyEndLog(JoinPoint joinPoint){
        System.out.println("最终通知02");
    }
}

执行后控制台输出:

前置通知01
前置通知02
car的飞行高度为:100米
返回通知02
返回结果02为:car is flying=100
最终通知02
返回通知01
返回结果01为:car is flying=100
最终通知01

请添加图片描述

2.11 基于XML配置AOP

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 配置自动扫描的包 -->
    <context:component-scan base-package="com.lhs.aop.xml"/>
    <!-- 开启基于注解的 AOP 功能 -->
    <aop:aspectj-autoproxy/>
    
    <!-- 配置  bean -->
    <bean id="smartAnimalAspect" class="com.lhs.aop.xml.SmartAnimalAspect"/>
    <!--配置 SmartDog-->
    <bean class="com.lhs.aop.xml.SmartDog" id="smartDog"/>
    
    <aop:config>
        <!-- 配置统一切入点 -->
        <aop:pointcut expression="execution(public float
                                  com.lhs.aop.xml.SmartDog.getSum(float, float))"
                      id="myPointCut"/>
        
        <aop:aspect ref="smartAnimalAspect" order="1">
            <!-- 配置各个通知对应的切入点 -->
            <aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
            <aop:after-returning method="showSuccessEndLog" pointcut-ref="myPointCut" returning="res"/>
            <aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="throwable"/>
            <aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
            <!-- 还可以配置环绕通知 -->
            <!-- <aop:around method=""/> -->
        </aop:aspect>
    </aop:config>
</beans>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林小果呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值