Spring介绍
Spring是一个企业级引用框架,其中包含了大量的各种应用软件。Spring框架为现代基于Java的企业应用程序提供了一个全面的编程和配置环境,能够在任何类型的部署平台上进行部署,其中核心是IoC和AOP。
Spring中的两个核心概念
IoC
控制反转(Inversion of control),将原来由我们完成的实例化过程,交给容器来完成。将组建对象的控制权从代码本身转移到外部容器中。
组件化的思想:分离关注点,使用接口,不再关注实现。
- DI:依赖注入。依赖于某一种媒介完成对某一个对象的初始化或者是赋值。
AOP
面向切面编程
Spring优点
- 低侵入式的设计
- 独立于各种应用服务器
- 依赖注入将组件关系透明化,降低了耦合度
- 面向切面编程特性允许将通用任务进行集中式处理
- 与第三方框架的良好整合
Spring工程构建
Maven Spring依赖注入
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.6.RELEASE</version> </dependency>
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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
完成控制反转和依赖注入
<bean id="userInfo" class="com.domain.UserInfo"> <!--完成依赖注入 DI--> <property name="username" value="张三" /> <property name="content" value="刘家豪大笨蛋!" /> </bean>
- bean:
- id:该bean对象名
- class:指向对应的类路径
- property:
- name:对象内的某个属性名
- value:属性值(基本数据类型使用value)
- ref:属性值(引用数据类型使用ref引用对象)
注意:在同一个配置文件下,bean中的id不能重复。
测试:
@Test public void test(){ // 获取IoC容器的对象 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); // 使用容器对象的getBean("id")来获取对应的bean对象 UserInfo userInfo = (UserInfo)ac.getBean("userInfo") ; // 使用bean对象进行操作 userInfo.print(); }
Spring中的IoC产生的对象是否是单例模式
@Test public void test1(){ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationConetxt.xml"); // 第一次向容器中获取userInfo的bean对象 UserInfo userInfo = (UserInfo)ac.getBean("userInfo"); userInfo.print(); System.out.println(userInfo); // 第二次向容器中获取userInfo的bean对象 UserInfo userInfo2 = (UserInfo)ac.getBean("userInfo"); userInfo2.print(); System.out.println(userInfo2); }
观察结果可以发现,userInfo和userInfo2是同一个对象,那么说明在调用同一个bean时,获取回来的对象都为同一对象。所以在Spring的设计中,所使用的是单例模式。每调用一个新的bean,都会创建一个新的对象。
虽然都是对着同一个类进行实例化,但是每一个bean代表着一个对象。
应用XML编程实现AOP
面向切面编程(Aspect Oriented Programming)。其目的是为了拦截某些类下的某些方法、参数、返回值内容。在此基础上,通过增强形式,使用代理模式显示AOP编程。
AOP的代理实现形式
通过JDK动态代理实现
JDK动态代理模式的实现,是要求必须拥有接口。Spring能够对任何一个对象都进行代理设计,此时如果某个对象没有对应的接口,那么JDK动态代理是无法创建对应的代理对象,这时Spring AOP就会使用CGLIB生成。
通过CGLIB动态代理实现
CGLIB是通过目标类实现的一种代理方式,因此不需要提前准备对应的接口。在Spring生成代理对象时,实际上是生成代理目标的一个子类。
public class SampleClass { public void test(){ System.out.println("hello world"); } public static void main(String[] args) { Enhancer enhancer = new Enhancer(); //告诉代理类,他的父类是谁 enhancer.setSuperclass(SampleClass.class); //通过setCallback方法,拦截对应的调用方法进行增强 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before method run..."); //返回值 Object result = proxy.invokeSuper(obj, args); System.out.println("after method run..."); return result; } }); SampleClass sample = (SampleClass) enhancer.create(); sample.test(); } }
通过编程形式基于XML实现AOP
- 引入aspectweaving依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version> </dependency>
- 修改配置文件
<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">
- 编写增强通知类
public class LoggerService{ /** * 前置通知 * @param joinPoint 连接点 */ public void before(JoinPoint joinPoint){ /** * joinPoint.getTarget():获取到目标的类 * joinPoint.getSignature().getName():获取到方法 * joinPoint.getArgs():获取到参数 */ System.out.println(joinPoint.getTarget()); } /** * 后置通知 * @param joinPoint 连接点 * @param result 返回值结果 */ public void afterReturning(JoinPoint joinPoint, Object result) { System.out.println("后置通知:" + joinPoint.getTarget() + "的" + joinPoint.getSignature().getName() + ",返回值是:" + result); } }
- 在核心文件中增加aop的配置
<!--增强bean设置--> <bean id=""loggerService" class="com.csi.aop.LoggerService"/> <!--目标对象--> <bean id="XXX" class="pointcut切入点实现类"/> <!--aop配置--> <aop:config> <!--接入点--> <aop:pointcut id="pointcut" expression="execution(* com.csi.service..*.*(..))"/> <!--切面 织入过程--> <aop:aspect ref="loggerService"> <!--前置增强--> <aop:before method="before" pointcut-ref="pointcut"/> <!--后置增强--> <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/> </aop:aspect> </aop:config>
- 测试
ApplicationContext ctx = new ClasspathXMLApplicationContext("applicationXXX.xml") ; Object obj = ctx.getBean("id") ; obj.xxx() ;
- 结果
前置通知:com.csi.service.impl.PersonServiceImpl@1dde4cb2的list,参数是[] 调用了list方法
使用场景
- 日志
- 会降低日志输出灵活度,没有办法进行个性化的输出
- 权限
- 如果将权限控制下沉到service层,那么基恩上没有太大意义
- 事务管理
- 由于事务管理是一成不变的,所以是最适合的场景
IoC和AOP使用扩展
IoC构造注入
使用内部bean
<bean id="userService" class="com.csi.service.impl.UserServiceImpl"> <!--引用数据类型,只能够通过ref或者是内部bean的方式进行赋值--> <constructor-arg name="userDao"> <bean class="com.csi.dao.impl.UserDaoImpl" /> </constructor-arg> </bean>
使用ref引用形式
<bean id="userDao" class="com.csi.dao.impl.UserDaoImpl"/> <bean id="userService" class="com.csi.service.impl.UserServiceImpl"> <!--引用数据类型,只能够通过ref或者是内部bean的方式进行赋值--> <constructor-arg name="userDao" ref="userDao" /> </bean>
通过构造中的参数索引顺序赋值
<bean id="userService" class="com.csi.service.impl.UserServiceImpl"> <constructor-arg index="0" ref="userDao" /> <constructor-arg index="1" value="50" /> </bean>
通过type类型注入
<bean id="userService" class="com.csi.service.impl.UserServiceImpl"> <!--引用数据类型,只能够通过ref或者是内部bean的方式进行赋值--> <!--<constructor-arg name="userDao" ref="userDao" />--> <!--<constructor-arg index="0" ref="userDao" /> <constructor-arg index="1" value="50" />--> <constructor-arg type="com.csi.dao.UserDao" ref="userDao" /> <constructor-arg type="int" value="20" /> </bean>
日期格式注入
<bean id="simpleDateFormat" class="java.text.SimpleDateFormat"/> <bean id="userInfo" class="com.csi.doamin.UserInfo"> <constructor-arg name="content" value="hello word"> <constructor-arg name="borndate"> <bean factory-bean="simpleDateFormat" factory-method="parse"> <constructor-arg value="2020-10-24"> </bean> </constructor-arg> </bean>
设值注入(setter)和构造注入的区别
集合、数组、map、Properties类型注入
<bean id="userInfo" class="com.csi.domain.UserInfo"> <!--属性Properties注入--> <property name="properties"> <props> <prop key="username">root</prop> <prop key="password">123456</prop> </props> </property> <!--map注入--> <property name="map"> <map> <entry key="key1" value="val1"/> <entry key="key2" value="val2"> </map> </property> <!--list、array、set--> <property name="list"> <array> <value>张三</value> <value>李四</value> </array> </property> </bean>
AOP扩展使用
异常通知
在出现异常时,Spring AOP会自动捕获异常,并使用代理对象处理
/** * 异常通知 */ public void afterThrowing(JoinPoint joinPoint,RuntimeException e) { System.out.println(joinPoint.getTarget() + "的" + joinPoint.getSignature().getName() + ",报出的异常信息:" + e); }
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e" />
最终通知
Spring AOP的最终通知是通过finally实现的,无论发生任何情况,都会执行。
/** * 最终通知:无论什么情况,都会执行 */ public void after(JoinPoint joinPoint) { System.out.println("最终通知:" + joinPoint.getTarget() + "的" + joinPoint.getSignature().getName() + ",参数是" + Arrays.toString(joinPoint.getArgs())); }
<aop:after method="after" pointcut-ref="pointcut" />
环绕通知
环绕通知功能十分强大,包含了四种全部通知类型,主要是通过暴露的原生方法调用所执行的目标方法(类似于JDK动态代理的invoke方法),因此可以获取到原生方法的所有状态加以处理。
/** * 环绕通知:在执行方法的前、后以及异常信息及finally中都可以灵活处理 */ public void afterAround(ProceedingJoinPoint pjp) { System.out.println("开启事务..."); try { //调用代理目标的方法 Object result = pjp.proceed() ; System.out.println("事务提交"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("事务回滚"); } finally { System.out.println("释放SqlSession对象"); } }
<aop:around method="afterAround" pointcut-ref="pointcut" />