目录
一、简介
1、什么是spring?
2、目的
3、功能及使用范围
二、spring IOC
1、ioc的理解
2、开发人员可达到的目的
3、分析实现
4、bean配置
三、spring IOC的注入方式
1、set方法属性注入
2、构造注入
3、自动装配
四、spring与tomcat的整合/spring与web容器的整合
五、spring AOP
1、aop的特点
2、AOP中关键性概念
3、前置通知
1.1、准备工作
1.2、前置通知
4、后置通知
5、环绕通知
6、异常通知
7、过滤通知
8、AOP的总结
8.1、概念
8.2、应用
六、spring中bean的生命周期
1、spring管理JavaBean初始化过程
1.1、思维导图
1.2、spring容器管理JavaBean的初始化过程(spring中bean的生命周期)。
2、spring的JavaBean管理中单例模式及原型模式
2.1、spring中JavaBean是单例还是多例。
Spring是一个开源的轻量级Java应用开发框架,它提供了一种简单、高效、灵活的方式来构建企业级应用程序。Spring框架的核心特点是依赖注入(Dependency Injection)和面向切面编程(Aspect-Oriented Programming),它通过一组模块化的组件提供全面的支持,使开发人员能够快速搭建可扩展、可维护的应用。
学习Spring的目的可以总结为以下几点:
Spring的功能范围非常广泛,包括但不限于以下方面:
总之,Spring框架是Java开发领域最流行的框架之一,它提供了丰富的功能和特性,帮助开发人员构建可靠、高效的企业级应用程序。
一句话概括,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
IoC(Inversion of Control,控制反转)是Spring框架的核心概念之一,也是实现依赖注入的基础。它通过解耦对象之间的依赖关系,使得对象的创建和管理由框架来负责,而不是由开发人员手动管理。
在传统的程序设计中,对象之间的依赖关系通常由开发人员在代码中进行硬编码,对象直接通过关键字(如new)来创建和管理。这种方式存在一些问题,如对象之间的耦合度高、可测试性差、扩展性差等。
而IoC则是一种思想上的转变,它将对象的创建和管理权利交给了框架。具体来说,IoC的核心思想是:通过配置或注解,告诉框架需要创建哪些对象,以及对象之间的依赖关系。然后,框架在应用程序运行时根据配置信息动态地创建和组装对象。
Spring通过IoC容器来实现控制反转。IoC容器是一个用于管理对象的容器,它负责创建、组装、初始化和销毁对象。开发人员只需要配置对象的创建和依赖关系,然后通过容器来获取需要的对象即可。
- 解耦对象之间的依赖关系:通过IoC容器,开发人员只需要关注对象的功能实现,而不需要关心对象是如何创建和组装的。对象之间的依赖关系由容器负责管理,降低了对象之间的耦合度。
- 提高代码的可测试性:由于对象的创建和组装由容器来完成,开发人员可以很容易地对对象进行替换或模拟,从而实现单元测试和集成测试。
- 增强代码的可扩展性:当需要添加新的功能或模块时,只需要配置新的对象和依赖关系,而不需要修改现有的代码。通过配置方式,可以方便地在不影响现有代码的情况下进行扩展。
- 提高代码的灵活性:IoC容器使得对象的创建完全可配置化,可以在运行时根据需要创建不同的实例。同时,框架提供了生命周期管理和依赖解析等功能,使得对象的管理更加便捷。
总而言之,IoC是Spring框架的核心特性之一,它通过控制反转的思想,将对象的创建和依赖关系的管理交给了框架,减少了对象之间的耦合度,提高了代码的可测试性和可扩展性。
首先创建好我们的maven项目
设置我们的pom.xml
junit junit 3.8.1 test org.springframework spring-context ${spring.version} org.springframework spring-aspects ${spring.version} junit junit ${junit.version} test javax.servlet javax.servlet-api ${javax.servlet.version} provided
我们用原来的方式写一下
public interface UserService {
public void updUser();
}
public class UserServiceImpl1 implements UserService {
public void updUser() {
System.out.println("修改SQL用户数据");
}
我们原来的方式是这样写的
现在我们添加一个实现类
public class UserServiceImpl2 implements UserService {
public void updUser() {
System.out.println("修改SQL用户数据");
//修改用户姓名
System.out.println("修改用户姓名");
}
}
手动实例化的弊端:
1、一旦依赖的接口实现需要大批量改动,迭代,维护成本高
2、当接口的实现类不统一,维护成本更高
我们怎么解决这个问题呢?
首先我们要在maven项目里面的resources文件里面建立一个基于spring的xml文件
我们在web文件里面建立两个Class类
public class UserAction { private UserService us; public UserService getUs() { return us; } public void setUs(UserService us) { this.us = us; } public String updUser() { us.updUser(); return "list"; } }
public class GoodsAction { private UserService us; public UserService getUs() { return us; } public void setUs(UserService us) { this.us = us; } public String updUser() { us.updUser(); return "list"; } }
编写一个测试类
public class demo1 { public static void main(String[] args) { // 加载spring核心配置文件(建模),获取spring的上下文对象,上下文对象中可以获取任何JavaBean对象 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); UserAction userAction = (UserAction) context.getBean("userAction"); userAction.updUser(); GoodsAction goodsAction = (GoodsAction) context.getBean("goodsAction"); goodsAction.updUser(); } }
在我们的新建的这个spring-context.xml文件里面添加
spring的ioc有哪些注入方式呢?
- set方法属性注入
- 构造注入
- 接口注入/自动装配
在我们前面的GoodsAction新添几个属性和get、set方法,一个输出打印的方法toPrint()
public class GoodsAction { private UserService us; private String name; private 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; } public List
getPeoples() { return peoples; } public void setPeoples(List peoples) { this.peoples = peoples; } private List peoples; public UserService getUs() { return us; } public void setUs(UserService us) { this.us = us; } public String updUser() { us.updUser(); return "list"; } public void toPrint() { System.out.println(this.name); System.out.println(this.age); System.out.println(this.peoples); } } 在print-context.xml里面设置值,使用set方法属性注入用property
留守儿童 情侣 留守老人 在测试类里面调用
goodsAction.toPrint();
在我们的User Action里面添加属性及有参无参的构造方法,一个输出打印的方法toPrint()
public class UserAction { private UserService us; private String name; private int age; private List
hobby; public UserAction() { } public UserAction(String name, int age, List hobby) { this.name = name; this.age = age; this.hobby = hobby; } public UserService getUs() { return us; } public void setUs(UserService us) { this.us = us; } public String updUser() { us.updUser(); return "list"; } public void toPrint() { System.out.println(this.name); System.out.println(this.age); System.out.println(this.hobby); } } 在print-context.xml里面设置值,使用构造输入需要用constructor-arg标签
唱 跳 rap 继续在测试类里面调用toPrint方法
自动装配中byName和byType的区别:
byName:在配置好的文件中变量名不更改就不会报错。按照属性的名称进行自动装配。
- 当一个bean的属性名称与其他bean的id匹配时,Spring容器会自动将该bean注入到对应的属性中。
- 如果找不到匹配的bean,则会抛出异常。
- 在XML配置中,可以使用
autowire="byName"
或@Autowired
注解实现byName自动装配。byType:会在整个spring中寻找JavaBean,按照属性的类型进行自动装配。
- 当一个bean的属性的类型与其他bean的class匹配时,Spring容器会自动将该bean注入到对应的属性中。
- 如果有多个匹配的bean,则会抛出异常,需要进行更具体的限定或使用
@Qualifier
注解来解决。- 在XML配置中,可以使用
autowire="byType"
或@Autowired
注解实现byType自动装配。
我们的xml文件
编写一个过滤器
@WebListener public class sprintListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("初始化"); //spring的上下文 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); //tomcat上下文 ServletContext servletContext = sce.getServletContext(); servletContext.setAttribute("sprintContext", context); } }
编写servlet类调用它
@WebServlet("userlist") public class Userservlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取到sprint的上下文对象 ClassPathXmlApplicationContext Context = (ClassPathXmlApplicationContext) req.getServletContext().getAttribute("sprintContext"); UserService bean = (UserService) Context.getBean("userService1"); System.out.println(bean); bean.updUser(); } }
启动服务器就完成了整合
Spring AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架的一个重要模块,提供了在应用程序中进行横切关注点的模块化支持。
- 非侵入性:Spring AOP采用编译期间的代理方式实现切面功能,不需要修改原始类的代码。使用者可以在不侵入原有代码的情况下,将切面逻辑插入到目标对象的方法中,实现对横切关注点的处理。
- 松耦合:Spring AOP通过使用切面来处理横切关注点,将关注点的实现与主要业务逻辑进行分离。切面可以独立于应用程序进行开发和测试,并且可以通过配置和织入的方式来将切面应用到不同的目标对象上,实现代码的松耦合性。
- 横切关注点的集中化管理:Spring AOP支持将相同的横切关注点集中定义在一个切面中,并在需要的地方进行切入。这使得横切关注点的管理更加方便,可以避免代码的重复。
- 与IoC容器的集成:Spring AOP与Spring IoC容器无缝集成,可以借助IoC容器来管理切面和目标对象,实现AOP配置的灵活性和可扩展性。通过注解或XML配置,可以方便地将切面和目标对象注入到容器中并进行织入操作。
- 支持丰富的切点表达式:Spring AOP支持使用切点表达式对目标对象的方法进行精确选择。切点表达式可以根据方法名称、参数、返回类型等条件来定义切点,灵活地进行切面织入。
- 支持不同类型的通知:Spring AOP提供了多种类型的通知,如前置通知、后置通知、环绕通知、异常通知和最终通知。用户可以选择合适的通知类型,并定义切面的处理逻辑。
- 可扩展性:Spring AOP对切面的实现支持继承和组合的机制,可以通过实现Advice接口或继承现有的Advice类来进行切面的定制和扩展。这样使得开发者能够根据具体需求来实现自定义的切面逻辑。
Spring AOP提供了一种灵活、非侵入性的横切关注点处理机制,通过将关注点的实现与主要业务逻辑分离,实现代码的解耦和管理的集中化。同时,与Spring IoC容器的集成以及丰富的切点表达式和通知类型,使得Spring AOP具有很高的扩展性和灵活性。
我们一般会用到系统的日志功能
名称 | 描述 | 注释 |
---|---|---|
连接点(Joinpoint) | 程序执行过程中明确的点,如方法的调用,或者异常的抛出. | |
目标对象(Target) | 被通知(被代理)的对象 | 完成具体的业务逻辑 |
通知(Advice) | 在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理) | 完成切面编程 |
代理(Proxy) | 将通知应用到目标对象后创建的对象(代理=目标+通知),例子:外科医生+护士 | 只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的 |
切入点(Pointcut) | 多个连接点的集合,定义了通知应该应用到那些连接点。 | 也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序 |
适配器(Advisor) | 适配器=通知(Advice)+切入点(Pointcut) |
如果需要可以创建如图一样的项目结构
- 编写接口
public interface IBookBiz { // 购物 public boolean buy(String userName, String bookName, Double price); // 发表评论 public void comment(String userName, String comments); }
- 实现上面的接口
public class BookBizImpl implements IBookBiz { public BookBizImpl() { super(); } public boolean buy(String userName, String bookName, Double price) { // 通过控制台的输出方式模拟购书 if (null == price || price <= 0) { throw new PriceException("book price exception"); } System.out.println(userName + " buy " + bookName + ", spend " + price); return true; } public void comment(String userName, String comments) { // 通过控制台的输出方式模拟发表书评 System.out.println(userName + " say:" + comments); } }
编写一个异常类
public class PriceException extends RuntimeException { public PriceException() { super(); } public PriceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public PriceException(String message, Throwable cause) { super(message, cause); } public PriceException(String message) { super(message); } public PriceException(Throwable cause) { super(cause); } }
配置spring-context.xml
编写一个测试类
public class demo { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz"); bookBiz.buy("小牛子", "钢铁是怎样炼成的", 99.9d); bookBiz.comment("小牛子", "睡不着,来劲了"); } }
添加一个通知类,需要继承MethodBeforeAdvice 重写方法
public class MyMethodBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { // 在这里,可以获取到目标类的全路径及方法及方法参数,然后就可以将他们写到日志表里去 String target = arg2.getClass().getName(); String methodName = arg0.getName(); String args = Arrays.toString(arg1); System.out.println("【前置通知:系统日志】:"+target+"."+methodName+"("+args+")被调用了"); } }
配置通知类,代理,在代理里面配置目标对象和通知
com.tgq.aop.biz.IBookBiz
beforeAdvice 编写测试类
public class demo { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); IBookBiz bookBiz = (IBookBiz) context.getBean("factoryBean"); bookBiz.buy("小牛子", "钢铁是怎样炼成的", 99.9d); bookBiz.comment("小牛子", "睡不着,来劲了"); } }
在前面的基础上再写一个后置通知的类,需要实现AfterReturningAdvice ,重写afterReturning方法
public class MyAfterReturningAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { String target = arg3.getClass().getName(); String methodName = arg1.getName(); String args = Arrays.toString(arg2); System.out.println("【后置通知:买书返利】:"+target+"."+methodName+"("+args+")被调用了,"+"该方法被调用后的返回值为:"+arg0); } }
配置xml
com.tgq.aop.biz.IBookBiz
beforeAdvice myAfterReturningAdvice
编写一个环绕通知类,实现MethodInterceptor接口,重写方法
public class MyMethodInterceptor implements MethodInterceptor {//Interceptor拦截器的意思 @Override public Object invoke(MethodInvocation arg0) throws Throwable { String target = arg0.getThis().getClass().getName(); String methodName = arg0.getMethod().getName(); String args = Arrays.toString(arg0.getArguments()); System.out.println("【环绕通知调用前:】:" + target + "." + methodName + "(" + args + ")被调用了"); // arg0.proceed()就是目标对象的方法 Object proceed = arg0.proceed(); System.out.println("【环绕通知调用后:】:该方法被调用后的返回值为:" + proceed); return proceed; } }
配置xml
com.tgq.aop.biz.IBookBiz
myMethodInterceptor
编写一个异常通知类,实现ThrowsAdvice 接口,编写方法,方法名必须是afterThrowing
public class MyThrowsAdvice implements ThrowsAdvice { public void afterThrowing(PriceException ex) { System.out.println("【异常通知】:当价格发生异常,那么执行此处代码块!!!"); } }
配置xml文件
com.tgq.aop.biz.IBookBiz
myMethodInterceptor myThrowsAdvice 测试,如果我们的价格有问题会出现异常通知
public class demo { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); // IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz"); IBookBiz bookBiz = (IBookBiz) context.getBean("factoryBean"); bookBiz.buy("小牛子", "钢铁是怎样炼成的", -99.9d); bookBiz.comment("小牛子", "睡不着,来劲了"); } }
我们配置我们的过滤通知,需要在xml里面进行配置
com.tgq.aop.biz.IBookBiz
beforeAdvice
regexpMethodPointcutAdvisor
myMethodInterceptor
myThrowsAdvice
测试结果,我们的评论后就没有返利了,我们只有购物后评论前才有返利
8.1、概念
aop是面向切面编程,程序是由上至下执行,但是aop面向切面编程不是,aop的程序执行,首先当程序执行到目标对象的目标方法时,如果连接点上由前置通知,则先执行前置通知,再执行目标方法,如果没有前置通知,则先执行前置通知,再执行目标方法,如果没有前置通知,则继续执行目标方法;再查看目标方法上是否有后置通知,如果有,则再执行后置通知代码。
8.2、应用
不管是前置通知、后置通知、环绕通知、异常通知还是过滤通知,代码都是非业务核心代码,比如日志和事务的管理。
我们还可以去学习一下:jdk代理与cglib代理,aop数据字典等其他
- 初始化 init
- 使用 service
- 销毁 destroy
BeanDefinition:包含了很多属性和方法。例如:id、class(类名)、
scope、ref(依赖的bean)等等。其实就是将bean(例如
)的定义信息 存储到这个对应BeanDefinition相应的属性中
例如:
-----> BeanDefinition(id/class/scope) Aware感知接口:在实际开发中,经常需要用到Spring容器本身的功能资源。
例如:BeanNameAware、ApplicationContextAware等等
BeanDefinition 实现了 BeanNameAware、ApplicationContextAware
BeanPostProcessor:后置处理器。在Bean对象实例化和引入注入完毕后,
在显示调用初始化方法的前后添加自定义的逻辑。(类似于AOP的绕环通知)
前提条件:如果检测到Bean对象实现了BeanPostProcessor后置处理器才会执行
Before和After方法
BeanPostProcessor
BeanDefinitionReader:解析Bean的定义。在Spring容器启动过程中,会将Bean解析成Spring内部的BeanDefinition结构;
理解为:将spring.xml中的
标签转换成BeanDefinition结构 有点类似于XML解析
BeanPostProcessor:后置处理器。在Bean对象实例化和引入注入完毕后,
在显示调用初始化方法的前后添加自定义的逻辑。(类似于AOP的绕环通知)
前提条件:如果检测到Bean对象实现了BeanPostProcessor后置处理器才会执行
Before和After方法
BeanPostProcessor
- xml/annotation/configuation配置JavaBean
- BeanDefinitionReader解析配置的JavaBean得到BeanDefinition,最终得到List
集合。 - 触发BeanFactoryPoatProcessor,JavaBean初始化之前执行自己的业务。
- spring中beanFactory,会通过List
集合遍历初始化所有的JavaBean对象。 - 如果自己的JavaBean需要调动spring上下文中的资源,那么需要实现*Aware感知接口
- 如果自己的JavaBean已经初始化好了,还需要扩展功能,那么需要借助BeanPostProcessor来实现。
- 默认是单例的,但是可以配置多例的。
- 单例的优点:节约内存;弊端:有变量污染;
多例的优点:无变量污染;弊端:消耗内存;public class ParamAction { private int age; private String name; private List
hobby; private int num = 1;//初始值 // private UserBiz userBiz = new UserBizImpl1(); public ParamAction() { super(); } public ParamAction(int age, String name, List hobby) { super(); this.age = age; this.name = name; this.hobby = hobby; } public void execute() { // userBiz.upload(); // userBiz = new UserBizImpl2(); System.out.println("this.num=" + this.num++); System.out.println(this.name); System.out.println(this.age); System.out.println(this.hobby); } }
xml配置@org.junit.Test public void test1() { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-context.xml"); // ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-context.xml"); ParamAction p1 = (ParamAction) applicationContext.getBean("paramAction"); ParamAction p2 = (ParamAction) applicationContext.getBean("paramAction"); System.out.println("p1 == p2:" + (p1 == p2)); if (p1 != null && p2 != null) { p1.execute(); p2.execute(); } applicationContext.close(); // 单例时,容器销毁instanceFactory对象也销毁;多例时,容器销毁对象不一定销毁; }
抽烟 烫头 大保健 - 单例:JavaBean是跟着spring上下文初始化的;(容器生对象生,容器死对象死)
多例:JavaBean是使用的时候才会创建,销毁跟着jvm走。
xml配置public class InstanceFactory { public void init() { System.out.println("初始化方法"); } public void destroy() { System.out.println("销毁方法"); } public void service() { System.out.println("业务方法"); } }
@org.junit.Test public void test2() {// // 多例不会调用我们的初始化方法 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-context.xml"); //多例模式的时候,只要调用就会初始化 InstanceFactory instanceFactory = (InstanceFactory) applicationContext.getBean("instanceFactory"); }
// BeanFactory会初始化bean对象,但会根据不同的实现子类采取不同的初始化方式
// 默认情况下bean的初始化,单例模式立马会执行,但是此时XmlBeanFactory作为子类,单例模式下容器创建,bean依赖没有初始化,只有要获取使用bean对象才进行初始化
@org.junit.Test
public void test3() {
// ClassPathXmlApplicationContext applicationContext = new
// ClassPathXmlApplicationContext("/spring-context.xml");
Resource resource = new ClassPathResource("/spring-context.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
// 调用会初始化
// InstanceFactory i1 = (InstanceFactory) beanFactory.getBean("instanceFactory");
}
什么情况下会使用多例:一个类里面的属性值有变化,会使用多例