Spring入门(IoC控制反转和AOP面向切面编程)
Spring诞生:
- 作者:Rod Johnson
- 目的:创建Spring的目的就是用来替代更加重量级的的企业级Java技术,比如EJB(Enterprise Java Beans)技术
- 功能:简化Java的开发
-
基于POJO(Plain Ordinary Java Object简单的Java对象)轻量级和最小侵入式开发
-
通过依赖注入(Dependency Injection)和面向接口实现松耦合
-
基于切面和惯例进行声明式编程
-
通过切面和模板减少样板式代码
简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
Spring是非侵入式的:
- 要了解非侵入式概念,就要先了解侵入式的概念。对于EJB、Struts2等一些传统的框架,通常是要实现特定的接口,继承特定的类才能增强功能,都改变了java类的结构,这称之为侵入式的。
- 对于Hibernate、Spring等框架,对现有的类结构没有影响,就能够增强JavaBean的功能,这就是非侵入式的。
- 但这都不是绝对的,框架不同于库的一个主要的点就是框架强制更改了开发者的使用习惯。
IoC与DI
IoC和DI是Spring的两个核心概念,很多人都把它们视为相同的东西,但事实并非如此。
IoC(Inversion of Control):控制反转。
DI(Dependency Injection):依赖注入。
控制反转是一种面向对象的思想,它是一种宽泛的概念,只要一个类将对它内部状态的控制权交由其他机制去完成即为『控制反转』。控制反转是为了降低类与类之间的耦合度。
IoC是编程领域中广泛应用的一种模式,实现方式主要有两种:依赖查找和依赖注入,而依赖注入是一种更为可取的方式,因此Spring使用后者实现控制反转。
Spring采用依赖注入这一具体的手段来达到控制反转的目的。
所谓的依赖注入,就是甲方开放接口,在它需要的时候,能够讲乙方传递进来(注入)。
所谓的控制反转,就是甲乙双方不相互依赖,交易活动的进行不依赖于甲乙任何一方,整个活动的进行由第三方负责管理(我们称它为IoC容器)。
IoC最初的目的就是充分利用OOP的多态性,使得通过配置文件而不是在代码里硬编码(hardcode)的方式来实例化对象和装配对象图,这样就有了为不同的客户场景服务的灵活性(不同的客户通过配置文件使用不同的子类)。你的代码不在被直接调用,而是被框架代码所调用,所以框架与类库的区别就在于控制反转(比如说,http请求不在由你的Servlet/Filter直接处理,而是交由Struts/Spring mvc的Servlet/Filter处理后,再把结果分配给你的组件)。
AOP(面向切面编程)
AOP(Aspect-OrientedProgramming,面向切面编程)可以说是对面向对象(OOP)的补充和扩展。OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系,这些散布在各处的与业务逻辑无关的代码被称为“横切代码”。
AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
(切面):通常是一个类,里面可以定义切入点和通知,运行时可以在方法执行前后加入通知的代码,实现方法的横切。
Spring模块组成
Spring Core: spring的核心功能,IOC容器, 解决对象创建及依赖关系。
Spring Web :Spring对web模块的支持。
Spring DAO :Spring 对jdbc操作的支持 【JdbcTemplate模板工具类】。
Spring ORM :spring对orm的支持(可以与hibernate集成)。
Spring AOP :面向切面编程。
Spring Context:Spring的上下文环境。
快速入门
- 首先导入相关依赖,pom.xml中引入(这里以Web程序为例):
<!-- Spring包core 这里我采用spring.version=5.0.1.RELEASE版本 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring的Context包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring的web包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring的AOP包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency>
- 创建Spring配置文件,可以命名为spring.xml或bean.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" 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 http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> </beans>
- 创建实体类Student,这里我就直接省略了,直接配置spring-student.xml文件,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: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 http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 引入外部配置文件 --> <import resource="spring-address.xml"/> <!-- 日期格式注入 --> <bean id="dateFormat" class="java.text.SimpleDateFormat"> <constructor-arg value="yyyy-MM-dd"></constructor-arg> </bean> <bean id="person" abstract="true" class="com.zking.entity.Person"> <property name="name"><value>未知</value></property> </bean> <!-- id:不能以/开头,在容器中查找id相应的bean对象。id必须唯一 class:类的全路径名 name:在容器中查询bean对象的名字(唯一,允许以/开头、允许多个值,各个值之间用逗号隔开) scope:(singleton|prototype)默认是singleton 1)singleton(单例模式):在每个Spring IoC容器中一个bean定义对应一个对象实例 2)prototype(原型模式/多例模式):一个bean定义对应多个对象实例 abstract:将一个bean定义成抽象bean(抽象bean是不能实例化的),抽象类一定要定义成抽象bean,非抽象类也可以定义成抽象bean parent:指定一个父bean(必须要有继承关系才行) init-method:指定bean的初始化方法 constructor-arg:使用有参数构造方法创建javaBean --> <bean id="student" name="/a b c" parent="person" init-method="init" class="com.zking.entity.Student" scope="prototype"> <property name="name"> <value>小花</value> </property> <!-- 初始化日期类型,property标签的name属性代表实体Bean的属性 --> <property name="birthday"> <bean factory-bean="dateFormat" factory-method="parse"> <constructor-arg><value>1999-02-13</value> </constructor-arg> </bean> </property> <!-- ref:引用其他的类型 --> <property name="address"> <ref bean="address" /> </property> <!-- 复杂类型注入:数组/list/map/properties --> <property name="arr"> <array> <value>张三</value> <value>王五</value> <value>李四</value> </array> </property> <property name="list"> <list> <value>1</value> <value>王五</value> <value>true</value> </list> </property> <property name="map"> <map> <entry> <key> <value>1</value> </key> <value>你好</value> </entry> <entry> <key> <value>2</value> </key> <value>魔力</value> </entry> <entry> <key> <value>3</value> </key> <value>梨花</value> </entry> </map> </property> <property name="per"> <props> <prop key="1">张三</prop> <prop key="2">王五</prop> <prop key="3">李四</prop> </props> </property> </bean> </beans>
- 根据配置文件获取Spring上下文容器:
ApplicationContext a1 = new ClassPathXmlApplicationContext("classpath:spring.xml"); //多文件配置,通配符* ApplicationContext a2 = new ClassPathXmlApplicationContext("classpath:spring-*.xml"); //自定义配置文件采用数组 ApplicationContext a3 = new ClassPathXmlApplicationContext(new String[] {"spring.xml","spring-address.xml"});
- 关于获取Bean对象:
//只根据类对象来获取bean对象 Hello hello = ac.getBean(Hello.class); //根据id属性获取bean对象 Student stu1 = ac.getBean("student",Student.class); //根据name属性获取bean对象,默认单例模式,返回同一个对象 Student stu2 = ac.getBean("/a",Student.class); Student stu3 = ac.getBean("b",Student.class); //指定了父类的bean可以访问父类的属性和构造函数,name属性来自person System.out.println(stu.getName()); //获取bean的集合属性
ac.getBean("student",Student.class).getList().forEach(System.out::println);
- 实现AOP功能:
要实现Spring的AOP功能,就必须要先了解一些概念:
1.连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出。
2.目标(Target):被通知(被代理)的对象,也就是具体的业务方法。
注1:完成具体的业务逻辑3.通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理)。
注2:面向切面的具体功能在此被定义,完成切面编程4.代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知),将通知与目标对象结合然后返回一个代理对象给程序使用。
注3:只有代理对象才有AOP特性,而AOP的代码是写在通知的方法里面的5.切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点,切入点被定义在bean对象的property标签中。
(也将Pointcut理解成一个限制条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)
6.适配器/切面(Advisor):适配器=通知(Advice)+切入点(Pointcut)
1)准备业务实现类:
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); } }
2):通知分为前置通知、后置通知、环绕通知、异常通知、最终通知。
首先定义通知实现类:
前置通知:
package com.zking.advice; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.MethodBeforeAdvice; /** * 前置通知实现类,实现MethodBeforeAdvice接口,在方法执行之前执行的代码 * 在每一个执行方法之前加入日志记录功能 * @author star * */ public class BeforeAdvice implements MethodBeforeAdvice { /** * @param method 要切入的方法 * @param params 方法的执行参数 * @param target 目标对象 */ @Override public void before(Method method, Object[] params, Object target) throws Throwable { // TODO Auto-generated method stub String methodName = method.getName();//获取方法名 String className = target.getClass().getName(); System.out.println("[前置通知]"+className+"."+methodName+" 执行参数"+Arrays.toString(params)); } }
后置通知:
package com.zking.advice; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.AfterReturningAdvice; /** * 后置通知,实现AfterReturningAdvice接口,在方法执行完毕之后执行的代码,如果遇到异常则不会执行,而是抛出异常 * 好评返利两元 * @author star * */ public class AfterAdvice implements AfterReturningAdvice { /** * * @param returnValue 返回值 * @param method 执行方法 * @param args 方法执行参数 * @param target 目标对象 * @throws Throwable 可处理异常 */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { // TODO Auto-generated method stub String methodName = method.getName(); String className = target.getClass().getName(); System.out.println("[后置通知]"+className+"."+methodName+",执行参数:"+Arrays.toString(args)+"返回值:"+returnValue); } }
环绕通知:
package com.zking.advice; import java.lang.reflect.Method; import java.util.Arrays; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 环绕通知,可以控制方法的执行,实现MethodInterceptor接口,可以在前后都执行。 * @author star * */ public class RoundAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { // TODO Auto-generated method stub Object[] params = invocation.getArguments();//获取执行参数 Method method = invocation.getMethod();//获取执行方法 Object target = invocation.getThis();//获取目标对象 System.out.println("[环绕通知]"+target.getClass().getName()+"."+method.getName()+",执行参数:"+Arrays.toString(params)); //调用方法并且执行 Object returnValue = invocation.proceed(); System.out.println("[环绕通知]"+target.getClass().getName()+"."+method.getName()+",执行参数:"+Arrays.toString(params)+"方法执行完毕,返回值:"+returnValue); return returnValue; } }
异常通知:
package com.zking.advice; import org.springframework.aop.ThrowsAdvice; /** * 异常通知,实现ThrowsAdvice接口 * @author star * */ public class ExceptionAdvice implements ThrowsAdvice { /** * 自定义的异常通知 * @param e */ public void afterThrowing(PriceException e) { System.out.println("[异常通知]买书价格异常:订单提交失败!"); } }
配置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" 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 http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 定义目标对象 --> <bean id="bookBiz" class="com.zking.biz.BookBizImpl"> </bean> <!-- 定义前置通知对象 --> <bean id="beforeAdvice" class="com.zking.advice.BeforeAdvice"> </bean> <!-- 定义后置通知对象 --> <bean id="afterAdvice" class="com.zking.advice.AfterAdvice"> </bean> <!-- 定义环绕通知对象 --> <bean id="roundAdvice" class="com.zking.advice.RoundAdvice"> </bean> <!-- 定义异常通知对象 --> <bean id="exceptionAdvice" class="com.zking.advice.ExceptionAdvice"> </bean> <!-- 定义适配器=通知对象+切入点 --> <bean id="customAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!-- 定义通知 --> <property name="advice"> <ref bean="afterAdvice"/> </property> <!-- 定义切入点 --> <!-- <property name="pattern"> <value>.*buy</value> </property> --> <!-- 定义多切入点 --> <property name="patterns"> <list> <value>.*buy</value> </list> </property> </bean> <!-- 定义代理对象,代理=目标+通知, 代理对象才具备AOP的特性--> <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref bean="bookBiz"/> </property> <!-- 注入通知 --> <property name="interceptorNames"> <list> <!-- 前置通知 --> <!-- <value>beforeAdvice</value> --> <!-- 后置通知 --> <!-- <value>afterAdvice</value> --> <!-- 环绕通知 --> <!-- <value>roundAdvice</value> --> <!-- 异常通知 --> <value>exceptionAdvice</value> <!-- 适配器--> <value>customAdvisor</value> </list> </property> <!-- 注入接口,面向接口开发,降低耦合度 --> <property name="proxyInterfaces"> <list> <value>com.zking.biz.IBookBiz</value> </list> </property> </bean> </beans>
测试类:
package com.zking.biz; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Demo { @SuppressWarnings("resource") public static void main(String[] args) { //spring的核心:AOP(面向切面编程) //连接点(Joinpoint):程序执行过程中明确的点,比如方法的调用,或者异常的抛出。 //目标(Target):被通知要代理的对象 //通知(Advice):也被称为处理,在某个特定的点上执行的操作,同时也是具体的代码实现,是AOP的具体实现,比如日志记录的代码 //代理(Proxy):将通知应用到目标对象后创建的对象(称为代理对象),代理对象=目标+通知。注意:只有代理对象才具有AOP的特性,而AOP的具体实现则定义在通知里面。 //切入点(Pointcut):多个连接点的集合,定义了通知应用应该连接到哪些连接点。 //适配器(Advisor):适配器=通知(Advice)+ 切入点(Pointcut),实际上就是对切入点的限制。 //初始化spring上下文 ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring-book.xml"); //案例1:前置通知 //获取代理对象(必须要用接口来接收对象) IBookBiz iBookBiz1 = ac.getBean("proxy",IBookBiz.class); iBookBiz1.buy("李四","两本好书",120d); //案例2:后置通知 IBookBiz iBookBiz2 = ac.getBean("proxy",IBookBiz.class); iBookBiz2.buy("王五","三本好书",130d); iBookBiz2.comment("赵六","内容舒适"); //案例3:环绕通知 IBookBiz iBookBiz3 = ac.getBean("proxy",IBookBiz.class); iBookBiz3.buy("林七","四本好书",130d); iBookBiz3.comment("林七","内容不健康"); //案例4:异常通知 IBookBiz iBookBiz4 = ac.getBean("proxy",IBookBiz.class); iBookBiz4.buy("林七","四本好书",0d); //案例5:适配器 IBookBiz iBookBiz5 = ac.getBean("proxy",IBookBiz.class); iBookBiz5.buy("柳八","五本好书",1d); iBookBiz5.comment("柳八","敷衍了事"); } }