在spring中经常被提及的概念,一个是控制反转IOC,一个是依赖注入DI。还有一个面向切面编程AOP。
浅谈控制反转(IOC):
在任何一个有请求作用的系统当中,至少需要有两个类互相配合工作,在一个入口类下使用new关键字创建另一个类的对象实例,这就好比在面向对象编程的思想下,“我“充当一个入口类,在这个入口类中,我每次吃饭的时候都要买一双一次性筷子(每一次使用都要new一次),在这样的关系下,是”我“(即调用者)每次都要”主动“去买一次性筷子(另一个类),我对筷子说你老老实实的过来我的手上,是我控制了筷子,那好,在这种控制正转的关系下,放在现实生活当中,肯定是不现实的,他总会去创造出更加方便自己生活的想法,更确切的做法是,买一双普通的筷子(非一次性),把他放在一个容器当中(在Spring中叫做IOC容器),你需要使用的时候就对容器说:IOC我想要用筷子(向容器发出请求),接着筷子就会”注入“到的手上,而在这个过程当中,你不再是控制方,反而演变成一名请求者(虽然本身还是调用者),依赖于容器给予你资源,控制权坐落到了容器身上,于是这就是人们俗称的控制反转。
依赖注入(DI):在控制反转中获取资源的过程叫做依赖注入【由容器动态的将某个依赖关系注入到组件之中】,那么这里代码实现也是专注于依赖注入。
依赖注入有3种方式:构造函数方法注入,setter方法注入,接口注入(侵入性,淘汰)。
IOC: 体现了工厂模式,通过BeanFactory注入实例,将对象交给容器管理,在配置文件定义相应的bean,以及设置相关的属性,让spring容器来生成实例对象以 及管理对象。
说白了其实就是由我们平常的new转成了使用反射来获取类的实例,相信任何人只要会用java的反射机制,比如:
public ObjectgetInstance(String className) throws Exceptio {
Object obj = Class.forName(className).newInstance(); //获取传入类的实例
Method[] methods = obj.getClass().getMethods(); //获取传入类的所有方法
for (Method method : methods) {
if (method.getName().intern() == "setString") {
method.invoke(obj, "hello world!");
}
}
}
2.关于Spring AOP :
AOP:面向切面编程,在不修改源代码的情况下(通过预编译方式和动态代理实现)给程序动态统一添加功能。比如说加日志,权限。
jdk动态代理:用拦截器(拦截器必须实现InvocationHanlder接口)加上反射机制生成一个实现代理接口的匿名类,在调用方法前调用 InvokeHandler来处理。使用Proxy.newProxyInstance产生代理对象。
cglib动态代理:必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法。是一种继承。针对接口编程的环境下推荐使用JDK的代理。
静态代理模式:是由程序员创建或特定工具自动生成源代码,在对其进行编译。在程序运行之前,代理类的.class文件就已经存在。
注意: 如果目标对象有实现接口,使用jdk代理。如果目标对象没有实现接口,则使用cglib代理。
比如:我们现在要开发的一个应用里面有很多的业务方法,但是,我们现在要对这个方法的执行做全面监控,就会在要一些方法前去加上一条日志记录。
springboot项目demo:
applicationContext.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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" 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/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!-- 配置扫描的包 --> <context:component-scan base-package="com.road.unifiedpay.test2"/> <!-- 切面的声明 --> <bean id="transaction" class="com.road.unifiedpay.test2.Transaction"/> <!--aop配置 --> <aop:config> <!-- 切点, 配置aop的切入点id; 是切入点的标识 ;expression 为切入点的表达式 --> <aop:pointcut expression="execution(* com.road.unifiedpay.test2..*(..))" id="perform"/> <!-- 切面,配置切面(切面里面配置通知)—— ref 指向声明切面的类 --> <aop:aspect ref="transaction"> <!-- 前置通知pointcut-ref 引用一个切入点 --> <aop:before method="beginTransaction" pointcut-ref="perform"/> <!-- 后置通知 --> <!-- <aop:after-returning method="commit" pointcut-ref="perform" returning="val"/> --> </aop:aspect> </aop:config> </beans>
package com.road.unifiedpay.test2; public class Person { private Long pid; private String pname; public Long getPid() { return pid; } public void setPid(Long pid) { this.pid = pid; } public String getPname() { return pname; } public void setPname(String pname) { this.pname = pname; } }
package com.road.unifiedpay.test2; import java.util.List; /** * 目标对象和代理对象都实现的接口 */ public interface PersonDao { void deletePerson(); List<Person> getPerson() throws Exception; void savePerson(); void updatePerson(); }
package com.road.unifiedpay.test2; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Service; /** * 目标对象:实现目标接口 */ @Service("personDao") public class PersonDaoImpl implements PersonDao{ @Override public void deletePerson() { System.out.println("delete perosn"); } @Override public List<Person> getPerson() throws Exception { List<Person> personList = new ArrayList<Person>(); Person person1 = new Person(); person1.setPid(1L); person1.setPname("person1"); System.out.println("get person"); personList.add(person1); return personList; } @Override public void savePerson() { System.out.println("save perosn"); } @Override public void updatePerson() { System.out.println("update perosn"); } }
package com.road.unifiedpay.test2; import java.util.ArrayList; import java.util.List; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; /** * 切面(spring aop 就不需要拦截器啦) * (模拟hibernate里面保存数据要打开事物,然后各种增删改之后,再提交事物。) */ public class Transaction { //前置通知 public void beginTransaction() { System.out.println("begin Transaction");//打开事物 } /** * @param joinPoint 通过joinPoint可以得到目标类和目标方法的一些信息 * @param val 目标方法的返回值 * 和<aop:after-returning returning="val"/>中returning的值保质一致 */ //后置通知 public void commit(JoinPoint joinPoint, Object val) { String methodName = joinPoint.getSignature().getName(); System.out.println("后置通知:"+methodName); //提交事物 System.out.println("commit"); List<Person> personList = (ArrayList<Person>) val; for (Person person : personList) { System.out.println(person.getPname()); } } public void finalMethod() { System.out.println("最终通知"); } public void aroundMethod(ProceedingJoinPoint joinPoint) {//环绕通知 try { System.out.println("around method"); joinPoint.proceed();//调用目标类的目标方法 } catch (Throwable e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 异常通知 */ public void throwingMethod(Throwable except) { System.out.println(except.getMessage()); } }
测试:
package com.road.unifiedpay.test2; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestAop { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); PersonDao personDao=(PersonDao) context.getBean("personDao"); try { personDao.savePerson(); } catch (Exception e) { e.printStackTrace(); } } }
结果:
说明:在applicationContext.xml中申明了切面,切点,通知类型,切点配置了调用personDaoImpl实现类的所有方法只要执行就会先打印前置通知“begin Transcation” 。
总结一句话就是:AOP 在不修改源代码的情况下给程序动态统一添加功能。 这样就能够在一个项目及时要在中途需要这么一个功能,那也就只需修改配置文件和加一个类,而没有该已经写好的类的代码。aop明显增加了代码的复用性,也省去了重新测试。
上面的三条红色的竖向框就是经常说的切面,在这个切面里面有很多的方法,可以把a()看做上面的说道的前置通知,b()看做后置通知,c()看做最终通知等等。总而言之,这些方法都不需要我们去写的,而是aop自动帮我们做好的。我们只要触动了我们的比如“保存方法”就会执行切面里的一系列方法。这样就省去了很多开发时间,也精简了代码。