IOC 容器在 Spring 中的实现
Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫 bean。在创建 bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式;
1. BeanFactory
这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。
2. ApplicationContext
BeanFactory 的子接口,提供更多高级特性
3. ApplicationContext的主要实现类
基于 XML 管理 bean
实验一:入门案例
-
创建Maven Module
-
引入依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>
-
创建类 HelloWorld
-
创建 Spring 配置文件
-
在 Spring 配置文件中那个配置 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 对象,将对象给 IOC 容器管理
id:bean 的唯一标识
class :全类名
-->
<bean id="helloWorld" class="com.lyq.spring.pojo.HelloWorld"/>
</beans>
- 创建测试类
public class HelloTest {
@Test
public void test(){
// 获取 IOC 容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld helloWorld = ioc.getBean("helloWorld", HelloWorld.class);
helloWorld.sayHello();
}
}
获取 bean 的三种方式
- 根据 bean 的 id 获取
HelloWorld helloWorld = ioc.getBean("helloWorld");
- 根据 bean 的类型获取
HelloWorld helloWorld = ioc.getBean(HelloWorld.class);
- 根据 bean 的 id 和类型获取
HelloWorld helloWorld = ioc.getBean("helloWorld", HelloWorld.class);
依赖注入
依赖注入 - setter 注入
<bean id="studentTwo" class="com.lyq.spring.pojo.Student">
<property name="age" value="12"/>
<property name="sname" value="小张"/>
<property name="sid" value="1"/>
<property name="gender" value="男"/>
</bean>
依赖注入 - 构造器注入
<bean id="studentThree" class="com.lyq.spring.pojo.Student">
<constructor-arg name="age" value="15"/>
<constructor-arg name="sname" value="校长"/>
</bean>
依赖注入 - 特殊值处理
<bean id="studentThree" class="com.lyq.spring.pojo.Student">
<property name="age" value="12"/>
<property name="sname" value="<小张>"/>
<property name="gender">
<null/>
</property>
</bean>
依赖注入 - 为类类型赋值
<bean id="clazzOne" class="com.lyq.spring.pojo.Clazz">
<property name="cid" value="05"/>
<property name="cname" value="吸杂"/>
</bean>
<bean id="studentFive" class="com.lyq.spring.pojo.Student">
<property name="age" value="26"/>
<property name="clazz" ref="clazzOne"/>
</bean>
依赖注入 - 给数组类型赋值
<bean id="studentSix" class="com.lyq.spring.pojo.Student">
<property name="clazz" ref="clazzOne"/>
<property name="hobby">
<array>
<value>抽烟</value>
<value>喝酒</value>
</array>
</property>
</bean>
依赖注入 - 给List类型赋值
<bean id="clazzOne" class="com.lyq.spring.pojo.Clazz">
<property name="cid" value="05"/>
<property name="students">
<list>
<ref bean="studentTwo"/>
<ref bean="studentThree"/>
</list>
</property>
</bean>
依赖注入 - 给 Map 类型赋值
<bean id="studentSix" class="com.lyq.spring.pojo.Student">
<property name="age" value="26"/>
<property name="teacherMap">
<map>
<entry key="001" value-ref="teacherOne" />
<entry key="002" value-ref="teacherTwo" />
</map>
</property>
</bean>
<bean id="teacherOne" class="com.lyq.spring.pojo.Teacher">
<property name="tid" value="001"/>
<property name="tname" value="消化"/>
</bean>
依赖注入 - 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 id="studentOne" class="com.lyq.spring.pojo.Student" scope="prototype"/>
<bean id="studentSeven" class="com.lyq.spring.pojo.Student"
p:score="1"
p:age="2"
p:clazz-ref="clazzOne"
></bean>
Spring 管理数据源
Spring 配置文件中引入 jdbc.properties
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 引入 jdbc.properties -->
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="password" value="${jdbc.password}"/>
<property name="username" value="${jdbc.username}"/>
</bean>
</beans>
Spring 引入 druid 数据源
- 加入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
- 编写配置文件
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 引入 jdbc.properties -->
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="password" value="${jdbc.password}"/>
<property name="username" value="${jdbc.username}"/>
</bean>
</beans>
案例 1:bean 的生命周期
- 实例化
- 依赖注入
- 初始化,需要通过 bean 的init-method 属性指定初始化的方法
- IOC 容器关闭时销毁,需要通过 bean 的destroy-method 属性指定销毁的方法
bean 的后置处理器(针对所有 bean)
- 创建处理类
public class MyBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 此方法在 bean 的生命周期初始化之前执行
return null;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 此方法在 bean 的生命周期初始化之后执行
return null;
}
}
- 写在配置文件中
<bean id="myBeanPostProcessor" class="com.lyq.spring.process.MyBeanPostProcessor"/>
Spring 自动装配
基于 xml 的自动装配 - byType
IOC 容器会自动根据类型在bean中那个自动匹配bean。
注意:
- 如果通过类型没有找到对应的 bean,此时不装配,使用默认值
- 如果通过类型找到了多个 bean,会抛出异常,NouniqueBeanDefginitionException 不唯一异常
<bean class="com.lyq.spring.controller.UserController" autowire="byType"/>
基于 xml 的自动装配 - byName
将要赋值的属性的属性名作为 bean 的 id 在 IOC 容器中那个匹配某个 bean,为属性赋值
注意
- 当类型匹配的 bean 有多个时,此时可以使用 byName 实现自动装配
<bean class="com.lyq.spring.controller.UserController" autowire="byName"/>
基于注解管理 bean
标识组件的常用注解
- @Component :将类标识为普通组件
- @Controller:将类标识为控制层组件
- @Service:将类标识为业务层组件
- @Repository:将类标识为持久层组件
使用注解功能需要在配置文件中扫描
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描组件 -->
<context:component-scan base-package="com.lyq.spring">
<!-- 排除 controller 类别的注解 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!-- 包括 controller 类别的注解 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
AOP
代理模式
二十三种设计模式中的一种,属于结构型模式。它的作用是提供一个代理类,让我们在调用的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用
静态代理
1. 创建实现类
public class CalculatorImpl implements Calculator {
public int add(int i, int j) {
int result = i + j;
return result;
}
public int sub(int i, int j) {
int result = i - j;
return result;
}
public int mul(int i, int j) {
int result = i * j;
return result;
}
public int div(int i, int j) {
int result = i / j;
return result;
}
}
2. 创建静态代理类
public class CalculatorStaticProxy implements Calculator{
private CalculatorImpl target;
public CalculatorStaticProxy(CalculatorImpl calculator){
this.target = calculator;
}
public int add(int i, int j) {
System.out.println("日志:方法:add,参数:" + i + " ," + j);
int result = target.add(i, j);
System.out.println("日志:方法:add,结果:" + result);
return result;
}
public int sub(int i, int j) {
System.out.println("日志:方法:sub,参数:" + i + " ," + j);
int result = target.sub(i, j);
System.out.println("日志:方法:sub,结果:" + result);
return result;
}
public int div(int i, int j) {
System.out.println("日志:方法:div,参数:" + i + " ," + j);
int result = target.div(i, j);
System.out.println("日志:方法:div,结果:" + result);
return result;
}
public int mul(int i, int j) {
System.out.println("日志:方法:mul,参数:" + i + " ," + j);
int result = target.mul(i, j);
System.out.println("日志:方法:mul,结果:" + result);
return result;
}
}
3. 创建测试类
public void t(){
CalculatorStaticProxy calculatorStaticProxy = new CalculatorStaticProxy(new CalculatorImpl());
int add = calculatorStaticProxy.add(1, 2);
int mul = calculatorStaticProxy.mul(1, 2);
System.out.println(mul);
System.out.println(add);
}
动态代理
public class ProxyFactory {
private Object target;
public ProxyFactory(Object object){
this.target = object;
}
public Object getProxy(){
/**
* ClassLoader loader :指定加载动态生成的代理类的类加载器
* Class<?>[] interfaces :获取目标对象实现的所有接口的 Class 对象数组
* InvocationHandler h :设置代理类中抽象方法如何重写
*/
ClassLoader classLoader = this.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("实现前" + method.getName() + ", 参数 " + Arrays.toString(args));
/**
* proxy :代表要代理的对象
* method :要执行的方法
* args :要执行方法的到的参数列表
*/
Object result = method.invoke(target, args);
System.out.println("实现前" + method.getName() + ", 结果 " + result);
return result;
}
};
return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
}
public void setTarget(Object target) {
this.target = target;
}
}
通知
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法
- 前置通知
@Before:再被代理的目标方法前面执行 - 返回通知
@AfterReturning:再被代理的目标方法成功执行后执行(寿终正寝) - 异常通知
@AfterThrowing: 再被代理的目标方法异常结束后执行(死于非命) - 后置通知
@After:再被代理的目标方法最终结束后执行(盖棺定论) - 环绕通知
@Around:使用 try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
基于注解的 AOP
导入相关依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.23</version>
</dependency>
</dependencies>
准备被代理的目标资源
导入 Calculator 接口 以及 CalculatorImpl 实现类
基于注解的 - 前置/后置通知
1. 日志类
@Component
@Aspect
public class LoggerAspect {
// 设置公共切入点
@Pointcut("execution(* com.lyq.spring.aop.annotation.CalculatorImpl.*(..))")
public void pointCut(){
}
// @Before("execution(public int com.lyq.spring.aop.annotation.CalculatorImpl.add(int, int))")
// 任意修饰符,任意返回值,在 com.lyq.spring.aop.annotation.CalculatorImpl下面的任意方法,任意参数
// @Before("execution(* com.lyq.spring.aop.annotation.CalculatorImpl.*(..))")
@Before("pointCut()")
public void beforeAdviceMethod(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
// signature.getName() 获取连接点方法名
Object[] args = joinPoint.getArgs();
// joinPoint.getArgs() 获取方法的参数
System.out.println("LoggerAspect, 前置通知 : " + signature.getName());
System.out.println(Arrays.toString(args));
}
@After("pointCut()")
public void afterAdviceMethod(){
System.out.println("后置通知");
}
}
2. spring 配置类
<?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">
<!--
aop 注意事项 :
切面类和目标类都需要交给 IOC 管理
必须通过@Aspects注解标识切面类
再在Spring的配置文件中设置 aop:aspectj-autoproxy 开启注解aop
-->
<context:component-scan base-package="com.lyq.spring.aop.annotation"></context:component-scan>
<!-- 开启基于注解的 AOP -->
<aop:aspectj-autoproxy />
</beans>
基于注解的 - 异常通知
- 需要使用注解
@AfterThrow - 如果需要获取异常信息,只需要指定
@AfterThrow的throwing属性即可
@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
public void afterThrowMethod(JoinPoint joinPoint, Exception ex){
System.out.println("异常通知");
}
基于注解的 - 环绕通知
- 需要使用
@Around注解 - 环绕通知的方法的返回值一定要和目标对象返回值一致
@Around("pointCut()")
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try {
// 环绕通知 - 前置通知的位置
System.out.println("环绕 - 前置");
// 目标对象方法的执行
result = joinPoint.proceed();
// 环绕通知 - 返回通知的位置
System.out.println("环绕 - 返回");
} catch (Throwable throwable) {
throwable.printStackTrace();
// 环绕通知 - 异常通知的位置
System.out.println("环绕 - 异常");
} finally {
// 环绕通知 - 后置通知位置
System.out.println("环绕 - 后置");
}
return result;
}
切面优先级
- 可以通过
@order注解的value属性设置优先级,默认值Integer的最大值 @Order注解的value属性值越小,优先级越高
基于 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:aop="http://www.springframework.org/schema/aop"
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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描组件-->
<context:component-scan base-package="com.lyq.spring.aop.xml"/>
<!--开始 aop-->
<aop:config>
<!--设置公共切入点表达式-->
<aop:pointcut id="pointCut" expression="execution(* com.lyq.spring.aop.xml.CalculatorImpl.*(..))"/>
<!--将 ioc 容器的某个 bean 设置为切面-->
<aop:aspect ref="loggerAspect">
<aop:after method="afterAdviceMethod" pointcut-ref="pointCut" />
<aop:before method="beforeAdviceMethod" pointcut-ref="pointCut" />
<aop:after-returning method="afterReturningAdvice" pointcut-ref="pointCut" returning="result" />
<aop:after-throwing method="afterThrowMethod" pointcut-ref="pointCut" throwing="ex" />
<aop:around method="aroundAdviceMethod" pointcut-ref="pointCut"/>
</aop:aspect>
<aop:aspect ref="validateAspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="pointCut" />
</aop:aspect>
</aop:config>
</beans>