Spring中的控制反转(IoC)和面向切面编程(AOP)详解

一、Spring是什么

Spring是一个开源框架,是为了解决企业应用开发的复杂性而创建的。这个框架的主要优势之一就是其分层架构,允许用户选择使用哪一个组件,同时为J2EE应用程序开发提供集成的框架。

1、Spring核心功能

Spring的核心是 控制反转(IoC)面向切面编程(AOP)

  • 控制反转是一种通过描述(在XML或注解中)并通过第三方(Spring)去产生或获取特定对象的方式。
  • 面向切面编程允许程序员定义横切关注点,并将它们插入到应用程序代码中。

此外,Spring还提供了丰富的功能,包括数据访问对象(DAO)支持、事务管理、JNDI查找、EJB集成、邮件服务、远程方法调用(RMI)、定时任务等。这些功能使得Spring成为了一个功能强大的框架,能够简化企业应用开发的流程,提高开发效率。

2、Spring框架

在Spring框架中,“框架”是一个核心概念,它指的是一种基于基础技术之上,从众多业务中抽取出的通用解决方案。

框架是一个半成品软件,使用框架规定的语法开发可以提高开发效率,使开发者能够用简单的代码完成复杂的基础业务。框架内部使用大量的设计模式、算法、底层代码操作技术,如反射、内省、xml解析、注解解析等。

框架的主要作用之一就是简化开发,使开发者可以将精力更多地投入到纯业务开发上,而不需要过多关注技术实现和一些辅助业务。此外,框架通常具备扩展性,允许开发者根据需要进行定制和扩展。

在Spring框架中,框架的作用体现在多个方面,如提供了功能强大的控制反转(IoC)和面向切面编程(AOP)功能,解决了开发者在JavaEE开发中遇到的许多常见问题。通过Spring框架,开发者可以更加高效地创建和管理对象,减少自行创建对象的繁琐工作,并通过Spring窗口获取对象,甚至获取到的对象的某些属性已经被注入了指定的值。

二、控制反转(IoC)

在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念。它改变了传统上由程序代码直接操控对象调用的方式,将对象的调用权交给容器(Spring IoC容器),通过容器来实现对象组件的装配和管理。目的就是减少对代码的改动, 实现不同的功能,实现解耦合。

IoC意味着将原本由代码直接操控的对象的调用权交给第三方(在Spring框架中就是IoC容器)来控制,用以解耦和降低代码之间的耦合度。通过使用IoC,不再需要手动创建对象并管理它们之间的依赖关系,而是由IoC容器来负责创建、配置和管理这些对象。

Spring IoC容器负责创建对象,管理对象之间的依赖关系,并在对象的生命周期内提供各种服务。这使得程序猿可以将更多的精力放在业务逻辑的实现上,而不需要过多关注对象创建和管理等底层细节。

1、IoC的优点

  • 解耦:IoC降低了代码之间的耦合度,使得各个组件之间的依赖关系更加清晰和灵活。
  • 易于测试:由于依赖关系由容器管理,可以更容易地替换依赖的组件,方便进行单元测试或集成测试。
  • 可扩展性:IoC容器支持动态地添加、删除或替换组件,使得应用程序更加灵活和可扩展。

在Spring中,IoC的实现方式通常是通过配置文件(如XML文件)或注解来定义Bean(Spring中的对象)及其依赖关系,然后由Spring IoC容器负责创建和注入这些Bean。

这样,只需要关注业务逻辑的实现,而不需要关心对象的创建和管理等底层细节。即把对象的创建,赋值,管理工作都交给代码之外的容器实现,就是对象的创建是由其它外部资源完成。

  • 反转: 把开发人员管理,创建对象的权限转移给代码之外的容器实现。由容器代替开发人员管理对象,创建对象,给属性赋值。
  • 正转:由开发人员在代码中,使用new构造方法创建对象, 开发人员主动管理对象。

2、Java中创建对象的方式

  • 构造方法
  • new Student()
  • 反射
  • 序列化
  • 克隆
  • IoC容器创建对象
  • 动态代理

2.1、Spring创建对象的步骤

  • 创建maven项目。
  • 加入maven的依赖:Spring的依赖,版本5.2.5版本,junit依赖。
  • 创建类(接口和它的实现类),和没有使用框架一样,就是普通的类。
  • 创建Spring需要使用的配置文件。
  • 声明类的信息,这些类由Spring创建和管理,通过Spring的语法完成属性的赋值。
  • 使用容器中的对象,通过 ApplicationContext 接口和它的实现类 ClassPathXmlApplicationContext 的方法getBean()
  • 测试Spring创建的对象。

注意:

  • Spring默认创建对象的时间:在创建Spring的容器时,会默认创建所有对象。
  • Spring创建对象,默认的是无参数构造方法。
  • Spring创建对象语句 SomeService someService = new SomeServiceImpl();
  • Spring把创建好的对象放入到map中,Spring框架有一个map的负责存放对象,springMap.put(id的值,对象);
  • 例如:springMap.put("someService", new SomeService());

2.2、IoC创建对象的实例(servlet)

  • 创建类继承HttpServelt,并在 web.xml 注册servlet,注册示例:
<servlet-name> myservlet </servlet-name>
<servelt-class>com.qdwa.controller.MyServlet1</servlet-class>
  • 没有创建 Servlet对象,没有 MyServlet myservlet = new MyServlet();
  • Servlet是Tomcat服务器它能你创建的,Tomcat也称为容器
  • Tomcat作为容器:里面存放的有Servlet对象, Listener , Filter对象

3、Spring框架中的依赖注入(DI)

在Spring框架中,依赖注入(Dependency Injection,简称DI)是一种实现控制反转(IoC)的具体技术。其原理是将对象所依赖的组件(即它所需要的对象)由外部容器(如Spring IoC容器)负责创建和注入,而不是由对象本身负责查找或创建。意思就是创建对象,给属性赋值,注入就是赋值的意思。

依赖注入通过在对象的构造函数、属性或方法中注入所依赖的对象,而不是在对象内部创建或获取依赖对象。这样,对象的依赖关系由外部容器来管理,对象只需要关注自身的核心功能,而不需要关心如何获取依赖对象。

依赖注入的主要目的是实现控制反转,即将对象的创建、组装和依赖关系的管理交给外部容器来负责。 这种方式有助于解耦组件之间的依赖关系,提高代码的灵活性、可维护性和可测试性。

在Spring中,依赖注入通常是通过配置文件(如XML文件)或注解来实现的。程序猿可以在配置文件中 定义Bean及其依赖关系 ,或者通过 注解 在Java代码中指定依赖关系。然后,Spring IoC容器会根据这些配置或注解自动创建和管理对象及其依赖关系,将它们注入到需要的地方。

通过使用依赖注入,代码会更加清晰、可读性更高,并且更易于单元测试和扩展。因为对象之间的依赖关系是通过外部容器来管理的,所以可以更容易地替换或修改依赖组件,以适应不同的需求或场景。

DI的实现语法有两种:

  • 在Spring的配置文件中,使用标签和属性完成,叫做基于XML的DI实现。
  • 在Spring中的注解,完成属性赋值,叫做基于注解的DI实现

3.1、设置方法注入(set方法)

在Spring框架中,set设置方法注入(Setter Injection)是另一种依赖注入的方式。set方法注入是通过调用Bean的setter方法来注入依赖对象。这种方式允许Bean在创建之后、调用setter方法之前进行其他初始化操作,同时依赖对象也可以是可选的。

3.1.1、示例

首先,我们定义两个简单的类:MessageServiceMessagePrinterMessagePrinter 类依赖于 MessageService 类,并通过setter方法注入。

// MessageService.java  
public class MessageService {  
    private String message;  
  
    public void setMessage(String message) {  
        this.message = message;  
    }  
  
    public String getMessage() {  
        return message;  
    }  
}  
  
// MessagePrinter.java  
public class MessagePrinter {  
    private MessageService messageService;  
  
    // 使用setter方法注入MessageService  
    public void setMessageService(MessageService messageService) {  
        this.messageService = messageService;  
    }  
  
    public void printMessage() {  
        System.out.println("Printing message: " + messageService.getMessage());  
    }  
}

接下来,需要在Spring的配置文件中定义这两个Bean,并配置set方法注入。

<!-- applicationContext.xml -->  
<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">  
  
    <!-- 定义MessageService Bean -->  
    <bean id="messageService" class="com.example.MessageService">  
        <property name="message" value="Hello, Spring!"/>  
    </bean>  
  
    <!-- 定义MessagePrinter Bean,并通过setter方法注入MessageService -->  
    <bean id="messagePrinter" class="com.example.MessagePrinter">  
        <property name="messageService" ref="messageService"/>  
    </bean>  
  
</beans>

在这个配置文件中,messageService Bean 使用 <property> 标签的 namevalue 属性来设置 message 属性。而 messagePrinter Bean 则使用 <property> 标签的 nameref 属性来注入 messageService Bean。

最后,在应用程序中,可以加载Spring的配置文件,并从容器中获取 MessagePrinter 的实例,调用其 printMessage 方法。

import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class SpringDemo {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
        MessagePrinter messagePrinter = context.getBean("messagePrinter", MessagePrinter.class);  
        messagePrinter.printMessage();  
    }  
}

Spring会创建 MessageServiceMessagePrinter 的实例,并通过set方法注入将 MessageService 的实例传递给 MessagePrinter。然后,MessagePrinterprintMessage 方法会被调用,输出从 MessageService 获取的消息。

注意,由于setter方法注入允许在Bean创建后进行依赖注入,因此它更适用于那些依赖项不是必须的或可以在Bean实例化后进行设置的场景。然而,在某些情况下,构造方法注入可能更为可取,因为它确保了Bean在创建时就已经具备了所有必需的依赖,从而提高了代码的健壮性。

3.2、构造方法注入

在Spring框架中,构造方法注入(Constructor Injection)是一种依赖注入的方式,它通过在Bean的构造方法中传入依赖对象,从而实现依赖的注入。这种方式确保了Bean在创建时就已经获得了所有必需的依赖,因此它的依赖关系在对象创建后是不可变的。

3.2.1、示例

首先,我们定义两个简单的类:MessageServiceMessagePrinterMessagePrinter 类依赖于 MessageService 类。

java
// MessageService.java  
public class MessageService {  
    private final String message;  
  
    public MessageService(String message) {  
        this.message = message;  
    }  
  
    public String getMessage() {  
        return message;  
    }  
}  
  
// MessagePrinter.java  
public class MessagePrinter {  
    private final MessageService messageService;  
  
    // 使用构造方法注入MessageService  
    public MessagePrinter(MessageService messageService) {  
        this.messageService = messageService;  
    }  
  
    public void printMessage() {  
        System.out.println("Printing message: " + messageService.getMessage());  
    }  
}

接下来,需要在Spring的配置文件中定义这两个Bean,并配置构造方法注入。

<!-- applicationContext.xml -->  
<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">  
  
    <!-- 定义MessageService Bean -->  
    <bean id="messageService" class="com.example.MessageService">  
        <constructor-arg value="Hello, Spring!"/>  
    </bean>  
  
    <!-- 定义MessagePrinter Bean,并通过构造方法注入MessageService -->  
    <bean id="messagePrinter" class="com.example.MessagePrinter">  
        <constructor-arg ref="messageService"/>  
    </bean>  
  
</beans>

在这个配置文件中,我们定义了两个Bean:messageServicemessagePrintermessageService Bean 使用 <constructor-arg> 标签通过值注入方式传递了一个字符串参数。而 messagePrinter Bean 则使用 <constructor-arg> 标签的 ref 属性引用了 messageService Bean,实现了构造方法注入。

最后,在应用程序中,可以加载Spring的配置文件,并从容器中获取 MessagePrinter 的实例,调用其 printMessage 方法。

import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class SpringDemo {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
        MessagePrinter messagePrinter = context.getBean("messagePrinter", MessagePrinter.class);  
        messagePrinter.printMessage();  
    }  
}

当运行这个程序时,Spring会创建 MessageServiceMessagePrinter 的实例,并通过构造方法注入将 MessageService 的实例传递给 MessagePrinter。然后,MessagePrinterprintMessage 方法会被调用,输出从 MessageService 获取的消息。

3.3、引用类型自动注入

在Spring框架中,引用类型自动注入(通常指的是自动装配,即Autowired)是指Spring容器能够自动将需要的Bean注入到使用它的其他Bean中,而无需显式地在配置文件中指定这种依赖关系。这种自动装配的特性是通过使用 @Autowired 注解来实现的。

引用类型自动注入通常用于那些通过接口或超类定义的引用类型字段或setter方法。当Spring容器遇到带有 @Autowired 注解的字段或方法时,它会尝试找到一个匹配的Bean实例并将其注入。

3.3.1、示例

首先,定义一个简单的服务接口和它的实现类:

// MyService.java  
public interface MyService {  
    String doSomething();  
}  
  
// MyServiceImpl.java  
@Service  
public class MyServiceImpl implements MyService {  
    @Override  
    public String doSomething() {  
        return "Service did something!";  
    }  
}

接下来,定义一个使用MyService的组件类,并使用@Autowired注解来自动注入MyService的实现:

// MyComponent.java  
@Component  
public class MyComponent {  
  
    // 使用@Autowired注解自动注入MyService的实例  
    @Autowired  
    private MyService myService;  
  
    public void performAction() {  
        // 调用注入的MyService实例的方法  
        String result = myService.doSomething();  
        System.out.println(result);  
    }  
}

在上面的代码中,MyComponent 类中的 myService 字段使用了@Autowired 注解,这意味着Spring容器将自动找到一个 MyService 类型的Bean实例并将其注入到 myService 字段中。

然后,你需要确保Spring容器能够扫描到这些类,并创建相应的Bean。这通常通过在配置类上使用 @ComponentScan 注解或者在XML配置文件中指定组件扫描路径来实现。

// AppConfig.java  
@Configuration  
@ComponentScan("com.example") // 假设MyComponent和MyServiceImpl都在com.example包下  
public class AppConfig {  
    // ...  
}

最后,你可以在应用程序的入口点(比如main方法)中创建Spring的 ApplicationContext ,然后获取并使用 MyComponent 的实例:

import org.springframework.context.ApplicationContext;  
import org.springframework.context.annotation.AnnotationConfigApplicationContext;  
  
public class Application {  
    public static void main(String[] args) {  
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);  
        MyComponent myComponent = context.getBean(MyComponent.class);  
        myComponent.performAction(); // 输出 "Service did something!"  
    }  
}

在这个示例中,MyServiceImpl的实例会被自动注入到MyComponent的myService字段中,而无需显式地在配置文件中指定这种依赖关系。当调用myComponent.performAction()方法时,它将通过注入的MyService实例执行操作并输出结果。

注意,@Autowired注解还可以用于构造器、setter方法和配置方法,而不仅限于字段。此外,从Spring 4.3开始,如果类只有一个构造器,那么@Autowired注解甚至可以省略,因为Spring会默认尝试自动装配构造器。

3.3.2、byName(按名称注入)

Java类中引用类型的 属性名 和Spring容器中(配置文件)<bean>id 名称一样,且数据类型是一致的,这样的容器中的bean,Spring能够赋值给引用类型。

<bean id="xxx" class="yyy" autowire="byName">;
	简单类型属性赋值;
</bean>
3.3.3 byType(按类型注入)

Java类中引用类型的 数据类型 和spring容器中(配置文件)<bean>class 属性,是同源关系,这样的bean能够赋值给引用类型。同源就是一类的意思。

  • Java类中引用数据类型的数据类型和bean的class的值是一样的。
  • Java类中引用数据类型的数据类型和bean的class的值是父子关系的。
  • Java类中引用数据类型的数据类型和bean的class的值是接口和实现类的关系。
<bean id="xx" class="xxx" autowire="byType">
	简单类型赋值;
</bean>

注意:在byType中,在XML配置文件中声明bean只能有一个符合条件的,多于一个是错误的。

4、IoC的技术实现

只需要在程序中提供要使用的对象名称就可以,至于对象如何在容器中创建,赋值,查找都由容器内部实现。Spring是使用的DI实现了IoC的功能,Spring是一个容器,底层创建对象,管理对象,给属性赋值, 底层是 反射创建对象

spring-conetxtspring-webmvc 是spring中的两个模块:

  • spring-context 是IoC功能的,创建对象的。
  • spring-webmvc 做web开发使用的, 是servlet的升级。
  • spring-webmvc 中也会用到 spring-context 中创建对象的功能的。

5、基于配置文件的DI

需要经常修改就使用配置文件,不经常修改就是用注解的方式。

多个配置文件会减少配置文件的代码内容,轻量化每一个配置文件,一个模块一个配置文件,不会发生冲突。

  • 按功能模块:一个模块一个配置文件。
  • 按类的功能:数据库一个配置文件, 事务相关的一个配置文件,service功能的一个配置文件等。

spring-total 主配置文件:包含其他的配置文件的,主配置文件一般是不定义对象的。

< import resource="其他配置文件的路径"/>

关键字:classpath,表示类路径的(class文件所在的目录)在Spring的配置文件中要指定其他文件的位置,在Spring的配置文件中要指定其他文件的位置,需要使用classpath,告诉Spring到哪里去加载读取文件。包换关系的配置文件中,可以使用通配符 * ,表示任意字符,主配置文件名称不能包含在通配符的范围之内。

6、基于注解的DI(7个注解)

使用注解的步骤:

  • 加入maven的依赖 spring-context ,在你加入spring-context的同时, 间接加入spring-aop的依赖。
  • 使用注解必须使用spring-aop依赖。
  • 在类中加入spring的注解(多个不同功能的注解)。
  • 在spring的配置文件中,加入一个组件扫描器的标签,说明注解在你的项目中的位置。

常用注解:

  • @Component
  • @Respotory
  • @Service
  • @Controller
  • @Value
  • @Autowired
  • @Resource

6.1、@Component 创建对象,等同于bean

value就是对象的名称,也就是bean的id值,value值是唯一的,创建的对象在整个spring容器中就一个。放在类的上面。

@Component(value = "myStudent")

等同于

< bean id="myStudent" class="com.jwruan.component.Student"/>

6.2、@Repository、@Service、@Controller

  • @Repository(用在持久层类的上面):放在dao层的实现类上面,表示创建dao对象,dao对象是能访问数据库的。
  • @Service(用在业务层类的上面): 放在service的实现类上面,创建service对象,service对象是做业务处理,可以有事务功能的。
  • @Controller(用在控制器的类上面的): 放在控制器(处理器)的类上面,创建控制器的对象,控制器对象,能够接受用户提交的参数,显示请求的处理结果。
  • @Component:如果一个类需要创建对象,不是上面的三种类型。
  • 以上三个注解的使用语法和@component一样的,都能创建对象。这三个注解还有额外的功能,是用来给项目的对象分层的。

6.3、@value 简单类型的属性赋值,反射机制

value是String类型的,表示简单类型的属性值,位置有两个地方。

  • 在属性定义的上面,无需set方法,推荐使用
  • 在set方法上面

6.4、@Autowired 引用类型的赋值

  • @Autowired:Spring框架提供的注解,实现引用类型的赋值。
  • Spring中通过注解给引用类型赋值,使用的是自动注入原理,支持byName,byType。
  • 默认使用的是byType自动注入。

属性:require,是一个boolean的类型,默认true。

  • required=true:表示引用类型赋值失败,程序报错,并终止执行。推荐使用true,可以更早的找到错误,发现问题。
  • required=false:引用类型如果赋值失败,程序正常执行,引用类型是null。使用容易产生空指针异常。

位置:

  • 在属性定义的上面,无需set方法,推荐使用。
  • 在set方法的上面。

如果要使用byName的方式,需要做的是:

  • 在属性上面加入@Autowired。
  • 在属性上面加入@Qualifier(value="bean的id") ,表示使用指定名称的bean完成赋值。
  • 这两个注解没有先后顺序。

6.5、@Resource 引用类型的赋值

@Resource: 来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值,使用的也是自动注入原理,支持byName,byType,默认的是byName。

位置:

  • 在属性定义的上面,无需set注方法,推荐使用。
  • 在set的方法上面。

默认是byName:先使用byName自动注入,如果byName赋值失败,在使用byType。

@Resource 若只使用byName的方式,需要增加一个属性name,name的值就是bean中 id 的名称。@Resource(name = "mySchool")

三、面向切面编程(AOP)

面向切面编程(AOP,Aspect Oriented Programming)是一种编程思想,它采取横向抽取机制,取代了传统纵向继承体系重复性代码的编写方式,用于处理在程序中散布的、跨越多个模块的公共关注点(如性能监视、事务管理、安全检查、缓存、日志记录等)

在AOP中,切面是一个模块化的单元,它封装了与横切关注点相关的行为,并可以在多个不同的应用程序中重用。 切面可以通过一种称为“织入”的过程将其与主要业务逻辑相结合,从而创建一个完整的应用程序。

名字解读:

  • Aspect:切面,给你的目标类增加的功能,就是切面。 像上面用的日志,事务都是切面。
  • 切面的特点:一般都是非业务方法,独立使用的。
  • Orient:面向,对着。
  • Programming:编程

OOP:面向对象编程、OOD:面向对象设计、OOA:面向对象分析

1、怎么理解面向切面编程

  • 需要在分析项目功能时,找出切面。
  • 合理的安排切面的执行时间(在目标方法前, 还是目标方法后)。
  • 合理的安排切面执行的位置,在哪个类,哪个方法增加增强功能。

切面术语:

  • Aspect:切面,表示增强的功能, 就是一堆代码,完成某个一个功能。非业务功能,常见的切面功能有日志, 事务, 统计信息, 参数检查, 权限验证。
  • JoinPoint:连接点 ,连接业务方法和切面的位置。 就某类中的业务方法,相当于一个方法。如果使用要将其放在参数列表的第一个位置。
  • Pointcut:切入点 ,指多个连接点方法的集合。多个方法。
  • 目标对象:给哪个类的方法增加功能, 这个类就是目标对象。
  • Advice:通知,通知表示切面功能执行的时间。

一个切面有三个关键的要素:

  • 切面的功能代码,切面干什么。
  • 切面的执行位置,使用Pointcut表示切面执行的位置。
  • 切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。

2、AOP的主要优势

  • 提高代码的可重用性、可维护性和可扩展性: 通过将横切关注点与业务逻辑分离开来,AOP可以使代码更加模块化,降低了模块之间的耦合度,从而使代码更易于理解和维护。同时,新功能的添加或现有功能的修改都可以通过添加或修改切面来实现,而无需修改主要业务逻辑。
  • 提高代码灵活性: AOP可以在运行时动态地将切面织入到主要业务逻辑中,这使得应用程序的配置和定制变得更加灵活,可以根据不同的需求进行动态调整。

在Spring框架中,AOP是一个重要的组成部分。Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类植入增强代码。它可以帮助开发人员更有效地处理应用程序中的公共关注点,提高代码的可重用性、可维护性和可扩展性,同时降低模块之间的耦合度。

3、AOP的实现方式

面向切面编程(AOP)的实现方式主要有以下几种:

  • 编译时织入(Compile-time weaving):编译时织入是在编译源代码时就将切面代码与目标代码合并。 这通常需要一个特殊的编译器来识别切面,并将其插入到目标代码中。AspectJ是一个支持编译时织入的AOP框架。
  • 类加载时织入(Load-time weaving):类加载时织入发生在类被加载到JVM时。这通常通过Java Agent技术来实现,Java Agent能够在类加载到JVM之前对类进行修改。Spring框架提供了对类加载时织入的支持,结合AspectJ的LTW(Load-Time Weaving)功能。
  • 运行时织入(Runtime weaving):运行时织入是在程序运行时动态地将切面代码与目标代码结合。 这通常通过 动态代理 技术实现,如JDK的动态代理或CGLIB。Spring AOP主要基于运行时织入,它使用代理模式创建代理对象,将切面逻辑动态地织入到业务逻辑中。
  • 手动织入:虽然不常见,但理论上开发者也可以手动将切面代码与目标代码结合。这通常涉及复制和粘贴代码,或者使用一些模板引擎来生成包含切面逻辑的代码。然而,这种方式缺乏灵活性和可维护性,通常不推荐使用。

在实际应用中,编译时织入和类加载时织入通常用于更复杂的场景,需要更高级别的控制和灵活性。而运行时织入(特别是通过Spring AOP)则更为常见,因为它相对简单且易于集成到现有的Spring应用中。

要注意的是,不同的AOP实现方式有其特定的优点和限制,选择哪种方式取决于具体的应用场景和需求。

  • 编译时织入可以提供更好的性能,因为它在编译时就完成了代码合并。
  • 而运行时织入则更加灵活,可以在不修改源代码的情况下动态地添加切面逻辑。

有关于JDK动态代理的作用和实现示例可以参考:
JDK动态代理简介

3.1 AOP的技术实现框架

  • Spring:Spring框架在内部实现了AOP规范,能做AOP的工作。Spring主要在事务处理时使用AOP。我们项目开发中很少使用Spring的AOP实现。 因为Spring的AOP比较笨重。
  • AspectJ:AspectJ是一个独立的AOP框架,它提供了编译时织入和类加载时织入两种方式。AspectJ使用特定的语法来定义切面、连接点和通知,使得切面逻辑能够精确地插入到目标代码中。AspectJ的功能强大且灵活,适用于复杂的AOP需求。
  • 以上两个主要的AOP框架外,还有一些其他的AOP实现方式,如使用JDK的动态代理或CGLIB等库来实现AOP。这些实现方式通常更为 轻量级 ,适用于简单的AOP需求。

3.2、动态代理

JDK动态代理,使用JDK中的Proxy,Method,InvocaitonHanderl创建代理对象。JDK动态代理要求目标类必须实现接口,如果没有接口,Spring会自动使用CGLIB动态代理。

CGLIB动态代理:第三方的工具库,创建代理对象,原理是继承。 通过继承目标类,创建子类。子类就是代理对象。 要求目标类不能是final的, 方法也不能是final的。

如果你期望目标类有接口,还要使用CGLIB动态代理:在配置文件中加入下面的语句,就是告诉Spring框架使用CGLIB动态代理。

<aop:aspectj-autoproxy proxy-target-class="true"/>

动态代理可以做到在目标类源代码不改变的情况下:

  • 增加功能。
  • 减少代码的重复。
  • 专注业务逻辑代码。
  • 解耦合,让你的业务功能和日志,事务非业务功能分离。

3.3、AspectJ框架

AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ通过在源代码中插入横切关注点(cross-cutting concern),实现了在不侵入代码结构的情况下对系统进行横向切分的能力。它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

主要特性和优势包括

  • 成熟稳定:AspectJ自2001年发展至今,已经是一个非常成熟和稳定的框架。通常在使用时,不需要过多担心插入的字节码正确性相关的问题。
  • 灵活性:AspectJ允许在多个位置插入自定义的代码,如方法调用的位置、方法体内部、读写变量的位置、静态代码块内部以及异常处理位置的前后。它还可以直接将原位置的代码替换为自定义的代码。
  • 模块化:切面(Aspect)是AspectJ中的一个关键概念,它是一个模块化的单元,封装了横切关注点的逻辑和行为。这使得代码更加模块化,提高了代码的可维护性和可重用性。
  • 引入新接口和实现:AspectJ的引入(Introduction)功能允许开发者为现有的类添加新的接口和实现,从而扩展类的功能。

然而,AspectJ也存在一些潜在的问题,例如性能问题。AspectJ在实现AOP时,会包装一些特定的类,并不会直接将Trace函数插入到代码中,而是经过一系列的封装。这可能导致生成的字节码较大,对原函数的性能产生一定的影响。特别是在对应用中的所有函数都进行插桩时,性能影响可能会更加显著。

具体实现方式:AspectJ框架实现AOP功能

四、Spring框架使用IoC集成MyBatis

为什么IoC能把MyBatis和Spring集成在一起,是因为ioc能创建对象。可以把MyBatis框架中的对象交给Spring统一创建,开发人员从Spring中获取对象。开发人员就不用同时面对两个或多个框架了,就面对一个Spring。

MyBatis使用步骤:

  • 定义dao接口,StudentDao
  • 定义mapper文件 StudentDao.xml
  • 定义mybatis的主配置文件 mybatis.xml
  • 创建dao的代理对象
StudentDao dao = SqlSession.getMapper(StudentDao.class);
List< Student> students  = dao.selectStudents();

要使用dao对象,需要使用 getMapper() 方法,怎么能使用 getMapper() 方法,需要哪些条件:

  • 获取 SqlSession 对象, 需要使用 SqlSessionFactoryopenSession() 方法。
  • 创建 SqlSessionFactory 对象。 通过读取MyBatis的主配置文件,能创建 SqlSessionFactory 对象。
  • 有了 SqlSessionFactory 对象, 就可以使用 Factory 能获取 SqlSession ,有了 SqlSession 就能有 dao ,目的就是获取 dao 对象。
  • Factory创建需要读取主配置文件。
  • 我们会使用独立的连接池POOLED类替换MyBatis默认自己带的, 把连接池类也交给Spring创建。

2、创建连接池

通过以上的说明,我们需要让Spring创建以下对象:

  • 独立的连接池类的对象, 使用阿里的druid连接池。
  • SqlSessionFactory 对象。
  • 创建出 dao 对象。

需要学习就是上面三个对象的创建语法,使用xml的bean标签。

连接池:多个连接Connection对象的集合, List<Connection> connlist,connList就是连接池。

通常使用Connection访问数据库。

Connection conn = DriverManger.getConnection(url, username, password);
Statemenet stmt = conn.createStatement(sql);
stmt.executeQuery();
conn.close();

3、集成步骤

  • 1、新建Maven项目
  • 2、加入Maven的依赖:Spring依赖,MyBatis依赖,MySQL驱动,Spring的事务的依赖,MyBatis和Spring集成的依赖: MyBatis官方体用的,用来在Spring项目中创建MyBatis的 sqlSessionFactory ,获取dao对象的。
  • 3、创建实体类
  • 4、创建dao接口和mapper文件
  • 5、创建MyBatis主配置文件
  • 6、创建service接口和实现类,属性是dao。
  • 7、创建Spring的配置文件:声明MyBatis的对象交给Spring创建。数据源 DataSourcesqlSessionFactory ,Dao对象,声明自定义的service。
  • 8、创建测试类,获取service对象,通过service调用dao完成数据库的访问。

4、使用连接池

在程序启动的时候,先创建一些Connection。

Connection c1 = ...
Connection c2 = ...
Connection c3 = ...
List< Connection>  connlist = new ArrayLits();
connList.add(c1);
connList.add(c2);
connList.add(c3);
	
Connection conn = connList.get(0);
Statemenet stmt = conn.createStatement(sql);
stmt.executeQuery();
把使用过的connection放回到连接池
connList.add(conn);
	
Connection conn1 = connList.get(1);
Statemenet stmt = conn1.createStatement(sql);
stmt.executeQuery();
把使用过的connection放回到连接池
connList.add(conn1);
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值