小白学Java38:(Spring)

本文介绍了Spring框架的基本概念,包括依赖注入(DI)、控制反转(IOC)、面向切面编程(AOP)等核心特性,并详细讲解了如何搭建开发环境、配置文件的编写及各种注入方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring是什么?

使用Spring

搭建使用环境

  • 导入依赖的jar包
		 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>
  • 新建配置文件\spring-context.xml
    写xml头
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
  • 构建bean映射
<bean id="xxx" class="/classPath/"></bean>
  • 测试
//启动公厂
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
//获取对象
        Shuiguo shuiguo = (Shuiguo) context.getBean("shuiguo");

名词解释:

  • schema:规范
    在这里插入图片描述

IOC:控制反转

之前,比如Service里需要一个Dao来实现底层的数据库交互问题,所以,必须要在Service中new一个Dao,但是如果dao发生了更换,这个时候就需要修改Service类的源码,造成一些不必要的麻烦

现在可以使用依赖反转,先在Service中创建一个未赋值的Dao对象,然后创建它的Get/Set方法,然后再配置中让Spring赋值给Service一个Dao对象

//Servic类中
private Dao dao;

    public Dao getDao() {
        return dao;
    }

    public void setDao(Dao dao) {
        this.dao = dao;
    }
  //配置文件中
 <bean id="dao" class="Dao.userDao"></bean>
 <bean id="service" class="Service.UserService">
 //注入属性值,满足依赖关系
        <property name="dao" ref="dao"/>
  </bean>

IOC思想:

之前是谁需要谁创建,是一个比较主动的事情
现在使用IOC之后,变成了框架为其自动创建,变主动为被动,
实现这种思想的主要方法是注入:

DI注入

Set注入

所谓set注入就是依赖类属性的set方法来进行注入赋值
注入格式:

 <bean id="自定义对象名" class="对象类路径">
<!--简单数据类型的注入-->
    <property name="属性名" value=""/>
 </bean>
//我现在要创建一个用户类,来演示属性注入
public class User {
//基础类型
    private Integer id;
    private String password;
    private String sex;
    private Integer age;
    private Date bortdate;
    //数组类型
    private String[] hobbys;
    //集合类型
    private Set<String> phones;
    private List<String> names;
    private Map<String,String> countries;
    private Properties files;
    //自建类型
    private Address address;

   //get/set方法就先省略了,太占位置了,要知道一定要有就行了
<!--        简单数据类型的注入-->
		<property name="id" value="01" />
        <property name="password" value="1234"/>
        <property name="sex" value="man"/>
        <property name="age" value="19"/>
        <property name="bortdate" value="2020/12/12 10:20:20"/>
 <!--数组注入-->
        <property name="hobbys">
            <array>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>烫头</value>
            </array>
        </property>
<!--集合注入-->
        <!--        list集合-->
        <property name="names">
            <list>
                <value>老大</value>
                <value>老二</value>
                <value>老三</value>
            </list>
        </property>
        <!--        set集合-->
        <property name="phones">
            <set>
                <value>1234312</value>
                <value>342452</value>
            </set>
        </property>
        <!--        map集合-->
        <property name="countries">
            <map>
                <entry key="zh" value="中国"></entry>
                <entry key="en" value="英国"></entry>
            </map>
        </property>
        <!--        properties配置文件读取的集合-->
        <property name="files">
            <props>
                <prop key="url">img/01.jpg</prop>
                <prop key="username">root</prop>
                <prop key="password">1234</prop>
            </props>
        </property>
<!--        自建类型的注入-->
        <property name="address" ref="add"></property>
    </bean>
    <bean id="add" class="user.Address">
        <property name="id" value="1"></property>
        <property name="address" value="dgakldfaidhfak"></property>
 </bean>

Set注入总结:

set注入是靠框架回调对象对应的set方法,来传参进去,实现注入的
一般数据类型使用的value
Date类型格式为"YYYY/MM/dd hh:mm:ss"

  • <property name="属性名" value="值"/>

数组类型是用Array 里面创建Value

  • <property name="属性名">
    <array>
    <value>值1</value>
    <value>值2</value>
    <value>值3</value>
    </array>
    </property>

集合类型

  • List集合 先list然后里面value
    • <property name="属性名">
      <list>
      <value>值1</value>
      <value>值2</value>
      <value>值3</value>
      </list>
      </property>
  • set集合 先set 然后value
    • <property name="属性名">
      <set>
      <value>值1</value>
      <value>值2</value>
      <value>值3</value>
      </set>
      </property>
  • map集合 先map 然后里面是entry 每一个entry有key和value两个属性
    • <property name="countries">
      <map>
      <entry key="zh" value="中国"></entry>
      <entry key="en" value="英国"></entry>
      </map>
      </property>
  • properties 配置文件, 先props 再prop 有key属性 然后标签内写值
    • <property name="files">
      <props>
      <prop key="url">img/01.jpg</prop>
      <prop key="username">root</prop>
      <prop key="password">1234</prop>
      </props>
      </property>
  • 自建类型 需要先创建bean 然后使用ref注入进去
    • <bean id="add" class="user.Address">
      <property name="id" value="1"></property>
      <property name="address" value="dgakldfaidhfak">
      </property>
      </bean>
      - 注入 <property name="address" ref="add"></property>

构造注入(了解)

调用构造函数来实现注入

//创建学生类
public class Student {
    private int id;
    private String name;
    private String sex;
    private int age;

    public Student(int id, String name, String sex, int age) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
}
//构造注入
 <bean id="stu" class="user.Student">
        <constructor-arg name="id" value="1"/>
        <constructor-arg name="name" value="张哈哈"/>
        <constructor-arg name="sex" value=""/>
        <constructor-arg name="age" value="1"/>
    </bean>

自动注入(了解)

<bean id=“service” class=“Service.UserService” autowire="byName/byType">
使用自动注入的时候,会根据需要注入的值的对象名,或者对象类型来进行查找自动注入

单例/多例

使用Spring-Context默认是用单例模式创建对象的
使用scopr来控制单例或者多例

  • scope=“prototype” 多例模式创建对象
  • scope=“singleton”:单例模式创建对象

复杂对象的创建

如connection 数据库连接对象,这种没有办法直接new的对象,就需要用到

  1. 首先创建复杂对象的工厂bean类,使其实现FactoryBean接口,泛型选择需要使用的对象类,如(Connection)
  2. 实现其中的三个方法
    • getObject()内写获取对象的方法
    • getObjectType() 内写获取对象的类信息
    • isSingleton() 是否以单例模式创建对象
//创建获取Connection的工厂Bean类:
public class MyFactoryBean implements FactoryBean<Connection> {

    @Override
    public Connection getObject() throws Exception {
        Class.forName("com.mysql.cj.jdbc.Driver");
        return DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb?serverTimezone=UTC", "root", "1234");
    }

    @Override
    public Class<?> getObjectType() {
        return Connection.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}
创建Conn的Bean对象获取
 <bean id="conn" class="factoryBean.MyFactoryBean"></bean>

复杂对象的创建总结

此方式获取的对象为工厂Bean中getObject()中获取的对象
如果需要获取工厂Bean本身的对象,
MyFactoryBean myFactoryBean = (MyFactoryBean)context.getBean("&conn");

Spring工厂特性

默认采用饿汉式的单例模式,
工厂一旦启动,将会直接创建好配置文件中的所有Bean对象,提高程序的效率,避免多次IO,减少对象的创建时间

生命周期

  • 单例
    工厂启动时立马创建,生命周期流程为:
    构造>set>初始化
    工厂关闭时执行销毁

  • 多例
    在需要调用时创建,生命周期流程为
    构造>set>初始化
    由JVM垃圾回收机制销毁

AOP:

先了解下代理设计模式

静态代理

举个例子:房东想要把房子租出去,他有个租房的方法,里面要做发布房源,带人看房,然后签合同,收房租,等等,对于房东来说,他的核心的业务其实只有签合同收房租,这时候就可以用代理来帮他先做发布房源,带客看房的操作,业务代码如下


//原本是这样
public class Fangdong implements FangdongService{
    public void zufang(){
        System.out.println("发布房源");//辅助功能
        System.out.println("带人看房");//辅助功能
        System.out.println("签合同");//核心功能
        System.out.println("收房租");//核心功能
    }
}

//引进代理之后  中介类
public class Zhongjie implements FangdongService {
    Fangdong fangdong = new Fangdong();
    public void zufang(){
        System.out.println("发布房源");//辅助功能
        System.out.println("带人看房");//辅助功能
        //调用核心功能
        fangdong.zufang();
    }
}
//房东类
public class Fangdong implements FangdongService{
    public void zufang(){
        System.out.println("签合同");
        System.out.println("收房租");
    }
}

动态代理模式

JDK实现动态代理(基于接口)
  1. 首先new一个核心业务类
  2. 然后使用匿名内部类的方式,重写一个InvocationHandler类,在内部实现其代理的功能
  3. 使用Proxy.newProxyInstance(本类的类的类加载器,核心业务类的类接口,第二步创建的类对象) 来动态生成代理类
  4. 返回对象为Object,但是可以强转为核心类的同类对象
    代码如下
//原生JDK的动态代理实现

        //目标 :核心功能的类
        FangdongService fangdong = new Fangdong();

        //额外功能
        InvocationHandler ih = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("发布房源");
                System.out.println("带人看房");
                fangdong.zufang();
                return null;
            }
        };
        //动态生成代理类
        FangdongService proxy = (FangdongService) Proxy.newProxyInstance(TestZufang.class.getClassLoader(),
                fangdong.getClass().getInterfaces(),
                ih);

        proxy.zufang();
CGlib实现动态代理(基于继承)
  1. 首先new一个核心业务类
  2. 创建一个Enhancer类对象,
    1.设置继承的父类setSuperclass
    2.设置回调setCallback,并传入匿名内部类的new org.springframework.cglib.proxy.InvocationHandler() 对象,对象内重写invoke方法,实现代理功能
  3. 使用enhancer.create()动态实现代理类
  4. 返回对象为Object,但是可以强转为2.1设置的父类
    代码如下
//CGlib实现动态代理
		//目标 :核心功能的类
        FangdongService fangdong = new Fangdong();
        //创建
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Fangdong.class);

        enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                System.out.println("发布房源");
                System.out.println("带人看房");
                fangdong.zufang();
                return null;
            }
        });

        //动态生成代理类
        Fangdong zhongjie = (Fangdong) enhancer.create();
        zhongjie.zufang();

原理还是不太明白先了解吧,后续慢慢摸清原理

AOP面向切面编程

这是一种新的编程思想,个人理解类似于嫁接(可能不太恰当)

  1. 先定义好一个主业务类,然后自定义一个前置通知类,实现MethodBeforeAdvice接口,实现其before()方法,在内添加额外功能的实现
  2. 在配置文件(Spring-context.xml)
    1.引入 xmlns:aop="http://www.springframework.org/schema/aop"
    2.在xsi:schemaLocation中添加http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    3.写好核心业务类的bean,和附加功能bean
  3. 使用<aop:config>做切入配置
    1.先定位切入点,和需要切入的方法<aop:pointcut id="自定义切入点名称" expression="execution(切入方法名)"/>
    2.再定义切入的内容,和切入到哪里
    <aop:advisor advice-ref="附加功能bean" pointcut-ref="切入点名称"/>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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-2.5.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
">

    <!--//核心业务-->
    <bean id="userServiceImpl" class="Service.UserService"/>

    <!--    附加功能-->
    <bean id="before" class="advice.MyBeforoAdvice"/>


<!--    编织-->
<aop:config>
<!--    定位切入点,  需要切入的方法-->
    <aop:pointcut id="qianzhitongzhi" expression="execution(* insert())"/>
<!--    切入的内容,需要切入到哪里-->
    <aop:advisor advice-ref="before" pointcut-ref="qianzhitongzhi"/>
</aop:config>

</beans>

通知类

前置通知

  • MethodBeforeAdvice
//自定义前置通知类,实现MethodBeforeAdvice接口并重写Before方法
public class MyBeforoAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        //前置通知
        System.out.println("事务控制");
        System.out.println("日志打印");
    }
}

后置通知 AfterAdvice(父类)

  • 后置通知(有异常不执行,方法会因为异常而结束,无返回值) AfterReturningAdvice(子类)
public class MyAfterAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("这是一个后置通知");
    }
}
  • 异常通知 ThrowsAdvice(子类)
//在核心中抛出异常时将会执行此Advice
public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Exception ex) {
        System.out.println("在核心业务中抛出异常时将会执行此Advice");
    }

}

环绕通知

  • MethodInterceptor
public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {

        System.out.println("核心开始执行~~");
        //执行核心代码,需有返回值
        Object o = invocation.proceed();
        System.out.println("核心执行结束~~~");

        return o;//返回返回值
    }
}

通配切入点

首先明确一点,<aop:pointcut id="自定义切入点名称" expression="execution(切入位置)"/>

这个切入位置的书写方式为
execution(返回值 包名.类名.方法名(参数))

几种通配切入点的写法
  1. 匹配参数
<aop:pointcut id="p1" expression="execution(* *(user.User))"/>
  1. 匹配方法名(无参)
<aop:pointcut id="p1" expression="execution(* uodate())"/>
  1. 匹配方法名(任意参数)
<aop:pointcut id="p1" expression="execution(* uodate(..))"/>
  1. 匹配返回值类型
<aop:pointcut id="p1" expression="execution(Integer *(..))"/>
  1. 匹配类名
<aop:pointcut id="p1" expression="execution(* user.User.*(..))"/>
  1. 匹配包名
<aop:pointcut id="p1" expression="execution(* user.*.*(..))"/>
  1. 匹配包名及其子包名
<aop:pointcut id="p1" expression="execution(* user..*.*(..))"/>
<!-- user包及其下面的子包的所有类的所有方法 -->

AOP原理

JDK还是CGlib

目标业务类有接口,就用jdk没有接口就用cgLib

后处理器

  1. 自定义后处理器

新建类使其实现BeanPostProcessor接口,并重写其中的两个方法

public class MyBeanPostProcessor implements BeanPostProcessor {

    //自定义后处理器,用来理解后处理器的工作原理
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("该后处理程序,运行在set之后,初始化之前");
        System.out.println("后处理接收到的Bean:" + bean + ",beanName:" + beanName);
        return bean;
    }


    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("该后处理程序,运行在初始化之后");
        System.out.println("后处理接收到的Bean:" + bean + ",beanName:" + beanName);

        return bean;
    }
}

事务

注解

Bean声明的注解

  1. @Service Service专用注解
  2. @Repository Dao专用注解
  3. @Controller Controller 专用注解
  4. @Component 通用注解

注解的使用方式都是一样的
例如 在一个Service类前打上@Service就相当于在context中声明了一个bean bean的ID就是当前类的类名首字母小写,class就是当前类的classpath, 如果想要自定义id 也可以这样写:@Service("ID")

  1. @Scope("prototype") 创建多例的Bean

注入的注解

  1. @Autowired 基于类型自动注入 相当于ByType
    也可以和@Qualifier("name")联用,意思是基于类型自动注入,并挑选id=name的

  2. @Qualifier("name") 限定要自动注入的Beande ID,一般都是和Autowired连用

  3. @Resource 基于名称的自动注入 相当于ByName
    也可以使用@Resource(name="name")来自定义name

  4. @value("value")简单类型的自动注入(8种基本数据类型+String)

事务注解

@Transactional()加到类上时等于把类里面的每个方法都加了此注解,加到方法时仅对此方法有效,并且时类的此注解,对此方法失效

注解的配置

扫描注解

告知Spring注解在哪些个类中

<context:component-scan base-package="父包名"></context:component-scan>
@ComponentScan(basePackages = "父包名")

AOP注解

  1. @Aspect 声明此类是一个切面类,会包含切入点和通知
  2. @Component 声明组件,进入工厂
  3. @Pointcut(execution(切入位置))定义切入点
    打到一个空的方法上,后面的通知可以调用,实际上调用的是此注解
  4. @Before定义前置通知
  5. @AfterReturning定义后置通知
  6. @Around定义环绕通知
  7. @AfterThrowing定义异常通知

@Aspect
@Component
public class MyAspect {
    //定义切入点
    @Pointcut("execution(* *.*(..))")
    public void pc() {
    }

    //前置通知
    @Before("pc()")
    public void myBefore(JoinPoint a) {
        System.out.println("当前目标:" + a.getTarget());
        System.out.println("当前方法的参数表:" + a.getArgs());
        System.out.println("当前方法名:" + a.getSignature().getName());
        System.out.println("添加前置通知!!!!!!!!");
    }

    //后置通知(有异常将不执行)
    @AfterReturning(value = "pc()", returning = "ret") //意思是核心方法的返回值为ret
    public void myAfterReturning(JoinPoint a, Object ret) {
        System.out.println("后置通知" + ret);
    }

    //后置通知,有异常将执行
    @AfterThrowing(value = "pc()", throwing = "ex")
    public void MyThrows(JoinPoint jp, Exception ex) {
        System.out.println("throws");
        System.out.println("错误信息:" + ex.getMessage());
    }

    //环绕通知
    @Around("pc()")
    public Object MyInterceptor(ProceedingJoinPoint p) throws Throwable {
        System.out.println("环绕的前置通知");
        Object ret = p.proceed();//执行核心功能
        System.out.println("环绕的后置通知");
        return ret;
    }
}

使AOP注解生效

    <!--    启用AOP注解-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@EnableAspectJAutoProxy

Spring和Junit的集成

导入Spring-text和Junit的依赖

		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
         <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

使用Spring-text测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值