IOC
什么是IOC
- 控制反转,把对象创建和对象之间的调用过程交给Spring进行管理
- 使用IOC的目的:为了降低耦合度
IOC底层原理
解析xml文件,获取bean标签的class属性,即全类名,通过反射获取到其Class对象,调用newInstance方法来创建一个对象。
IOC容器
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。
Spring提供IOC容器实现的两种方式(两个接口)
两个接口作用相似,都可以通过加载配置文件,通过对象工厂创建对象。不过两者有一定的区别。
-
BeanFactory
IOC容器的基本实现,是Spring内部使用的接口,不提供开发人员进行使用。
特点:延迟加载,调用的时候再创建对象。
-
ApplicationContext
BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用。 基本上所有场合都能直接用ApplicationContext。在加载配置文件时就会创建全部对象。
Application的三个实现类:
- ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话加载不了
- FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须要有访问权限)
- AnnotationConfigApplicationContext:他是用于读取注解注解配置容器的类
IOC操作Bean管理
什么是Bean管理
Bean管理指的是两个操作:1. Spring创建对象。2. Spring注入属性
在XML中Bean对象的三种创建方式
- 通过调用无参构造器创建。如果类中没有无参构造器,就会创建失败。
- 使用普通工厂中的成员方法创建对象。先注册Bean工厂,然后注册需要的Bean对象,通过factory-bean属性指定工厂,factory-method指定工厂中的方法,就能够创建Bean对象。
- 使用工厂的静态方法创建对象。通过Bean标签的class属性指向工厂方法,factory-method指向静态方法,这样就可以获得一个Bean对象。相比于上面使用成员方法创建对象,可以少注册一个工厂的Bean对象。
Bean管理操作的两种方式
- 基于xml配置文件方式实现
- 基于注解方式实现
基于xml方式
-
基于xml方式创建对象
<!--配置xx对象的创建--> <bean id="xx" class="xx.xx.xx"/>
- 在xml文件中,使用bean标签,标签里添加对应的属性,就可以实现对象创建
- bean标签的常用属性
- id属性:容器中的唯一标识,不允许重复
- class属性:类全路径(包类路径)
- 创建对象时候,默认是执行无参数构造方法完成对象创建
-
基于xml方式注入属性
DI(Dependency Injection,依赖注入): DI是IOC的一种具体实现,就是注入属性,有以下两种方式在xml中注入属性
-
使用set方法注入:使用bean标签的property属性进行注入。
<bean class="com.me.pojo.Dog" id="dog"> <property name="name" value="汪酱"/> <property name="weight" value="20"/> </bean>
-
使用有参构造注入属性
<bean class="com.me.pojo.Dog" id="dog"> <constructor-arg name="name" value="汪酱"/> <constructor-arg name="weight" value="20"/> </bean>
-
基于注解方式
- 基于注解创建对象,在类上加入注解@Component,@Service,@Controller,@Repository,可以自动在xml中创建相应的Bean实例。这四个注解的功能是完全一样的,名字不同可以快速确定当前这个类是什么类型的服务。
/*
这里的value对应的是bean标签中的id属性,class属性会获取当前的类的包类路径。
如果不加value属性,默认是首字母小写的类名
*/
@Service(value ="xxService")
-
基于注解方式实现属性注入
-
@Autowired
根据属性类型进行注入,在属性上添加@Autowired后,可以不添加该属性的setter方法,因为该注解底层是通过反射进行注入了,并不需要走setter方法。
-
@Qualifier
根据属性名称进行注入,可以与@Autowired注解一起使用。
-
@Resource
可以根据类型注入,也可以根据名称注入
-
@Value
用在成员属性上,给属性添加基本类型+String的值
-
Bean的作用域
通过bean标签中的scope管理Bean对象的作用域
- singleton(默认):单例,在解析xml文件时创建,并全局唯一
- prototype:多例(原型),在调用getBean方法时创建对象,每个对象独立,互不干扰。
- request:作用于web应用的请求范围
- session:作用于web应用的会话范围
Bean的生命周期
生命周期:从对象创建到对象销毁的过程
Bean标签中有两个属性用于配置bean对象在生命周期的一些方法。
init-method=“”:配置初始化方法。
destroy-method=“”:配置销毁方法。
- 通过构造器创建Bean实例 bean(无参构造)
- 为bean的属性设置值和对其他bean的引用(调用set方法)
- 调用bean的初始化方法(需要配置init-method)
- bean可以使用了(对象获取到了)
- 当容器关闭时,调用bean的销毁方法(需要配置destroy-method)
单例对象的生命周期:与容器的生命的周期相同,容器创建就创建,容器销毁就销毁。
多例对象的生命周期:在被调用时创建,当无用时销毁。
Bean的自动装配
使用bean标签的autowire属性进行自动注入。
一般autowire常用属性有两个:
-
byType ,根据对象类型进行自动注入,在xml搜索与当前bean中的属性的类型相同的bean,然后进行set注入。
-
byName,根据name或者id进行查找,查找到与属性的属性名相同的bean时进行注入。
全注解开发
在Java类上使用注解@Configuration表明该Java类是Spring的配置类,用于替代xml配置文件,这样一来就可以不写xml文件。
关于在Junit中无法自动装配的问题
如果出现在Junit单元测试中使用@Autowired无法自动装配,完成以下步骤以可。
- 导入spring-test依赖,然后在Test类上加入注解@RunWith(SpringJUnit4ClassRunner.class)
- 加入注解@ContextConfiguration,来标明想要导入这个测试类的Bean。
- 如果使用的是xml文件作为配置,则使用@ContextConfiguration的locations属性,写入所有配置了Bean的xml文件。要注意xml文件中需要写component-scan标签,扫描所有的bean对象。
- 如果使用全注解开发,则使用classes属性添加作为配置的Java类。要注意使用@ComponentScan注解扫描Bean对象所在的位置。
AOP
什么是AOP
面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
通俗描述:不通过修改源代码的方式,在主干功能中添加一个新功能。
底层原理
AOP底层使用动态代理,有两种情况的动态代理
- 有接口情况,使用JDK动态代理,通过创建一个接口实现类的代理对象并加入额外的功能,最终完成功能的添加。
- 没有接口情况,使用CGLIB动态代理,通过继承创建一个子类的代理对象,加入额外的功能,最终完成功能的添加。
JDK动态代理
使用Proxy类(在java.lang.reflect中)里面的方法创建对象。通过调用里面的newProxyInstance方法返回指定接口的代理类的实例。方法有三个参数。
- 第一个参数是类加载器。可以通过调用对象的getClass方法获得Class对象,然后调用getClassLoader获得类加载器。
- 第二个参数是该类实现的接口。与上述步骤差不多,获得Class对象后,调用getInterfaces获取该类实现的接口。
- 第三个参数是InvocationHandler(接口),需要自己写一个其实现类,在里面完成功能的增强。
步骤
-
创建接口,定义方法
public interface CalculateDao { int add(int a, int b); CalculateDao update(String id); }
-
创建接口实现类,实现方法
public class CalculateDaoImpl implements CalculateDao { @Override public int add(int a, int b) { System.out.println("add方法执行了"); return a+b; } @Override public CalculateDao update(String id) { System.out.println("update方法执行了"); System.out.println(id); return this; } }
-
使用Proxy类创建接口代理对象前需要实现InvocationHandler
class ProxyFactory implements InvocationHandler { private Object obj; /** * @param obj Object包容所有传进来对象 */ public ProxyFactory(Object obj) { this.obj = obj; } /** * @param proxy 代理对象信息 * @param method 被调用的方法 * @param args 方法传入的参数 * @return 返回值 * @throws Throwable 抛异常 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("update".equals(method.getName())) { method.invoke(obj, args); return proxy; } System.out.println("方法执行前....."+method.getName()+":传递的参数"+ Arrays.toString(args)); Object result = method.invoke(obj, args); System.out.println("方法执行后....."+obj); return result; } }
-
调用Proxy的newInstance方法创建代理对象
CalculateDao cal = (CalculateDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), CalculateDaoImpl.class.getInterfaces(), new ProxyFactory(new CalculateDaoImpl())); System.out.println("结果为:"+cal.add(1, 5)); /* 控制台输出: 方法执行前.....add:传递的参数[1, 5] add方法执行了 方法执行后..... 结果为:6 */
关于Invoke方法中的第一个参数proxy
用法:可以在特定条件下让方法返回proxy,达到对该代理对象的连续调用。
注意事项:如果要返回proxy并连续调用,那么被调用的方法的返回值一定要是接口类型
//在Main方法中
CalculateDao cal = (CalculateDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), CalculateDaoImpl.class.getInterfaces(), new ProxyFactory(new CalculateDaoImpl()));
cal.update("13").update("12");
/*
结果为:
update方法执行了
13
update方法执行了
12
*/
AOP术语
-
连接点
类里面可以被增强的方法,这些方法被称为连接点。
-
切入点
实际被增强的方法被成为切入点。
-
通知(增强)
-
实际增强的逻辑部分成为通知(增强)
-
通知有多种类型
-
前置通知( @Before )
在被增强的方法执行前会执行
-
后置通知( @AfterReturning )
在被增强的方法执行后会执行,被增强方法出现异常后将不会执行
-
环绕通知( @Around )
在被增强的方法执行的前后都会执行,环绕通知的写法比较特殊。环绕通知需要有返回值,并且与被增强方法的返回值一致。 如下所示。环绕通知可以做到其他4个通知的功能。
@Around(value = "execution(* com.me.spring.aopwithannotation.User.add(..))") public Object around(ProceedingJoinPoint proceedingJoinPoint){ Object result = null; try { System.out.println("环绕前置"); //proceed方法用于执行被增强的方法 result= proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs()); System.out.println("环绕后置"); } catch (Throwable throwable) { throwable.printStackTrace(); } return result;//其他通知获得的返回值就是环绕通知的返回值 }
-
异常通知( @afterThrowing)
在被增强的方法出现异常时会执行
-
最终通知(@After)
类似于java代码块的finally,无论是否出现异常,都会执行
-
-
-
切面
切面是通知和切入点的结合
-
织入
把切面应用到目标对象来创建新的代理对象的过程
AOP操作(准备)
Spring框架一般都是基于AspectJ实现AOP操作
什么是AspectJ
AspectJ不是Spring的组成部分,是一个独立的AOP框架。一般把AspectJ和Spring框架一起使用,进行AOP操作。
切入点表达式
execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))
基于AspectJ实现AOP操作
基于注解方式实现(一般使用)
相同切入点的抽取,如下所示
//抽取了公共点以后,接下来的切入只需要在value值中写入pointCur()即可
@Pointcut(value = "execution(* com.me.spring.aopwithannotation.User.add(..))")
public void pointCut(){ }
多个代理类同时对一个方法进行增强:
//每个代理类上都加上Order注解,里面的x为数字,代表优先级。数字越小,优先级越高
@Order(x)
-
创建被增强类User
@Component public class User { public int add(int a, int b) { return a + b; } }
-
创建增强类UserProxy
@Component @Aspect public class UserProxy { @Pointcut(value = "execution(* com.me.spring.aopwithannotation.User.add(int,int))") public void pointCut() { } @Before(value = "pointCut()") public void logStart(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); System.out.println("[" + signature.getName() + "]开始执行,参数为" + Arrays.asList(joinPoint.getArgs())); } @After(value = "pointCut()") public void logEnd(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); System.out.println("[" + signature.getName() + "]方法执行完成"); } @AfterThrowing(value = "pointCut()", throwing = "exception") public void afterThrowing(JoinPoint joinPoint, Exception exception) { Signature signature = joinPoint.getSignature(); System.out.println("[" + signature.getName() + "] 出现异常,异常信息为" + exception); } @AfterReturning(value = "pointCut()", returning = "result") public void logReturn(JoinPoint joinPoint, Object result) { Signature signature = joinPoint.getSignature(); System.out.println("[" + signature.getName() + "]方法正常执行,结果为" + result); } }
-
配置xml文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.me.spring.aopwithannotation"/> <aop:aspectj-autoproxy/> </beans>
-
测试结果
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml"); User user = context.getBean("user", User.class); user.add(1, 5); /* 运行结果: [add]开始执行,参数为[1, 5] [add]方法正常执行,结果为6 [add]方法执行完成 */
基于xml配置文件实现
<!--xml文件配置-->
<!--注册被增强类与增强类-->
<bean id="user" class="com.me.spring.aopwithannotation.User"/>
<bean id="userProxy" class="com.me.spring.aopwithannotation.UserProxy"/>
<aop:config>
<!--选择切入点-->
<aop:pointcut id="addPoint" expression="execution(* com.me.spring.aopwithannotation.User.add(..))"/>
<!--配置切面-->
<aop:aspect ref="userProxy">
<!--配置通知-->
<aop:before method="logStart" pointcut-ref="addPoint"/>
<aop:after-returning method="logReturn" pointcut-ref="addPoint" returning="result"/>
<aop:after method="logEnd" pointcut-ref="addPoint"/>
</aop:aspect>
</aop:config>