Bean 的生命周期指的就是由 Spring 管理的对象
从创建到销毁
的过程,和人生老病死的过程一样。
它主要分为三个阶段:创建 --> 初始化 --> 销毁
Spring 的工厂创建对象的方式分两类:
1. singleton 模式
当 scope 属性为 singleton ,创建 Spring 工厂的同时创建
所有单例对象。
例如:
新建 User 类:
public class User {
String name;
int age;
public User() {
System.out.println("调用User的构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
spring 配置文件注册 bean :
<bean id="user" class="com.xxl.model.User">
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>
测试:
@Test
public void testSpring(){
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
}
执行结果:
我们发现当创建 Spring 工厂的同时就会调用对象的构造方法。因为 spring 中 bean 默认的 scope 就是 singleton
,所以创建工厂的同时默认就会创建多个单例对象。
如果想修改创建单例对象的方式为「获取的时候才创建」,只需要在 bean 标签上面添加如下属性:
lazy-init="true"
例如:
<bean id="user" class="com.xxl.model.User" lazy-init="true">
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>
2. prototype 模式
只有获取对象的时候才会创建对象。
修改 bean 标签的 scope 属性:
<bean id="user" class="com.xxl.model.User" scope="prototype">
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>
测试:
@Test
public void testSpring(){
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
Object user = act.getBean("user");
System.out.println(user);
}
执行结果:
通过上面的例子我们发现只有当执行 getBean() 方法的时候才会调用该类的构造方法。
spring 中 bean 的初始化操作指的是在创建对象的时候完成一些附加
的功能。bean 的初始化操作有两种实现方式:
1.实现 InitializingBean 接口
public class 类名 implements InitializingBean {
public void afterPropertiesSet(){
// 初始化方法操作
}
}
例如:
public class User implements InitializingBean {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 初始化操作
@Override
public void afterPropertiesSet(){
this.name = "张无忌";
this.age = 30;
}
}
测试:
@Test
public void testSpring(){
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
Object user = act.getBean("user");
System.out.println(user);
}
执行结果:
2.通过创建普通方法完成初始化
在 User 类中创建一个方法
// 初始化方法
public void initMethod() {
this.name = "张无忌";
}
在配置文件中配置 init-method
属性
<bean id="user" class="com.xxl.model.User" init-method="initMethod" >
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>
测试:
@Test
public void testSpring(){
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
Object user = act.getBean("user");
System.out.println(user);
}
执行结果:
我们发现该初始化方法在创建对象之后修改了 user 对象的名字。
总结:
初始化方法修改了注入的值,所以初始化方法一定在「注入之后」执行。
Spring 销毁对象前,会调用对象的销毁方法,完成销毁操作。
Spring 什么时候销毁所创建的对象?当 Spring 工厂关闭时,Spring 工厂会调用我们自定义的销毁方法。
销毁方法的定义有两种方式:
1.实现DisposableBean
接口
public class 类名 implements DisposableBean {
// 销毁操作
@Override
public void destroy(){
// 销毁操作业务
}
}
2.创建普通方法
在 User 类中创建一个方法
// 销毁方法
public void destroyMethod() {
// 销毁操作业务
}
在配置文件中配置 destroy-method
属性
<bean id="user" class="com.xxl.model.User" destroy-method="destroyMethod">
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>
Spring 工厂创建完对象后如果还想对对象干点别的事情,除了初始化阶段,还可以采用Bean的后置处理
。
Bean 的后置处理:对 Spring 工厂创建的对象进行二次加工处理,就是创建完对象后再干点别的事。
Bean 后置处理的流程:
1.实现 「BeanPostProcessor」 接口
public class BeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置bean:before 方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置bean:after 方法");
if (bean instanceof User) {
User user = (User) bean;
user.setName("亚里士多德");
return user;
}
return bean;
}
}
2.配置文件添加 bean
<bean id="beanProcessor" class="com.xxl.config.BeanProcessor"/>
3.测试
@Test
public void testSpring(){
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
Object user = act.getBean("user");
System.out.println(user);
}
执行结果:
前面我们学习了对象的初始化方法,那么初始化方法和 Bean 的后置处理的执行顺序是什么?
我们来修改一下 User 类,测试一下:
public class User implements InitializingBean {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public void afterPropertiesSet(){
System.out.println("初始化方法");
}
}
测试执行顺序:
其实在实际开发中,我们很少对 Spring 工厂创建的对象进行初始化操作,一般是采用 Bean 的后置处理的方式来加工对象。
BeanPostProcessor 接口有两个方法,这里简称 before 和 after 方法。
这两个方法都是先获取 Spring 创建的对象,然后再对其加工,加工完成后再交给 Spring。
因为这两个方法的作用一样,所以我们一般采用其中的一个方法,这里建议采用 after 方法。
从上面的例子中我们得到了Spring 操作 bean 的顺序:
咱们先来看一个需求:在所有方法的执行前后输出一段日志。
程序小白可能会这样写:
接口:
public interface CalculateService {
// 加法
int add(int a,int b);
// 减法
int sub(int a,int b);
}
实现类:
public class CalculateServiceImpl implements CalculateService {
@Override
public int add(int a, int b) {
System.out.println("方法执行前打印");
int result = a + b;
System.out.println("方法执行后打印");
return result;
}
@Override
public int sub(int a, int b) {
System.out.println("方法执行前打印");
int result = a - b;
System.out.println("方法执行后打印");
return result;
}
}
但是这样写有 3 个问题:
1.代码混乱:业务代码和非业务代码混在一起,看着太乱了
2.代码重复:如果有多个方法,那就要写一堆输出日志的代码片段,吃力不讨好。
3.代码耦合:如果非业务代码(日志打印)要做修改,那所有相关的业务方法都要改一遍,代码耦合度太高。
那有什么解决办法呢?使用代理
。
生活中有关代理的例子无处不在,例如:一些大学可以面向全球招生,所以会衍生很多留学中介,这些中介可以帮学校招生。
所以中介的作用就是帮助雇主做事,有了中介,雇主变得很轻松。而在 java 开发中,也存在这样的代理关系,它的专业术语是代理设计模式。
代理设计模式可以很好解决上面开发中遇到的三个问题,帮助我们简化代码、提高工作效率。
代理设计模式:通过代理类为目标类做一些额外(非业务)的功能。
专业名词解释
:
1.目标类(原始类):指的是完成业务的核心类,一般指的是 service 层的各种实现类。
2.目标方法(原始方法):目标类中的方法是目标方法(原始方法)。
3.额外功能(附加功能):打印日志等非业务功能。
代理设计模式开发步骤:
(1)代理类和目标类实现相同的接口
(2)代理类中除了要调用目标类的方法实现业务功能,还要实现额外功能。
例如:
// 接口
public interface CalculateService {
业务方法
}
// 目标类
public CalculateServiceImpl implements CalculateService {
业务方法
}
// 代理类:要实现目标类相同的接口
public CalculateServiceProxy implements CalculateService {
// 业务方法
// 额外功能
}
静态代理:给每一个目标类手动开发一个代理类。
例如:
public interface CalculateService {
// 加法
int add(int a,int b);
// 减法
int sub(int a,int b);
}
// 目标类
public CalculateServiceImpl implements CalculateService {
@Override
public int add(int a, int b) {
int result = a + b;
return result;
}
@Override
public int sub(int a, int b) {
int result = a - b;
return result;
}
}
// 代理类:要实现目标类相同的接口
public CalculateServiceProxy implements CalculateService {
private CalculateService calculateService = new CalculateServiceImpl();
@Override
public int add(int a, int b) {
System.out.println("方法执行前打印");
int result = calculateService.add(a,b);
System.out.println("方法执行后打印");
return result;
}
@Override
public int sub(int a, int b) {
System.out.println("方法执行前打印");
int result = calculateService.sub(a,b);
System.out.println("方法执行后打印");
return result;
}
}
通过上面的例子我们发现静态代理也存在很多问题:
1.如果存在很多目标类,我们就要手动创建一堆代理类,太繁琐。
2.代理类中混杂着目标类方法和额外功能,代码耦合度高。
那有没有这样一种代理模式?
有的,动态代理闪亮登场!
动态代理:也是通过代理类为目标类做一些额外的功能,但是不用手动写一堆代理类,而是动态地为目标类创建代理类。
开发流程:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
这里我们主要是引入了 aspectj 这个技术,aspectj 是 spring 社区中非常流行的基于动态代理技术的框架。
接口:
public interface CalculateService {
// 加法
int add(int a,int b);
// 减法
int sub(int a,int b);
}
实现类(目标类):
public class CalculateServiceImpl implements CalculateService {
@Override
public int add(int a, int b) {
int result = a + b;
System.out.println("加法操作。。。");
return result;
}
@Override
public int sub(int a, int b) {
int result = a - b;
System.out.println("减法操作。。。");
return result;
}
}
3.在 spring 配置文件中注册 bean
<bean id="calculateService" class="com.xxl.service.impl.CalculateServiceImpl" />
4.实现额外功能
这里我们需要创建一个类实现 MethodInterceptor 接口:
/**
* @Desc: 动态代理完成非业务功能
* @Author: 知否技术
* @date: 下午8:49 2022/5/4
*/
public class PrintLog implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("在目标方法执行之前打印。。。。。");
// 执行目标方法
Object object = methodInvocation.proceed();
System.out.println("在目标方法执行之后打印。。。。。");
return object;
}
}
5.注册完成额外功能的 bean
<bean id="printLog" class="com.xxl.aop.PrintLog" />
<!--切入点:给哪些方法加入额外功能-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* * (..))"/>
</aop:config>
<!--切入点:给哪些方法加入额外功能-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* * (..))"/>
<aop:advisor advice-ref="printLog" pointcut-ref="pc"/>
</aop:config>
8.测试
@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
CalculateService calculateService = (CalculateService) act.getBean("calculateService");
// 3.调用方法
int result = calculateService.add(1, 2);
System.out.println("result:" + result);
}
讲解:
1.上面的例子中我们定义了一个 PrintLog 打印日志的类,并实现了 MethodInterceptor 接口的 invoke 方法。invoke 方法里面实现了在目标方法执行前后打印日志的功能。
2.invoke 方法的返回值就是原始方法的返回值,上个例子中的原始方法就是 add 方法。
3.aop:config 这个标签用来配置切入点和额外功能。上面例子中额外功能就是在要执行的方法前后打印日志,而切入点就是额外功能要作用的位置:比如某些类上或者某些方法上。
4.execution(* * (..)) 是切入点表达式,表示作用在所有类的所有方法上,这个后面会讲。
5.上面的例子表示:你无论执行哪个方法,这个方法的前面和后面都会打印一段日志。
我们通过 spring 的工厂获取的对象,其实是通过动态代理技术创建的代理类。那这个代理类在哪里?
当程序运行的时候,spring 框架通过动态字节码技术
在 JVM 内存中为目标类创建代理类。当程序运行结束的时候,这个代理类就会随之消亡。
所以使用动态代理不需要手动创建多个代理类。
AOP: 全称 Producer Oriented Programing,即面向切面编程。
那啥是面向切面编程?其实说白了还是 Spring 的动态代理,通过代理类为原始类增加一些 额外功能(例如打印等)。
那啥是切面?
切面 = 切入点 + 额外功能。
切入点:额外功能作用的位置,在哪些类哪些方法上。
额外功能作用在不同的类上面,我们都知道点连接起来构成面,所以不同的切入点连接起来构成了切面,这个切面就像刀切西瓜一样切在不同的类上面,所以额外功能就对这些类中的方法起了作用。
AOP 的底层还是使用 Spring 的动态代理技术创建代理类对象。
动态代理的方式分为两种:
创建代理对象的三个元素:
代码格式:
Proxy.newPorxyInstance(classloader,interfaces,invocationHandler)
讲解:
(1)classloader:叫做类加载器,它可以用来创建代理对象。
创建方式:
类.class.getClassLOader()
(2)interfaces:原始对象实现的接口
创建方式
接口.getClass().getInterfaces()
(3)invocationHandler:额外功能
创建方式:
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---- 方法执行前打印 ----");
// 执行原始方法
Object ret = method.invoke(caculateService, args);
System.out.println("---- 方法执行后打印 ----");
return ret;
}
};
完整代码:
@Test
public void testJDKProxy() {
// 1. 原始对象
CalculateService calculateService = new CalculateServiceImpl();
// 2. JDK 动态代理:包含额外功能
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---- 方法执行前打印 ----");
// 执行原始方法
Object result = method.invoke(calculateService, args);
System.out.println("---- 方法执行后打印 ----");
return result;
}
};
// 3. 代理类
CalculateService calService = (CalculateService) Proxy.
newProxyInstance(CalculateService.class.getClassLoader(),
calculateService.getClass().getInterfaces(),
handler);
// 4. 执行方法
int result = calService.add(1, 2);
System.out.println("result:" + result);
}
测试结果:
CGlib 创建动态代理的原理:原始类作为父类,代理类作为子类,通过继承关系创建代理类。
代码格式:
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(classLoader);
enhancer.setSuperclass(calculateService);
enhancer.setCallback(interceptor);
讲解:
(1)classLoader:类加载器(了解即可)
(2)Superclass:父类,就是原始类
(3)interceptor:额外功能
MethodInterceptor interceptor = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("---- 方法执行前打印 ----");
// 执行原始方法
Object result = method.invoke(calculateService, args);
System.out.println("---- 方法执行后打印 ----");
return result;
}
};
完整代码:
@Test
public void testCglibProxy() {
// 1. 原始对象
CalculateService calculateService = new CalculateServiceImpl();
// 2. Cglib 动态代理:包含额外功能
MethodInterceptor interceptor = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("---- 方法执行前打印 ----");
// 执行原始方法
Object result = method.invoke(calculateService, args);
System.out.println("---- 方法执行后打印 ----");
return result;
}
};
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(CalculateService.class.getClassLoader());
enhancer.setSuperclass(calculateService.getClass());
enhancer.setCallback(interceptor);
// 3. 创建代理类
CalculateService calService = (CalculateService)enhancer.create();
// 4. 执行方法
int result = calService.add(3, 4);
System.out.println("result:" + result);
}
执行结果:
Spring 是如何为原始对象创建目标对象的呢?是通过 BeanPostProcessor。
前面我们讲过 BeanPostProcessor 可以对对象进行二次加工,所以可以用来创建代理对象。
Spring 创建代理对象的流程:
/**
* @Desc: 后置bean创建代理对象
* @Author: 知否技术
* @date: 上午11:59 2022/5/5
*/
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
InvocationHandler handler = (proxy, method, args) -> {
System.out.println("--- 方法执行前打印6666666---");
Object ret = method.invoke(bean, args);
System.out.println("--- 方法执行后打印7777777---");
return ret;
};
return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler);
}
}
<bean id="calculateService" class="com.xxl.service.impl.CalculateServiceImpl" />
<bean id="proxyBeanPostProcessor" class="com.xxl.aop.ProxyBeanPostProcessor"/>
@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
CalculateService calculateService = (CalculateService) act.getBean("calculateService");
// 3.调用方法
int result = calculateService.sub(7, 2);
System.out.println("result:" + result);
}
开发流程:
@Aspect
public class TestAspect {
// 前置通知:方法执行前添加额外功能
@Before("execution(* *(..))")
public void beforePrint(){
System.out.println("------before: 方法执行前打印~");
}
//后置通知: 方法执行后添加额外功能
@After("execution(* *(..))")
public void afterPrint(){
System.out.println("------after: 方法执行前打印~");
}
// 环绕通知:方法执行前后添加额外功能
@Around("execution(* *(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("方法执行前打印~");
Object result = joinPoint.proceed();
System.out.println("方法执行后打印~");
return result;
}
}
<bean id="testMyAspect" class="com.xxl.aop.TestAspect" />
<!-- 扫描 aop 相关注解-->
<aop:aspectj-autoproxy/>
@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
CalculateService calculateService = (CalculateService) act.getBean("calculateService");
// 3.调用方法
int result = calculateService.add(100, 1);
System.out.println("result:" + result);
}
讲解:
1.我们新建了一个 TestMyAspect 类,然后添加 @Aspect 注解,表示这是一个切面类,专门用来完成非业务功能的。
2.在这个类中,我们创建了三个方法,其中 @Before 注解标注的方法表示在目标方法操作前
执行。@After 注解标注的方法表示在目标方法操作后
执行。@Around 注解标注的方法表示在目标方法操作前后
执行。
3.在实际开发中一般使用 @Around 注解标注的方法完成非业务功能。
4.我们新建了这个切面类,但是 spring 不知道啊,所以需要在 Spring 的配置文件中注册一下 bean。
5.现在 Spring 工厂能够管理这个类了,但是 Spring 不知道他是切面类啊!所以需要配置一下扫描注解的标签。
6.然后通过 Spring 获取创建的类,我们获取的其实是 Spring 通过后置 Bean 加工后的代理类。
切入点复用
我们可以在切面类中定义⼀个方法,方法上面标注 @Pointcut 注解。然后就可以重复使用切入点表达式了:
@Aspect
public class TestAspect {
@Pointcut("execution(* *(..))")
public void myPointcut() {}
// 环绕通知:方法执行前后添加额外功能
@Around(value = "myPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("方法执行前打印~");
Object result = joinPoint.proceed();
System.out.println("方法执行后打印~");
return result;
}
}
切入点:额外功能加入的位置。
<aop:pointcut id="pc" expression="execution(* * (..))"/>
public int add(int a, int b)
* * (..)
第一个 * 表示方法的修饰符和返回值 第二个 * 是方法名 .. 表示方法中的参数
1.(包.类.方法)切入点:
修饰符-返回值 包.类.方法(参数)
expression="execution(* com.xxl.service.caculateServiceImpl.add(..))"
2.指定切入点为某个包下的所有类中的所有方法:
修饰符-返回值 包.类.方法(参数)
expression="execution(* com.xxl.service.*.*(..))"
3.@annotation
作用:用于匹配当前执行方法持有指定注解的方法,并为之加入额外的功能。
例如我们自定义了一个注解:NoToken
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoToken {
}
方法中添加自定义注解:
@Override
@NoToken
public int add(int a, int b) {
int result = a + b;
System.out.println("加法操作。。。");
return result;
}
然后我们要为包含 NoToken 注解的方法加入额外功能:
@Aspect
public class TestAspect {
// 环绕通知:方法执行前后添加额外功能
@Around("@annotation(com.xxl.annotion.NoToken)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("包含 NoToken 注解--------------");
Object result = joinPoint.proceed();
return result;
}
}
测试:
@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
CalculateService calculateService = (CalculateService) act.getBean("calculateService");
// 3.调用方法
int result1 = calculateService.add(99, 1);
System.out.println("-----------------------");
int result2 = calculateService.sub(99, 1);
System.out.println("result1:" + result1);
System.out.println("result2:" + result2);
}
-END-