构建灵活应用:Spring中的IoC与AoP

Spring

在这篇文章中,我们将深入探讨Spring框架中的IoC(控制反转)和AOP(面向切面编程),我们将展示IoC如何有效管理对象的生命周期,从而提升代码的可维护性和灵活性。

同时,我们还将解析AOP的核心理念,介绍如何通过切面实现关注点的分离,使得业务逻辑与横切关注点(如日志、事务管理)更好地解耦。

无论你是Spring的新手还是希望深化理解的开发者,这篇文章都将为你提供实用的见解和技巧,让你在实际项目中充分利用Spring的强大功能。

简介

Spring框架是为了解决企业应用开发的复杂性,使用基本的JavaBean代替EJB,并提供了更多的企业应用功能,Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架。

Spring优点

  • Spring是一个开源的免费的框架(容器)
  • Spring是一个轻量级的、非入寝式的框架
  • 控制反转(IOC)、面向切面编程(AOP)
  • 支持事务的处理,对框架整合的支持

总的来说,Spring是一个轻量级的控制反转和面向切面编程的框架

Spring框架模块划分

  • Core-Container这是Spring运行的核心,主要包括IoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP等内容。
  • Testing这是Spring中非常重要的一个模块,主要包括Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient等内容。
  • Data Access这是对数据库的访问相关,包括Transactions, DAO Support, JDBC, O/R Mapping, XML Marshalling等内容。
  • Web Servlet传统的对Web Servlet的支持,包括Spring MVC, WebSocket, SockJS, STOMP Messaging等内容。
  • Web Reactive这是Spring5新增的对于响应式系统的支持,包括Spring WebFlux, WebClient, WebSocket等。
  • Integration这是对第三方系统的支持,包括Remoting, JMS, JCA, JMX, Email, Tasks, Scheduling, Caching等常用的第三方系统。
  • Languages这是对其他JVM语言的支持,包括Kotlin, Groovy等动态语言。

Core核心技术

  • IOC:控制反转,依赖注入
  • AOP:面向切面编程
  • Events:Spring事件处理机制,包括事件类ApplicationEvent和事件监听类ApplicationListener。如果实现了Application Listener接口的bean部署到Spring容器中,则每次ApplicationEvent发布到
  • ApplicationContext:都会通知该bean。
  • i18n:国际化,多语言
  • Validation:数据校验
  • Data Binding: 数据绑定
  • Type Conversion:类型转换,SpringMVC中参数的接收就使用到了
  • SpEL:SpEL的全称叫做Spring Expression Language。通常是为了在XML或者注解里面方便求值用的,通过编写##{ }这样的格式,即可使用

IOC

简介

Spring的核心就是提供了一个IOC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。

如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。

IOC容器解决了将组件的创建+配置与组件的使用相分离,实现对象之间的”解耦“。并且,由IOC负责管理组件的生命周期。

一个类中的方法、字段的值,可以通过外部注入的方式获得,不需要自己本身进行创建和推导。这个外部就是IOC容器。所谓的IOC,就是对象由Spring来创建、管理、装配!

什么是控制反转:

  • 传统应用程序的对象是由程序本身控制创建的;
  • 使用Spring后,对象是由Spring来创建的。
  • 程序本身不创建对象,而变成被动的接收对象。

IOC创建对象的方式

  1. 使用无参构造创建对象,默认!
  2. 使用有参构造器构建对象,3种(下标赋值,类型,参数名)

Spring配置(自己配置的xml)

  • Bean(对象)
    <bean id="user" class="org.example.U.User1">        <constructor-arg name="name" value="olderhard" > </constructor-arg>    </bean>    <bean id="user1" class="org.example.U.User2" name="asdf,us"><!--    <property name="name" value="adsf"> </property>-->    </bean>
  • alias(别名)一个对象的另一个名字
<alias name="user" alias="oasdf"></alias>
  • import一般用于团队开发使用,它可以将多个配置文件,导入合并为一个配置文件

DI(依赖注入)

依赖:bean对象的创建依赖于容器

注入:bean对象的所有属性,由容器来注入

  1. 构造器注入
  2. Set方式注入
  3. 拓展方式注入
  • c命名空间、p命名空间,通过这俩可以在配置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"       xmlns:p="http://www.springframework.org/schema/p"       xsi:schemaLocation="http://www.springframework.org/schema/beans  https://www.springframework.org/schema/beans/spring-beans.xsd"><!--    p命名空间注入,可以直接注入属性的值-->    <bean id="use" class="com.old.User" p:name="older" p:age="20"> </bean></beans>

Bean的作用域

单例模型(默认)

scope="singleton"

原型模式(每次取出都是新的对象)

scope="prototype"

其余

request、session、application,这些只能在web开发中使用到

Bean的自动装配

Spring会在上下文中自动寻找,并自动给bean装配属性。

在Spring中有三种装配的方式

  1. 在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  https://www.springframework.org/schema/beans/spring-beans.xsd">    <bean id="address" class="com.old.Address">        <property name="name" value="asdffsad.com"> </property>    </bean>    <bean id="student1" class="com.old.Student">        <!--        普通值注入-->        <property name="name" value="olderhard"> </property>        <!--        引用类型(Bean) 注入-->        <property name="address" ref="address"> </property>        <!--        数组注入-->        <property name="books">            <array>                <value>红楼梦</value>                <value>西游记</value>                <value>水浒传</value>                <value>金瓶梅</value>            </array>        </property>        <!--        List注入-->        <property name="hobbys">            <list>                <value>骑行</value>                <value>听歌</value>                <value>敲代码</value>            </list>        </property>        <!--        Map注入-->        <property name="card">            <map>                <entry key="西游记" value="吴承恩"> </entry>                <entry key="三国演义" value="罗贯中"> </entry>            </map>        </property>        <!--        Set注入-->        <property name="games">            <set>                <value>apex</value>                <value>三国杀</value>            </set>        </property>        <!--        null注入-->        <property name="wife">            <null> </null>        </property>        <!--        Properties注入-->        <property name="info">            <props>                <prop key="学号">12345678</prop>                <prop key="班级">计科2204</prop>                <prop key="性别">男</prop>                <prop key="username">olderhard</prop>                <prop key="password">12345678</prop>            </props>        </property>    </bean></beans>
  1. 在java中显式配置
@Configurationpublic class Config {    @Bean    public User getUser(){        return new User();    }}
@Componentpublic class User {    private String name;    public String getName() {        return name;    }    @Value("tiantiantian")    public void setName(String name) {        this.name = name;    }    @Override    public String toString() {        return "User{" +                "name='" + name + '\'' +                '}';    }}
  1. 隐式的自动装配bean
  • byName:会自动在容器的上下文中寻找,和自己对象set方法后面的值对应的bean id,但要保证id全局唯一,并且id与set方法中的值一致。
  • byType:会自动在容器的上下文中寻找,和自己对象set方法后面的参数类型相对应的bean id,但要保证那个参数类型全局唯一,并且bean和set方法的类型一样。
<!--    byName:会自动在容器的上下文中寻找,和自己对象set方法后面的值对应的bean id-->    <bean id="person" class="org.example.person" autowire="byName">        <property name="name" value="hujin"> </property>    </bean>

使用注解实现自动装配

使用注解须知

  1. 导入约束:context约束
<context:annotation-config/>
  1. 配置注解的支持:
<?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  https://www.springframework.org/schema/beans/spring-beans.xsd  http://www.springframework.org/schema/context  https://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/></beans>

常用注解

  • @Autowired 直接在属性上用即可,也可以在set方法上使用。使用Autowired我们可以不用编写set方法,前提是你这个自动配置的属性在IOC容器中存在,且符合byname!@Autowired(required==false) 如果显示定义了required为false,说明这个对象可以为null,否则不能为null@Autowired+@Qualifier(value="person"),这两个搭配使用,这样就是通过byName的方式实现的
  • @nullable 字段标记了这个注解,说明这个字段可以为null
  • @Resource:自动给字段装配,装配顺序:id,类型。
  • @Component:组件,放在类上,说明这个类被Spring管理了,就是bean,相应对象的名字就是首字母小写的这个类名。
  • @Scope("singleton")标注一个类是单例模式。
// 等价于在xml中注入Bean对象 <bean id="cat" class="org.example.Cat"/>@Componentpublic class Cat {    public void shout(){        System.out.println("miao~~");    }}

注解说明:

@Autowired和@Resource

  • 都是用来自动装配的,都可以放在属性字段上
  • @Autowired通过byType的方式实现,并且要求这个对象存在!(可以通过required=false调整)
  • @Resource 默认通过byname的方式实现,如果找不到名字,则通过byType实现。但是如果两个都找不到的情况下,会报错。

@Component衍生注解

  • 我们在web开发中,会按照mvc三层架构分层。
  • dao,【@Repository】
  • service,【@Service】
  • controller,【@Controller】

@Component,@Repository,@Service,@Controller,这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean。

xml与注解

  • xml更万能,适用于任何场合!维护非常方便
  • 注解 不是自己的类使用不了,维护相对复杂,
  • 最佳实践:xml管理bean,注解自负责完成属性的注入。
  • 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持。
<context:component-scan base-package="org.example"> </context:component-scan>
<context:annotation-config/>
  • 完全不需要配置文件,使用Java的方式配置SpringJavaConfig是Spring的子项目,在Spring4之后,它变成了核心功能。@Configuration,这个所注释的类会被托管到Spring容器,注册到容器中,因为它本来就是一个@Component,@Configuration代表这是一个配置类,就和我们之前看到的beans.xml是一样的。注册一个bean,就相当于我们之前写的一个bean标签,这个方法的名字就相当于bean标签中的id,这个方法的返回值就相当于bean标签中的class属性
@Configurationpublic class Config {    @Bean    public User getUser(){        return new User();    }}

AOP

简介

AOP,面向切面编程,面向切面编程是一种编程范式,旨在通过分离横切关注点来提高软件模块化程度。横切关注点是指那些跨越多个模块的功能,例如日志记录、安全性、事务管理等。

AOP中的概念

  • 切面:就是每个模块中都有一个相似的操作,而这个操作都是同一个类,如日志,在每个功能上都需要操作日志。这个日志就可以是切面,一个横跨多个核心逻辑的功能。
  • 连接点:在一个功能中插入一个方法,这个方法的执行就可以说是一个连接点。通过连接点,可以实现对一个系统的各个方面进行灵活的增强和监控,而不会影响核心业务逻辑的代码。
  • 切入点:就是一些对象中同样的插入方法执行,这些方法的执行点集合就是切入点。
  • 通知:在特定连接点上执行的动作,如在方法的执行前、执行后,打印出不同的任务信息。
  • 引介:有一个类,原本没有一个功能,但是可以通过引介为这个类动态地增加一个接口,并实现相应的方法。
  • 织入:将切面集合整合到系统的执行流程中,如在编译时、类加载时呼叫哦这运行时将这个相应的切面代码插入到合适的连接点。
  • 拦截器:拦截器可以看作一种实现增强的方式。
  • 目标对象:目标对象是真正执行业务核心逻辑的对象。

AOP 代理:

  • 当使用AOP对目标对象进行增强后,客户端持有的就是增强后的AOP对象。
  • 如当客户端调用非核心逻辑的方法时,就是调用的是AOP代理对象的方法,这个代理对象会在合适的时候执行切面的逻辑。

动态代理

AOP本质上就是动态代理,动态代理有两种

  • JDK动态代理,Spring的AOP的默认实现,要求必须实现接口
  • CGLIB动态代理,Spring的AOP的可选配置,类和接口都支持

使用Spring实现AOP

方式一:使用Spring的API接口(主要是SpringAPI接口实现)

<?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  https://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/aop  https://www.springframework.org/schema/aop/spring-aop.xsd">    <!--  注册bean  -->    <bean id="userServiceImpl" class="org.example.Service.UserServiceImpl"> </bean>    <bean id="log" class="org.example.log.log"> </bean>    <bean id="afterlog" class="org.example.log.Afterlog"> </bean>    <!--  配置Aop:需要导入aop的约束,上面的xmlns处  -->    <!--  方式一:使用原生的Spring API接口  -->    <aop:config>        <!--    切入点:expression:表达式execution(要执行的位置)    -->        <aop:pointcut id="pointcut" expression="execution(* org.example.Service.UserServiceImpl.*(..))"/>        <!--    执行环绕增强!    -->        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>        <aop:advisor advice-ref="afterlog" pointcut-ref="pointcut"/>    </aop:config></beans>
public class log implements MethodBeforeAdvice {    @Override    /*method:要执行的目标对象的方法    * objects:参数    * target:目标对象**/    public void before(Method method, Object[] args, Object target) throws Throwable {        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");    }}
public class Afterlog implements AfterReturningAdvice {    @Override    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {        System.out.println("执行了"+method.getName()+"返回结果为:"+returnValue);    }}

方式二:使用自定义类来实现AOP(主要是切面)

    <!--  方式二:自定义类  -->    <bean id="diyPointCut" class="org.example.diy.DiyPointCut"> </bean>    <aop:config>        <!--    自定义切面,ref 要引用的类    -->        <aop:aspect ref="diyPointCut">            <!--    切入点    -->            <aop:pointcut id="point" expression="execution(* org.example.Service.UserServiceImpl.*(..))"/>            <!--     通知       -->            <aop:before method="before" pointcut-ref="point"/>            <aop:after method="after" pointcut-ref="point"/>        </aop:aspect>    </aop:config>
public class DiyPointCut {    public void before(){        System.out.println("方法执行前");    }    public void after(){        System.out.println("方法执行后");    }}

方式三:使用注解实现

        <!--    方式三:使用注解实现-->    <bean id="annotationPointCut" class="org.example.AnnotationPointCut"> </bean>        <!--    开启注解支持!-->    <aop:aspectj-autoproxy/>
@Aspect//标注这个类是一个切面public class AnnotationPointCut {    @Before("execution(* org.example.Service.UserServiceImpl.*(..))")    public void before(){        System.out.println("====方法执行前====");    }    @After("execution(* org.example.Service.UserServiceImpl.*(..))")    public void after(){        System.out.println("方法执行后");    }}
#Java##Spring##SSM框架##项目#
Java面试题 文章被收录于专栏

本专栏汇总了大量的Java面试题和Java面经,希望对大家有所帮助!

全部评论

相关推荐

评论
1
1
分享
牛客网
牛客企业服务