Spring01

spring框架

spring是轻量级的容器框架

spring framework

1、Spring核心学习内容 IOC、AOp, jdbcTemplate,声明式事务
2、IOC:控制反转,孚以管理部8号对象
3.AOP:切面编程4.JDBCTemplate:是spring提供一套访问数据库的技术,应用性强,相对好理解5.声明式事务:基于ioc/aop实现事务管理,理解有需要小伙伴花时间6.IOC, AOP 是重点同时难点

重要概念

1.Spring可以整合其他的框架(老韩解读: Spring是管理框架的框架)

2.Spring有两个核心的概念: IOC 和 AOP
3.IOC [Inversion Of Control反转控制]。传统的开发模式[JdbcUtils/反射]程序---->环境//程序读取环境配置,然后自己创建对象

1.spring根据配置文件xml注解,创建对象,并放入容器中,并且可以完成对象之间的依赖.

spring可以整合其他框架,是管理其他框架的框架

spring中有两个核心概念:ioc控制反转 aop 切面编程文件

在传统开发中:程序读取环境配置信息{可能会用到new,或者反射}然后自己创建对象

在ioc开发模式:容器中创建好对象---->被程序使用

容器创建对象的方法:1.xml配置文件,2.注解

1.spring根据配置文件或者配置文件创建对象,并放入到容器中;并完成对象之间的依赖

2.当需要某个对象实例的时候,可以直接从容器中获取

3.编写人员可以更加关注对象完成相应的业务

4.DI{dependency injection}依赖注入

5.Spring最大的价值,通过配置提供给程序使用

实例

package com.Test;

import com.bean.Monster;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Author: zs
 * @Description: TODO
 * @DateTime: 2024/9/9 13:56
 **/
public class MonsterTest {
    @Test
    public void getMonster() {
        //创建容器ApplicationContext
        //该容器和容器配置文件关联
        //建议用接口接收,
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");
        //拿到容器是为拿到容器关联配置文件中的Java对象
        //可以通过getbean获取对应的对象,通过id,默认返回的类型是object,运行类型是monster
        /*这里的运行类型可以改为monter,
        因为getclass方法获取到该对象的运行类型为monter
        Object monster01 = ioc.getBean("monster01");*/
        Monster monster01 = (Monster) ioc.getBean("monster01");
        //完成向下转型之后可以分别获取属性
        //输出
        System.out.println("monster01="+ monster01 + monster01.getClass());
        System.out.println(monster01.getName()+monster01.getSkill()+monster01.getMonsterid());
        System.out.println("ok");


        //可以在获取的时候直接指定class类型
        Monster monster2 = ioc.getBean("monster01", Monster.class);
        System.out.println(monster2.getName()+monster2.getSkill()+monster2.getMonsterid());
    }
}

配置文件编写

<?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">
<!--    配置monster对象Javabean-->
<!--    在beans中可以配置多个bean-->
    <!--bean表示一个对象-->
<!--    class创建Java对象类的全路径-->
<!--    Spring 底层使用反射创建的-->
<!--    id表示Java对象在spring容器中的id,通过id可获得对象 id是唯一的-->
<!--    <property name="name" value="牛哈"/>用于给对象属性赋值,如果不改就是默认值-->
    <bean class="com.bean.Monster" id="monster01">
        <property name="monsterid" value="100"/>
        <property name="name" value="牛哈"/>
        <property name="skill" value="叫"/>
    </bean>
<!--    完成Java对象的创建-->

</beans>
package com.bean;

/**
 * @Author: zs
 * @Description: TODO
 * @DateTime: 2024/9/9 13:26
 **/
public class Monster {
    private Integer monsterid;
    private String name;
    private String skill;

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

    public Monster() {
    }
//无参构造器一定要写,因为反射要用
    public Integer getMonsterid() {
        return monsterid;
    }

    public void setMonsterid(Integer monsterid) {
        this.monsterid = monsterid;
    }

    public String getName() {
        return name;
    }

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

    public String getSkill() {
        return skill;
    }

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

}

classpathxmlapplicationcontext 为什么可以读取到beans.xml

 public void classPath() {
     File file = new File(this.getClass().getResource("/").getPath());
     System.out.println(file);
     //结果 /*D:\javaprogram\Spring\out\production\Spring*/
     //读取路径是out目录
 }

debug时会发现:beandefinitionmap会保存类的信息,但是不创建对象

当需要对象时,通过反射创建对象时用;

IOC容器

底层机制

当代码执行到

  ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");

时,容器已经创建完毕

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在beanfactory下的beandefinitionmap中就会保存配置文件中的信息,通过集合的方式 大小512

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图一

ConcurrentHashMap类型时集合,存bean节点配置信息

在beanDefinitionMap中有属性table

table为数组 类型为ConcurrentHashMap$node

因为是数组类型,可以存放很多bean对象信息,就是bean.xml配置文件

初始化为512, 会自动扩容

图二

通过hash算法monster01对象信息就保存在index = 217 位置

保存是以 ConcurrentHashMap$node类型

key值就是beans.xml文件中的id值

value 就是monster01对象的[属性/属性值/是否为懒加载]

属性值保存在propertyValueList,就是记录monster01对象的属性值

beandefinitionmap只保存了类信息,并不保存对象
在这里插入图片描述
在这里插入图片描述

类的对象用concurrenthashmap$node的类型保存,类型为基于泛型的,

因为保存类的对象时,会默认为单例所以存在这个位置

在beanfactory中的另一个属性singletonObjects

类型为concurrentHashMap

其中有个属性table 类型是concurrenthashmap$node

如果在beans.xml文件中配置的对象时单例的 就是初始化放在里边

当我们getbean(“id”)时,首先会在底层查beandifinitionmap中按照id 名字查询是为单例,如果为单例会在singletonobject[类似单例池的作用]查找并返回,若不是单例会通过动态反射机制创建一个对象放进去.
在这里插入图片描述

为了方便我们快速查找定位,直接存入id[就是在beans.xml文件中配置的bean的名称]值,

如果先查看自己定义了多少对象,通过beandefinitionnames方法查找

        //查看容器注入了那些bean对象  输出bean的id
        String[] beanDefinitionNames = ioc.getBeanDefinitionNames();
        for(String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }

原理模拟

手动开发一个简单的spring基于XML配置的程序

第一步引入jar包{dom4j}

xml文件用的是上边的deans.xml

类的代码

package com.applicationcontext;


import com.bean.Monster;
import com.sun.org.apache.bcel.internal.generic.NEW;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: zs
 * @Description: 实现spring的一个简单容器
 * 实现bean.xml文件的解析,并生成容器
 * @DateTime: 2024/9/10 12:09
 **/
public class ApplicationContext {
    private ConcurrentHashMap<String, Object> singletonobject =
        new ConcurrentHashMap<>();

    //构造器
    //接收一个容器的配置文件,该文件默认在src
    public ApplicationContext(String iocBeanXmlFile) throws DocumentException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        //1, 得到类加载路径"
        String path = this.getClass().getResource("/").getPath();

//        创建saxreader,它用于读取 XML 文件并将其转换为 Java 对象。
//        这个类是基于 SAX (Simple API for XML) 解析器的,
//        它允许你以流的方式读取 XML 文件,而不需要将整个文件加载到内存中,
        SAXReader saxReader = new SAXReader();

        //得到document对象
        Document document = saxReader.read(new File(path + iocBeanXmlFile));


        //得到rootdocument
        Element root = document.getRootElement();

        //获取bean元素
         Element bean = (Element) root.elements("bean").get(0);

         //获取id,属性,属性值,类的全路径
        String id = bean.attributeValue("id");
        String classPath = bean.attributeValue("class");
        List<Element> property = bean.elements("property");;

        //遍历
        Integer monsterId =Integer.parseInt(property.get(0).attributeValue("value"));

        String name = property.get(1).attributeValue("value");
        String skill = property.get(2).attributeValue("value");

//        使用反射创建对象
        Class<?> aclass = Class.forName(classPath);
        //这里o对象就是monster,
        Monster o =(Monster) aclass.newInstance();
        //给对象转化类型,赋值
        //1.反射赋值
//        Method[] declaredMethods = aclass.getDeclaredMethods();
//        for (Method declaredMethod : declaredMethods ) {
//            declaredMethod.invoke();
//        }
        //2.直接赋值
        o.setMonsterid(monsterId);
        o.setName(name);
        o.setSkill(skill);

        //将创建的对象放入容器中
        singletonobject.put(id, o);


    }



    public Object getBean(String id) {
        //获取容器中的对象
        return singletonobject.get(id);
    }

}

测试类的编写

package com.applicationcontext;

import com.bean.Monster;
import org.dom4j.DocumentException;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Author: zs
 * @Description: TODO
 * @DateTime: 2024/9/10 12:18
 **/
public class ApplicationContextTest {
    public static void main(String[] args) throws DocumentException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        ApplicationContext ioc = new ApplicationContext("beans.xml");

        Monster monster01 = (Monster)ioc.getBean("monster01");
        System.out.println(monster01);
        System.out.println(monster01.getClass());
        System.out.println(monster01.getName());

        System.out.println("ok");
    }
}

Spring管理bean-IOC

bean的配置方式1.基于xml文件配置的方式

​ 2.基于注解

bean的管理:1.创建bean对象

​ 2.注入属性

基于xml形式

1.通过类型获取

    <!--    配置monster 通过类型获取-->
    <bean class="com.bean.Monster">
        <!--
        1.当我们给某个bean对象设置属性时
          底层使用setter方法完成的
        -->
        <property name="monsterid" value="100"/>
        <property name="name" value="牛哈"/>
        <property name="skill" value="叫"/>
    </bean>
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");
        //直接传入class类型
        Monster bean = ioc.getBean(Monster.class);
        System.out.println("bean" + bean);
    }

要求类型获取bean,要求容器IOC中的同一个类的bean只能有一个,否则会抛出异常NouUniqueBeanDefinitionException

应用场景:在一个线程中只需要一个对象实例的情况(单例)

2.通过构造器配置bean

<!-- 配置monster对象,通过构造器-->
    <bean id="monster03" class="com.bean.Monster">
<!--        index索引表示构造器的第几个参数 从零开始计算-->
<!--        除了index 还可以通过name / type 来指定 
            类的构造器,不能有完全相同类型顺序的构造器
-->
        <constructor-arg value="200" index="0"/>
        <constructor-arg value="猴"  index="1"/>
        <constructor-arg value="变"  index="2"/>
    </bean>
   @Test
    public void setBeanByConstructor(){
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");

        Monster monster03 = ioc.getBean("monster03",Monster.class);
        System.out.println("monster03=" + monster03);
    }

通过P名称空间配置

//通过p名称空间来设置属性
@Test
public void setBeanByCP(){
    ApplicationContext ioc =
            new ClassPathXmlApplicationContext("beans.xml");

    Monster monster06 = ioc.getBean("monster06", Monster.class);
    System.out.println(monster06);
}
<!--    通过P名称空间配置-->
<!--    在编写代码时.p会报错,p空间没绑定-->
<!--    解决方法:光标放在爆红的地方,按alt+int 有时需要多试几次-->
    <bean id="monster06" class="com.bean.Monster"
        p:monsterid="500"
        p:name="红孩儿"
        p:skill="玩火"
    />
xmlns:p="http://www.springframework.org/schema/p"

引用其他bean

测试文件

    @Test
    public void setBeanByRef(){
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");
        MemberServiceImpl memberService = ioc.getBean("memberService", MemberServiceImpl.class);

        memberService.add();
    }

DaoImpl

public class MenberDAOImpl {
    //构造器.....
    public MenberDAOImpl() {
        System.out.println("MenberDAOImpl 构造器");
    }
//    完成添加
    public void add(){
        System.out.println("MemberDaoImpl add()方法");
    }
}

ServiceImpl

public class MemberServiceImpl {
    private MenberDAOImpl menberDao;

    public MenberDAOImpl getMenberDao() {
        return menberDao;
    }

    public void setMenberDao(MenberDAOImpl menberDao) {
        this.menberDao = menberDao;
    }

    public void  add(){
        System.out.println("service方法被调用");
        menberDao.add();
    }
}

配置文件

<!--    配置memberdaoimpl对象-->
    <bean class="com.dao.MenberDAOImpl" id="memberDAO"/>
<!--    配置menmberserviceimpl对象-->
<!--    ref="memdao" 表示 memberserviceimpl对象的属性memberDao引用的对象是id = memberDao
        的对象-->
<!--    体现出spring容器的依赖注入-->
    <bean class="com.service.MemberServiceImpl" id="memberService">
        <property name="menberDao" ref="memberDAO"/>
    </bean>

引用内部bean对象

<!--    配置memberserviceimpl-使用内部bean-->
    <bean class="com.service.MemberServiceImpl" id="memberService2">
        <property name="memberDao" >
            <bean class="com.dao.MemberDAOImpl"/>
        </property>
    </bean>

引用集合/数组类型的值赋值

list

<!--    配置master对象-->
    <bean class="com.bean.Master" id="master">
        <property name="name" value="老头"/>
<!--        给list属性赋值-->
        <property name="monsterList" >
            <list>
                <ref bean="monster06"/>
                <ref bean="monster03"/>
            </list>
        </property>
    </bean>
<!--    配置master对象-->
    <bean class="com.bean.Master" id="master">
        <property name="name" value="老头"/>
<!--        给list属性赋值-->
        <property name="monsterList" >
            <list>
<!--                引用的方式-->
                <ref bean="monster06"/>
                <ref bean="monster03"/>
<!--                内部-->
                <bean class="com.bean.Monster">
                    <property name="name" value="老鼠"/>
                    <property name="monsterid" value="100"/>
                    <property name="skill" value="toushi"/>
                </bean>
            </list>
        </property>
<!--        给map属性赋值-->
        <property name="monsterMap">
            <map>
                <entry>
                    <key>
                        <value>monster03</value>
                    </key>
                    <ref bean="monster03"/>
                </entry>
                <entry>
                    <key>
                        <value>monster06</value>
                    </key>
                    <ref bean="monster06"/>
                </entry>
            </map>
        </property>
        <property name="monsterSet">
            <set>
                <ref bean="monster03"/>
                <ref bean="monster06"/>
                <bean class="com.bean.Monster">
                    <property name="name" value="男"/>
                    <property name="monsterid" value="1000"/>
                    <property name="skill" value="吃"/>
                </bean>
            </set>
        </property>
<!--        array标签中的使用value 还是bean,ref 需要根据实际情况-->
        <property name="monsterName">
            <array>
                <value>怪</value>
                <value>女</value>
            </array>
        </property>
        <property name="pros">
            <props>
                <prop key="username">root</prop>
                <prop key="password">123456</prop>
                <prop key="ip">127.0.0.1</prop>
            </props>
        </property>
    </bean>

使用util名称空间创建list

<!--    配置bookstore对象-->
    <bean class="com.bean.BookStore" id="bookStore">
        <property name="bookList" ref="myBookList"/>
<!--            <list>-->
<!--                <value>三国演义</value>-->
<!--                <value>水浒传</value>-->
<!--                <value>你好</value>-->
<!--            </list>-->
<!--        </property>-->
    </bean>
<!--    定义一个utillist 并指定一个id 可以达到复用的效果-->
    <util:list id="myBookList">
        <value>三国演义2</value>
        <value>水浒传2</value>
        <value>你好2</value>
    </util:list>
xmlns:util="http://www.springframework.org/schema/util"

级联属性赋值

在编程中,级联属性赋值通常指的是在对象之间设置属性值时,将一个对象的属性值赋给另一个对象的对应属性。这种操作在面向对象编程中很常见,尤其是在处理具有复杂关系的对象时。级联赋值可以简化代码,避免重复编写相同的赋值语句。

以Java为例,假设我们有两个类 PersonAddress,其中 Person 类包含一个 Address 类型的属性:

public class Address {
    private String street;
    private String city;
    private String zipCode;

    // 构造器、getter和setter省略
}

public class Person {
    private String name;
    private Address address;

    // 构造器、getter和setter省略
}

如果我们想要级联地为 Person 对象的 address 属性赋值,可以这样做:

public class Main {
    public static void main(String[] args) {
        Address address = new Address();
        address.setStreet("123 Main St");
        address.setCity("Anytown");
        address.setZipCode("12345");

        Person person = new Person();
        person.setName("John Doe");
        person.setAddress(address); // 级联赋值

        // 现在person对象有了一个包含地址信息的address属性
    }
}

emp类创建

package com.bean;

/**
 * @Author: zs
 * @Description: TODO
 * @DateTime: 2024/9/10 17:01
 **/
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;
    }

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

dept类创建

package com.bean;

/**
 * @Author: zs
 * @Description: TODO
 * @DateTime: 2024/9/10 17:00
 **/
public class Dept {
    private String name;

    public Dept() {
    }

    public String getName() {
        return name;
    }

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

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

级联配置文件

<!--    配置dept-->
    <bean class="com.bean.Dept" id="dept"/>
<!--    配置emp-->
    <bean class="com.bean.Emp" id="emp">
        <property name="name" value="jj"/>
        <property name="dept" ref="dept"/>
<!--        这我希望给dept的name属性赋值-->
        <property name="dept.name" value="开发部门"/>
    </bean>

通过静态工厂获取对象

创建一个静态工厂类

package com.factory;

import com.bean.Monster;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author: zs
 * @Description: TODO
 * @DateTime: 2024/9/10 17:22
 **/
public class MyStaticFactory {
    private static Map<String, Monster> monsterMap;
    //使用static代码块进行初始化
    //static代码块特点:随着类的加载而加载,并且只加载一次
    static {
        monsterMap = new HashMap<>();
        monsterMap.put("monster01", new Monster(1000,"你啦","跑"));
        monsterMap.put("monster02",new Monster(100000,"你","走走走"));

    }

    //提供一个方法,返回monster对象
    public static Monster getMonster(String key) {
        return monsterMap.get(key);
    }
}

配置文件

<!--    配置一个monster对象-->
<!--    通过静态工厂类获取/配置bean-->
<!--    class 是静态工厂的全路径-->
<!--    foctory-mehtod 表示是静态工厂用哪个方法返回对象-->
<!--    constructor-arg value 返回静态工厂的那个对象-->
    <bean id="my_monster01" class="com.factory.MyStaticFactory"
          factory-method="getMonster">
        <constructor-arg value="monster02"/>
    </bean>

不管获取多少次都是通一个对象

动态实例工厂

<!--    配置monster对象-->
    <bean id="myInstanceFactory" class="com.factory.MyInstanceFactory"/>
    <bean id="my_monster02" factory-bean="myInstanceFactory" factory-method="getMonster">
        <constructor-arg value="monster03"/>

    </bean>

通过factory bean

package com.factory;

import com.bean.Monster;
import org.springframework.beans.factory.FactoryBean;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author: zs
 * @Description: TODO
 * @DateTime: 2024/9/11 12:55
 **/
public class MyFactoryBean implements FactoryBean<Monster> {

    private String key;//就是配置时,指定获取的对象
    private Map<String, Monster> monsterMap;

    {
        monsterMap = new HashMap<String, Monster>();
        monsterMap.put("monster05", new Monster(500,"反复","请求"));
        monsterMap.put("monster06", new Monster(600,"ff","qq"));

    }

    public void setKey(String key) {
        this.key = key;
    }

    public Monster getObject() throws Exception {
        return monsterMap.get(key);
    }

    public Class<?> getObjectType() {
        return Monster.class;
    }
    public boolean isSingleton() {
        return true;
    }
}

<!--    配置factorybean-->
    <bean id="my_monster05" class="com.factory.MyFactoryBean">
        <property name="key" value="monster05"/>
    </bean>

bean的重用

<bean id="monster11" class="com.hspedu.spring.beans.Monster" parent="monster10"/>

或者

使用abstract = true 抽象用来继承

但是设置之后本类不能被实例化

bean创建顺序

默认是按照编写的顺序

当有依赖对象时,会先创建被依赖的对象

ioc是一个整体,先创建对象最后完成依赖关系 除depende之外

bean的单例和多实例

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

多例设置

添加标签
scope = "prototype"

设置多实例时,实例只会在执行getbean时创建

单例模式默认是lazy-init= false;不是懒加载,所以容器一加载对象就会创建

bean的生命周期

bean对象的创建是由jvm完成的

1,执行构造器

2,执行set方法

3,调用bean的初始化方法

4,使用bean

5,容器关闭时,调用销毁方法

bean的后置处理器

这个对象会在bean初始化方法调用之前和调用之后调用

功能可以操作整个bean文件的对象,就像一个切面

在Spring框架中,Bean后置处理器(Bean Post Processor)是一种特殊的组件,它允许你在Spring容器初始化任何bean之后,但在bean的属性被设置之后进行额外的处理。Bean后置处理器可以用来修改bean的定义、属性值,或者根据特定的逻辑决定是否要返回一个代理而不是原始bean实例。

关键特点:

1.实现接口:Bean后置处理器需要实现 org.springframework.beans.factory.config.BeanPostProcessor 接口。

两个主要方法

  • postProcessBeforeInitialization(Object bean, String beanName):在bean的初始化方法(如@PostConstruct注解的方法或InitializingBean接口的afterPropertiesSet方法)之前调用。
  • postProcessAfterInitialization(Object bean, String beanName):在bean的初始化方法之后调用。

3.作用时机:Bean后置处理器在Spring容器的bean生命周期的特定点被调用,允许开发者在bean完全初始化之前和之后执行自定义逻辑。

4.返回值:这两个方法都可以返回bean本身或一个代理对象。如果返回null,则Spring容器会忽略该bean,不再进行后续处理。

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 在bean初始化之前可以进行的操作
        System.out.println("Before initialization: " + beanName);
        return bean; // 返回bean本身或修改后的bean
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 在bean初始化之后可以进行的操作
        System.out.println("After initialization: " + beanName);
        return bean; // 返回bean本身或修改后的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后置处理器 -->
    <bean id="myBeanPostProcessor" class="com.example.MyBeanPostProcessor"/>

    <!-- 定义其他bean -->
    <bean id="myService" class="com.example.MyService">
        <!-- bean的配置 -->
    </bean>

    <!-- 其他bean定义 -->

</bean
使用场景:
  • 修改bean属性:在bean初始化前后修改其属性值。
  • 条件性地返回代理:根据某些条件返回一个代理对象,例如用于AOP(面向切面编程)。
  • 资源释放:在bean销毁之前执行清理工作。
  • 日志记录:记录bean的创建和销毁过程。

通过过程文件给bean注入值

<!--    配置monster-->
    <context:property-placeholder location="classpath:my.properties"/>
    <bean class="com.bean.Monster" id="monster10">
        <property name="monsterid" value="${monsterid}"/>
        <property name="name" value="${name}"/>
        <property name="skill" value="${skill}"/>
    </bean>

创建一个配置文件

monsterid=1000
name=jack
skill=heal

自动装配bean

bytype------->通过类型

<!--    配置orderdao orderservice-->
    <bean class="com.dao.OederDao" id="oederDao"/>
<!--    autowire = "bytype" orderservice时通过类型的方式给对象属性自动完成赋值-->
<!--    作用:在bean文件中寻找有没有orderdao类型的对象,有的化,就会自己完成装配-->
<!--    注意bean文件中不能有两个orderfao的类型-->
    <bean  autowire="byType" class="com.service.OrderService" id="orderService"/>
<!--        手动装配-->
<!--        <property name="oederDao" ref="oederDao"/>-->

    <bean autowire="byType" class="com.web.OrderAction" id="orderAction"/>

创建三个文件为:dao,service,selvet

byname---->通过名字完成自动装配

按照setxxx方法set后边的名字

spring EL表达式

界定符#{}

基于注解配置bean

基本使用

在Java编程中,注解(Annotations)是一种用于为代码提供元数据的机制。注解不会直接影响代码的执行逻辑,但可以被编译器、其他工具或运行时环境读取,从而提供额外的信息或指导。注解在很多方面被广泛使用,包括但不限于依赖注入、事务管理、日志记录、安全性检查等。

注解的基本概念:

  1. 1.声明注解:使用@interface关键字声明一个新的注解类型。

java

public @interface MyAnnotation {
    String value();
}
  1. 2.使用注解:在类、方法、变量等元素上使用注解。

java

@MyAnnotation(value = "Example")
public class ExampleClass {
    // ...
}
  1. 3.注解的保留策略:定义注解的生命周期,即它在源代码、字节码或运行时是否可用。通过@Retention注解指定。

java

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

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    // ...
}
  1. 4.注解的适用目标:通过@Target注解指定注解可以应用的目标类型。

java

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

@Target(ElementType.METHOD)
public @interface MyAnnotation {
    // ...
}
  1. 5.注解的继承:注解默认不会被子类继承,但可以通过@Inherited注解指定注解可以被继承。

java

import java.lang.annotation.Inherited;

@Inherited
public @interface MyAnnotation {
    // ...
}

常见的注解使用场景:

  • 依赖注入:Spring框架使用@Autowired@Qualifier等注解来实现依赖注入。

java

@Autowired
private MyService myService;
  • 事务管理@Transactional注解用于声明方法或类的事务边界。

java

@Transactional
public void performOperation() {
    // ...
}
  • RESTful Web服务:JAX-RS和Spring MVC使用注解来定义资源和路由。

java

@RestController
@RequestMapping("/api")
public class MyController {
    @GetMapping("/hello")
    public String sayHello() {
        return "Hello, World!";
    }
}
  • 单元测试:JUnit和TestNG使用注解来标记测试方法。

java

import org.junit.Test;
import static org.junit.Assert.*;

public class ExampleTest {
    @Test
    public void testAddition() {
        assertEquals(2, 1 + 1);
    }
}
  • 日志记录:使用如@Log@Log4j等注解来简化日志记录。

java

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyClass {
    private static final Logger logger = LoggerFactory.getLogger(MyClass.class);

    public void myMethod() {
        logger.info("This is an info message");
    }
}

注解处理:

  • 编译时处理:注解处理器可以在编译时生成额外的源代码、资源文件或进行其他操作。
  • 运行时处理:在运行时,可以通过反射API读取注解信息并根据这些信息执行特定的逻辑。

基于注解的方式配置bean,主要是项目开发中的组件,比如Controller、Service、和Dao。

组件注解的形式有

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

spring框架的注解需要引入的jar包

1.spring-aop-5.3.8.jar

仿写注解

package com.annotation;

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

/**
 * @Author: zs
 * @Description: TODO
 * @DateTime: 2024/9/12 13:29
 **/
//规定当前自定义的的注解可以作用于那些类型
@Target(ElementType.TYPE)
//指定注解的作用范围
@Retention(RetentionPolicy.RUNTIME)

public @interface ComponentScan {
    //表示我们的注解可以传入一个value属性
    String value() default "";
}

package com.annotation;

import org.springframework.stereotype.Component;

/**
 * @Author: zs
 * @Description: TODO
 * @DateTime: 2024/9/12 16:01
 **/
@ComponentScan(value = "com.component")
public class SpringConfig {

}
package com.annotation;

import com.applicationcontext.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.security.auth.login.Configuration;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: zs
 * @Description: TODO
 * @DateTime: 2024/9/13 12:46
 **/
public class SpringApplicationContext {
    //这个类的作业是模仿容器
    //需要拿到class类型,因为要使用反射

    //定义一个属性
    private Class configClass;
    //因为在框架内,bean的信息要放入容器的hashMap
    //所以这一要创建一个map
    private final ConcurrentHashMap<String, Object> ioc =
            new ConcurrentHashMap<>();

    //构造器,在初始化容器时,选用传入beans.xml文件
    //
    public SpringApplicationContext(Class configClass) {

        //配置类不止在构造器使用,跨方法使用
        //拿到配置类的class类型,就可以到的注解
        //就可以拿到注解的value里的路径信息
        this.configClass = configClass;
//        System.out.println(this.configClass);
        //获取要扫描的包
        //1.先得到配置类的注解
        ComponentScan componentScan =
                (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //通过comonentScan得到value
        String path = componentScan.value();
        //获得value下的所有class的信息
        //1.得到类的加载器
        ClassLoader classLoader = ApplicationContext.class.getClassLoader();
        //2.通过加载器获得要扫描包的资源路径,class文件的信息不是在src而是在out
        //Java中解析路径识别不出. 所有要把查找出来com. 的路径转化为com/
        path = path.replace('.', '/');
        URL resource = classLoader.getResource(path);
        //3.将要加载的资料(.class)路径下的文件进行遍历
        //注意:如果要有子目录,需要进行递归
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                System.out.println(f.getAbsolutePath());
                String fileAbsolutePath = f.getAbsolutePath();

                //过滤,只处理.class文件
                if (fileAbsolutePath.endsWith(".class")) {

                    //获取全类名,反射对象,放入容器中
                    //1.获取类名
                    String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.lastIndexOf(".class"));
                    //2.获取类的路径
                    String classFullName = path.replace("/", ".") + "." + className;

                    //判断文件是不是需要注入容器中
                    try {
                        //这时我们得到该类的class对象
                        //forname 可以通过反射加载类对象
                        //loadclass 也可以
                        //但是forname会调用该类的静态方法,
                        Class<?> aClass = classLoader.loadClass(classFullName);
                        if(aClass.isAnnotationPresent(Component.class) || aClass.isAnnotationPresent(Controller.class)
                            || aClass.isAnnotationPresent(Service.class) ||aClass.isAnnotationPresent(Repository.class)) {

                            //指定value 分配id
                            if(aClass.isAnnotationPresent(Component.class)) {
                                Component component= aClass.getDeclaredAnnotation(Component.class);
                                String id = component.value();
                                if (!"".endsWith(id)) {
                                    className = id;
                                }
                            }
                            
                            //这里为什么用forname  会调用静态方法,会把类的联系加载进去
                            Class<?> aClazz = Class.forName(classFullName);
                            Object instance = aClazz.newInstance();
                            //类的小写首字母作为id
                            ioc.put(StringUtils.uncapitalize(className), instance);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }


                }
            }
        }


    }

    //编写方法返回容器对象
    public Object getBean(String beanName) {
        return ioc.get(beanName);
    }
}

注解自动装配

1.基于注解配置bean,也可实现自动装配,使用的注解是:@AutoWired 或者 @Resource

@AutoWired 的规则说明:

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

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

  3. 操作流程

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

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

@Controller
public class UserAction {
    @Autowired
    private UserService userService;
    public void sayOk() {
        System.out.println("UserAction");
        userService.hi();
    }
}
@Service
public class UserService {
    public void hi(){
        System.out.println("hi");
    }
}

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

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

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


    <bean class="com.component" id="userService200"/>
    <bean class="com.component" id="userService300"/>
</beans>

3.@Resource 的规则说明:

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

使用type时,对象必须是单例的]

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

建议,不管是@AutoWired 还是 @Resource,都应谨慎使用,确保理解其自动装配的规则和潜在的异常情况,以避免运行时错误。

@Controller
public class UserAction {
    @Resource(name = "userService200")
    private UserService userService;
    public void sayOk() {
        System.out.println("UserAction");
        System.out.println("useraction自动装配的" + userService);
        userService.hi();
    }
}

配置文件不变

泛型依赖注入

泛型:

  • 1.普通成员可以使用泛型(属性、方法)
  • 2.使用泛型的数组,不能初始化
  • 3.静态方法中不能使用类的泛型
  • 4.泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
  • 5.如果在创建对象时,没有指定类型,默认为Object

aop

动态代理

动态代理是 Java 中一种非常强大的特性,它允许在运行时创建一个实现了某个接口的代理对象,这个代理对象可以作为目标对象的替代品。动态代理在很多场景下非常有用,比如:

  1. 1.日志记录:在方法调用前后添加日志记录。
  2. 2.事务管理:在方法调用前后管理数据库事务。
  3. 3.安全检查:在方法调用前进行权限验证。
  4. 4.缓存:缓存方法的返回结果,避免重复计算。
  5. 5.延迟加载:实现对象的延迟加载机制。
  6. 6.远程方法调用:在远程方法调用(RMI)中,动态代理可以用来封装远程调用的细节。

动态代理的实现

在 Java 中,动态代理主要通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来实现。

  • Proxy 类用于生成动态代理实例。
  • InvocationHandler 接口定义了代理实例上的方法调用处理程序。
package com.aop;

import java.lang.reflect.*;
import java.util.Arrays;

/**
 * @Author: zs
 * @Description: TODO
 * @DateTime: 2024/9/14 15:22
 **/
public class ProxyProvider {
    //创建一个目标对象,这个对象要实现接口
    private SmartAnimal smart_traget;
    public ProxyProvider(SmartAnimal smart_traget) {
        this.smart_traget = smart_traget;
    }
    //创建一个代理对象,我们要用代理对象如执行目标对象的方法
    public SmartAnimal getProxy() {
        //1.类加载器
        ClassLoader classLoader = smart_traget.getClass().getClassLoader();
        //2.目标接口信息
        Class<?>[] interfaces = smart_traget.getClass().getInterfaces();
        //3.创建一个InvocationHandler
        InvocationHandler invocationHandler = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    //横切关注点
                    System.out.println("方法执行前" + method.getName() + "参数" + Arrays.asList(args));
                    //返回执行结果

                    //使用反射方法调用方法

                    result = method.invoke(smart_traget, args);

                    //横切接入点
                    System.out.println("方法执行后" + method.getName() + "结果" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                    //如果执行方发时,出现异常,就会进入catch
                    System.out.println("方法" + method.getName() + "执行异常 结果" + e.getClass().getName());
                } finally {
                    //最中都会执行到这里
                    //横切关注点,最终通知
                    System.out.println("方法" + method.getName() + "名");
                }

                return result;
            }
        };


        //创建代理对象----动态代理类型
        SmartAnimal proxy = (SmartAnimal)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxy;
    }

}

面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,旨在将横切关注点(cross-cutting concerns)从业务逻辑中分离出来,以提高模块化。横切关注点是指那些影响多个类的问题,比如日志记录、事务管理、安全性等。AOP 通过创建切面(aspects)来实现这一点,切面可以定义在何处以及如何将额外的行为插入到程序代码中。

AOP 的关键概念:

  • 1.切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是切面的一个典型例子。
  • 2.连接点(Join Point):在程序执行过程中某个特定的点,比如方法的调用或异常的抛出。在Spring AOP中,连接点总是方法的执行点。
  • 3.通知(Advice):在切面的某个特定的连接点上执行的动作。不同类型的通知包括“前置通知”(在方法调用之前)、“后置通知”(在方法成功执行之后)、“异常通知”(在方法抛出异常后)、“最终通知”(无论方法执行成功还是失败都会执行)和“环绕通知”(围绕方法执行的通知)。
  • 4.引入(Introduction):允许我们向现有的类添加新的方法或属性。
  • 5.织入(Weaving):把切面应用到目标对象并创建新的代理对象的过程。织入可以在编译时、加载时或运行时完成。

AOP 在 Spring 中的实现:

Spring AOP 是 Spring 框架的一部分,它使用代理模式来实现 AOP。Spring AOP 只支持方法级别的连接点,这意味着它只能在方法执行时进行拦截。

  • 使用 @Aspect 注解:在 Spring 中,你可以使用 @Aspect 注解来定义一个切面类。
  • 定义通知:使用 @Before@After@AfterReturning@AfterThrowing@Around 等注解来定义不同类型的通知。
  • 配置 AOP:在 Spring 配置中启用 AOP 支持,并指定哪些类或方法应用切面。
    <context:component-scan base-package="com.asp"/>
<!--    开启基于注解的AOP-->
    <aop:aspectj-autoproxy/>

切入表达式:

通过表达式的方式定位一个或多个具体的连接点

execution[[权限修饰符] [返回类型] [简单类名/全类名] [方法名] [参数列表] ]

切入表达式的注意细节

1.切入表达式也可以指向类的方法,这时切入表达式会对该类/对象生效。
2.切入表达式也可以指向接口的方法,这时切入表达式会对实现了接口的类/对象生效。
3.切入表达式也可以对没有实现接口的类,进行切入.

动态代理机制1.jdk的proxy 面向接口 2.CGlib 是面向父类

两个动态代理的区别

  • 1.JDK动态代理是面向接口的,只能增强实现类中接口中存在的方法。CGlib是面向父类的,可以增强父类的所有方法。
  • 2.JDK得到的对象是JDK代理对象实例,而CGlib得到的对象是被代理对象的子类。

在类和切面类在一个包下可以简写类名

JoinPoint

public void beforeMethod(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.This(); // 获取代理对象自己
}

Aop切面优先级

@order(value=n) 来控制 n 值越小,优先级越高。

运行原理类似filter

基于xml配置Aop

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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">


<!--    使用xml,完成aop编程-->
    <bean class="com.aopxml.SmartAnimalAspect" id="animalAspect"/>
<!--    配置一个smartdog-->
    <bean class="com.aopxml.SmartDog" id="smartDog"/>
<!--    配置切面类-->
    <aop:config>
<!--        这里指定切面对象-->
<!--        <aop:aspect ref="animalAspect" order="10"/>-->
        <!--配置切入点-->
        <aop:pointcut id="myPointCut" expression="execution(public float com.aopxml.SmartDog.getSum(float,float))"/>
<!--        这里指定切面对象-->
        <aop:aspect ref="animalAspect" order="10">
            <!--配置前置通知-->
            <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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值