Spring框架是基于软件开发的复杂性创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度上而言,绝大部分Java应用都可以从Spring中受益。Spring的目的是解决企业应用开发的复杂性,功能为使用基本的JavaBean代替EJB,并提供了更多的企业应用功能;Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架。
一、Spring框架简介
我们日常所说的Spring,实际上指的是Spring Framework,属于Spring家族的一个分支。Spring是一个开源框架,框架的主要优势之一就是分层架构,分层架构允许使用者选择使用哪一个组件,同时为J2EE应用程序开发提供集成的框架。Spring的核心是一个容器,常称为Spring应用程序上下文,用于创建和管理应用程序组件。组件在spring应用程序上下文中连接在一起构成一个完整的应用程序。Spring通过依赖注入将各种Bean连接在一起。Spring的核心是控制反转(IOC)和面向切面(AOP),简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式)轻量级开源框架。
Spring的优点
- 方便解耦,简化开发(高内聚低耦合),Spring就是一个大工厂,可以将所有对象创建和依赖关系维护,交给Spring管理,spring工厂是用于生成Bean
- AOP编程的支持,Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
- 声明式事务的支持,只需要通过配置就可以完成对事务的管理,而无需手动编程
- 方便程序的测试,Spring对Junit4支持,可以通过注解的方式方便的测试Spring程序
- 方便集成各种优秀框架,Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(Struts、Hibernate、Mybatis、Quartz)的支持
- 降低JavaEE API的使用难度,Spring对JavaEE开发中一些非常难用的API(JDBC、JavaMail、远程调用等),都提供了疯狂,使这些API使用难度都大大降低
Spring的七个模块
Spring Core:提供 Spring 框架基本功能,主要组件是 BeanFactory,是工厂模式的实现,通过 IOC 机制将应用程序的配置和依赖性规范与实际的应用程序代码分开。
Spring Context:一个配置文件,给 Spring 框架提供上下文信息,上下文包括 JNDI、EJB、电子邮件、国际化、校验和调度等企业服务。
Spring AOP :通过配置管理特性,Spring AOP 直接将 AOP(面向切面)功能集成到 Spring 框架。从而我们能够十分方便的使用 Spring 框架来管理任何支持 AOP 的对象。模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用该组件,可以不依赖其他组件九江声明性事务管理集成到应用程序中。
Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可以用来管理异常处理和不同数据库供应商抛出的错误信息。异常层次结构简化了错误处理,而且极大降低了需要编写的异常代码数量。Spring DAO 面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
Spring ORM:Spring 框架中插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map,这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
Spring Web:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文,所以 Spring 框架支持与 Jakarta Structs 的集成。同时该模块还简化了处理多部分请求以及请求参数绑定到域对象的工作。
Spring Web MVC:MVC 是一个全功能的构建 Web 应用的 MVC 实现,可以通过策略接口对 MVC 框架实现高度可配置。而且 MVC 还容纳了 JSP、Velocity、Tiles 等视图技术。
二、Spring核心介绍
控制反转IOC
控制反转,就是将对象创建的权利交给Spring,如果我们需要new一个对象,让Spring帮我们创建好,然后我们进行使用就可以了。假设一个公司有开发、测试、BI等职位,如果需要招聘人员,根据公司的职位要求,进行筛选,这就是正向流程;如果反过来,不用公司自己进行筛选,而是根据第三方公司根据要求进行筛选,然后推荐给公司,这就是控制反转。
在Spring中,对象的属性是由对象自己创建的,这就是正向流程;如果属性不是由对象创建,而是由Spring来自动进行装配,就是控制反转;这里的DI依赖注入,就是实现控制反转的方式。正向流程导致了对象与对象之间高耦合,IOC可以解决对象耦合的问题,有利于功能的复用,能够使程序的结构变得更加灵活。
依赖注入DI
指Spring创建对象的过程中,将对象依赖的属性(简单之、集合、对象)通过配置的方式进行设值。IOC和DI其实是同一个概念不同角度的描述,DI相对IOC而言,明确的描述了被管理的对象中,依赖的属性也应该由Spring容器进行自动注入。
面向切面AOP
AOP就是把多个方法前后的共同代码抽离出来,使用动态代理机制来控制。好处是降低耦合度,减少重复代码,提高代码的可扩展性和可维护性。假设,一般程序执行流程是controller层调用service层,然后service层调用DAO层访问数据,最后在逐层返回结果。但是,在一个程序中会有多个不同的服务,例如用户信息、商品信息、订单服务等,每个服务的controol层都需要验证参数,都需要处理异常生成日志等,我们可以对每个服务进行横切,然后在每个切面上完成通用的功能,例如验证参数、生成日志、处理异常等,这样就不用再每个服务中写相同的逻辑,这就是AOP思想解决的问题。
三、Spring入门案例
使用IDEA新建一个Spring项目,spring会创建好这个spring项目架构,新建一个测试Person类
public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = 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 "IOC.Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
配置Spring 核心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" 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.xsd"> <bean id="Person" class="IOC.Person"> <constructor-arg> <value>旺财</value> </constructor-arg> <constructor-arg> <value>20</value> </constructor-arg> </bean> </beans>
在程序中通过读取spring的配置文件来获取bean
public class IocTest { public static void main(String[] args) { //查询类路径 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml"); //根据id获取bean //Spring就是一个大工厂(容器)专门生成bean bean就是对象 Person person = (Person) applicationContext.getBean("Person"); //输出获取到的对象 System.out.println("person = " + person); } }
Spring创建对象A的时候,会将对象A所依赖的对象B也创建出来,并自动注入到对象A中
public class UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void addUser(){ userDao.add(); } }
public class UserDao { public void add(){ System.out.println("userDao add"); } }
<bean id="userDaoId" class="DI.UserDao"/> <bean id="userServiceId" class="DI.UserService"> <property name="userDao" ref="userDaoId"></property> </bean>
先创建UserService对象,然后根据在property中的ref找到userDaoId并创建UserDao对象,然后根据property中的name,通过setter方法注入,这样就完成了依赖注入
public class DiTest { public static void main(String[] args) { //查询类路径 加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml"); //这样写不用强转 UserService us = applicationContext.getBean("userServiceId", UserService.class); //依赖注入 us.addUser(); } }
使用动态代理来增强某个方法
public interface UserService { public void addUser(); public void updateUser(); public void deleteUser(); }
public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println("添加用户"); } @Override public void updateUser() { System.out.println("修改用户信息"); } @Override public void deleteUser() { System.out.println("删除用户"); } }
public class MyAspect { public void before(){ System.out.println("之前"); } public void after(){ System.out.println("以后"); } }
public class MyBeanFactory { public static UserService createService(){ //目标类 final UserService userService = new UserServiceImpl(); //切面类 final MyAspect myAspect = new MyAspect(); /* 代理类:将目标类(切入点)和切面类(通知)结合-->切面 * Proxy.newProxyInstance * 参数1:loader ,类加载器,动态代理类 运行时创建,任何类都需要类加载器将其加载到内存。 * 一般情况:当前类.class.getClassLoader(); * 目标类实例.getClass().get... * 参数2:Class[] interfaces 代理类需要实现的所有接口 * 方式1:目标类实例.getClass().getInterfaces() ;注意:只能获得自己接口,不能获得父元素接口 * 方式2:new Class[]{UserService.class} * 例如:jdbc 驱动 --> DriverManager 获得接口 Connection * 参数3:InvocationHandler 处理类,接口,必须进行实现类,一般采用匿名内部 * 提供 invoke 方法,代理类的每一个方法执行时,都将调用一次invoke * 参数31:Object proxy :代理对象 * 参数32:Method method : 代理对象当前执行的方法的描述对象(反射) * 执行方法名:method.getName() * 执行方法:method.invoke(对象,实际参数) * 参数33:Object[] args :方法实际参数 */ UserService proxService =(UserService) Proxy.newProxyInstance(MyBeanFactory.class.getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //前执行 myAspect.before(); //执行目标类的方法 Object obj = method.invoke(userService, args); //后执行 myAspect.after(); return obj; } }); return proxService; } }
public class Test { public static void main(String[] args) { UserService userService = MyBeanFactory.createService(); userService.addUser(); userService.updateUser(); userService.deleteUser(); } }
成功的增强了UserServiceImpl对象的其中的方法。如果别的类有需要被增强的方法,那么同样通过创建工厂代理就可以拿到对象的代理对象。
//没有接口,只有实现类。采用字节码增强框架 cglib,在运行时 创建目标类的子类,从而对目标类进行增强。 public class MyCglibBeanFactory { public static UserServiceImpl createService(){ //1 目标类 final UserServiceImpl userService = new UserServiceImpl(); //2切面类 final MyAspect myAspect = new MyAspect(); // 3.代理类 ,采用cglib,底层创建目标类的子类 //3.1 核心类 Enhancer enhancer = new Enhancer(); //3.2 确定父类 enhancer.setSuperclass(userService.getClass()); /* 3.3 设置回调函数 , MethodInterceptor接口 等效 jdk InvocationHandler接口 * intercept() 等效 jdk invoke() * 参数1、参数2、参数3:以invoke一样 * 参数4:methodProxy 方法的代理 * * */ enhancer.setCallback(new MethodInterceptor(){ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //前 myAspect.before(); //执行目标类的方法 Object obj = method.invoke(userService, args); // * 执行代理类的父类 ,执行目标类 (目标类和代理类 父子关系) methodProxy.invokeSuper(proxy, args); //后 myAspect.after(); return obj; } }); //3.4 创建代理 UserServiceImpl proxService = (UserServiceImpl) enhancer.create(); return proxService; } }
public class CglibTest { public static void main(String[] args) { UserService userService = MyCglibBeanFactory.createService(); userService.addUser(); userService.updateUser(); userService.deleteUser(); } }
spring中使用AOP的相关术语
- Target:目标类,需要被增强的类,也就是上面写道的UserServiceImpl
- JointPoint:连接点,目标类上需要被增强的方法,
- PointCut:切入点,被增强的方法,切入点就是一个连接点的子集
- Advice:增强/通知,增强的代码,也就是上面将增强的代码凑成一个类。类中的每个方法都代表一个增强的功能代码,这个类中的方法被称作为通知
- weaving:织入,将切入点和通知结合,从没被增强到已经增强的过程
- Aspect:切面,切入点和通知结合,切入点和通知点多点形成面,
半自动代理
/** * 切面类中确定通知,需要实现不同接口,接口就是规范,从而就确定方法名称。 * * 采用“环绕通知” MethodInterceptor * */ public class MyAspect implements MethodInterceptor { @Override public Object invoke(MethodInvocation mi) throws Throwable { System.out.println("前3"); //手动执行目标方法 Object obj = mi.proceed(); System.out.println("后3"); return obj; } }
<!-- 1 创建目标类 --> <bean id="BanAOPUserService" class="BanAOP.UserServiceImpl"></bean> <!-- 2 创建切面类 --> <bean id="myAspectId" class="BanAOP.MyAspect"></bean> <!-- 3 创建代理类 * 使用工厂bean FactoryBean ,底层调用 getObject() 返回特殊bean * ProxyFactoryBean 用于创建代理工厂bean,生成特殊代理对象 interfaces : 确定接口们 通过<array>可以设置多个值 只有一个值时,value="" target : 确定目标类 interceptorNames : 通知 切面类的名称,类型String[],如果设置一个值 value="" optimize :强制使用cglib <property name="optimize" value="true"></property> 底层机制 如果目标类有接口,采用jdk动态代理 如果没有接口,采用cglib 字节码增强 如果声明 optimize = true ,无论是否有接口,都采用cglib --> <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 接口 :如果只是一个接口,就写Value,如果是多个接口就写List--> <property name="interfaces" value="BanAOP.UserService"/> <!-- 确定接口,多个接口使用array,单个用value --> <!-- 目标对象 --> <property name="target" ref="BanAOPUserService"/> <!-- 切面类--> <property name="interceptorNames" value="myAspectId"></property> <!-- 配置使用cglib生成--> <property name="optimize" value="true"></property> </bean>
public class Test { public static void main(String[] args) { //获取Spring容器中代理对象 ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml"); UserService userService = (UserService) context.getBean("serviceProxy"); userService.deleteUser(); } }
Spring中各种通知类型
- 前置通知(Before Advice): 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用 @Before注解使用这个Advice。
- 返回之后通知(After Retuning Advice): 在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过 @AfterReturning`关注使用它。
- 抛出(异常)后执行通知(After Throwing Advice): 如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通用 @AfterThrowing`注解来使用。
- 后置通知(After Advice): 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过@After注解使用。
- 围绕通知(Around Advice): 围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过 @Around注解使用。