目录
概述
Java EE开发最常用的框架是Spring,该框架开始于2003年,它是由罗德·约翰逊(Rod Johnson)创建的一个轻量级开源框架。Spring框架是为了解决企业应用开发的复杂性而创建的,它的出现使得开发者无须开发重量级的Enterprise JavaBean(EJB),而是通过控制反转(IOC)和面向切面编程(AOP)的思想进行更轻松的企业应用开发,取代了EJB臃肿、低效的开发模式。
一、Spring框架
Spring框架是一款开源的、轻量级的、基础架构型的开发框架,核心思想是控制反转(IOC)和面向切面编程(AOP),为Java应用程序开发提供组件管理服,用于组件之间的解耦,以及简化第三方Java EE中间件技术(JMS、任务调度、缓存、ORM框架)的使用。
Spring框架包括:IOC容器、Validation数据校验、AOP面向切面编程、Transactions事务管理、Spring JDBC、Spring MVC框架、以及各类第三方Java EE中间件技术集成。
Spring框架主要由Core(核心模块)、Testing(测试模块)、Data Access(数据访问模块)、Web Servlet(基于Servlet的Web应用开发)、Integration(企业级系统集成模块)等模块组成。
二、控制反转(IOC)
1、控制反转(IOC)
控制反转(IOC)是Spring框架的核心思想之一,主要用于解耦。IOC是指将创建对象的权利交给Spring进行管理。由Spring框架根据配置文件或注解等方式,创建bean对象并管理各个bean对象之间的依赖关系。是对象之间形成松散耦合的关系,实现解耦。
控制:指创建对象的权力;
反转:控制权交给Spring框架。
Student类:
public class Student {
}
Test类:
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Student stu = (Student) applicationContext.getBean("student");
System.out.println(stu);
}
}
bean对象:
<bean id="student" class="com.dytx.info.Student"></bean>
执行结果:
由上述代码的执行结果我们可以切实的体会到Spring框架核心思想之一的控制反转(IOC)。我们不需要通过new关键字去创建对象,而是由Spring框架帮我们创建对象。
2、IOC容器
IOC容器使用ConcurrentHashMap集合存储了BeanDefinition对象,该对象封装了Spring对一个bean所有配置信息,包括:类名、属性、构造方法参数、依赖、是否延迟加载、是否单例等配置信息。
IOC容器我们可以理解为一个Map,key是每个bean对象的id,value是bean对象本身。IOC容器负责创建bean对象并管理bean对象的生命周期。并且根据配置好配置文件或注解,管理IOC容器中的每个bean,以及根据bean之间的依赖关系,完成bean之间的注入。
IOC容器属于Spring Core模块,用来创建和管理bean,默认使用单例的方式将bean存储在DefaultListableBeanFactory类的beanDefinitionMap中(一个ConcurrentHashMap类型的Map集合)。
3、依赖注入(DI)
依赖注入(DI)是对IOC概念的不同角度的描述,是指在运行期间,当前bean对象注入它所依赖的两一个bean对象。例如:有两个类,A类和B类,A类要调用B类中的方法,此时我们不通过new的方式在A类中创建B类的对象,而是通过注入的方式,在A类的bean对象中注入B类的bean对象。
Spring IOC有三种注入方式:set方法注入、构造注入、属性注入。
(1)set方法注入(使用简单的三层模式的串联调用和学生类为例演示)
dao接口和daoImp实现类:
public interface Dao {
void save();
}
//--------------------------------------------
public class DaoImp implements Dao {
public void save() {
System.out.println("---数据访问层---");
}
}
service接口和serviceImp实现类:
public interface Service {
void save();
}
//------------------------------------------
public class ServiceImp implements Service{
Dao dao;
public void setDao(Dao dao) {
this.dao = dao;
}
public void save() {
System.out.println("---服务层---");
dao.save();
}
}
controller接口和controllerImp实现类:
public interface Controller {
void save();
}
//-----------------------------------------------
public class ControllerImp implements Controller{
Service service;
public void setService(Service service) {
this.service = service;
}
public void save() {
System.out.println("---控制层---");
service.save();
}
}
Student类:
public class Student {
private String id;
private int age;
private String name;
public void setId(String id) {
this.id = id;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
Hobby类:(类名是测试时随意起的,大家千万不要!!!)
public class Hobby {
private List myList;
private String[] myArray;
private Map myMap;
private Set mySet;
private Properties myPop;
public void setMyList(List myList) {
this.myList = myList;
}
public void setMyArray(String[] myArray) {
this.myArray = myArray;
}
public void setMyMap(Map myMap) {
this.myMap = myMap;
}
public void setMySet(Set mySet) {
this.mySet = mySet;
}
public void setMyPop(Properties myPop) {
this.myPop = myPop;
}
public String toString() {
return "Hobby{" +
"myList=" + myList +
"\n myArray=" + Arrays.toString(myArray) +
"\n myMap=" + myMap +
"\n mySet=" + mySet +
"\n myPop=" + myPop +
'}';
}
}
Test类:
public class Test {
public static void main(String[] args){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Controller controller = (Controller) applicationContext.getBean("controllerImp");
controller.save();
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
Hobby hobby = (Hobby) applicationContext.getBean("hobby");
System.out.println(hobby);
}
}
bean对象:
<!-- set方法注入:对象 -->
<bean id="controllerImp" class="com.dytx.controller.ControllerImp">
<property name="service" ref="serviceImp"></property>
</bean>
<bean id="serviceImp" class="com.dytx.com.dytx.service.ServiceImp">
<property name="dao" ref="daoImp"></property>
</bean>
<bean id="daoImp" class="com.dytx.dao.DaoImp"></bean>
<!-- set方法注入:基本类型和String -->
<bean id="student" class="com.dytx.student.Student">
<property name="id" value="2010044217"></property>
<property name="name" value="是大眼同学!"></property>
<property name="age" value="21"></property>
</bean>
<!-- set方法注入:List、array、Map、Set、Properties -->
<bean id="hobby" class="com.dytx.hobby.Hobby">
<property name="myList">
<list>
<value>水浒传</value>
<value>西游记</value>
<value>红楼梦</value>
<value>三国演义</value>
</list>
</property>
<property name="myArray">
<array>
<value>孙悟空</value>
<value>猪八戒</value>
<value>沙和尚</value>
<value>唐僧</value>
</array>
</property>
<property name="mySet">
<set>
<value>张飞</value>
<value>赵云</value>
<value>马超</value>
<value>关羽</value>
<value>黄忠</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="豹子头" value="林冲"></entry>
<entry key="霹雳火" value="秦明"></entry>
<entry key="大刀" value="关胜"></entry>
<entry key="双鞭" value="呼延灼"></entry>
<entry key="没羽箭" value="张青"></entry>
</map>
</property>
<property name="myPop">
<props>
<prop key="黑旋风">李逵</prop>
<prop key="小李广">花容</prop>
<prop key="浪里白条">张顺</prop>
<prop key="行者">武松</prop>
</props>
</property>
</bean>
执行结果:
(2)构造注入
Student类:
public class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
Test类:
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Student stu = (Student) applicationContext.getBean("student");
System.out.println(stu);
}
}
bean对象:
<!--构造方法注入基本数据类型和String-->
<bean id="student" class="com.dytx.student.Student">
<constructor-arg name="name" value="是大眼同学"></constructor-arg>
<constructor-arg name="age" value="21"></constructor-arg>
</bean>
执行结果:
使用构造方法注入对象与set方法注入对象类似,只需将<bean>下的子标签<property>标签换成<constructor-args>,依然需要使用”ref“属性来标识所注入对象代表的<bean>对象的id。
注意:构造注入不能 给bean对象注入复杂类型。
(3)属性注入:使用成员属性注入bean,不推荐。原因是:使用私有的成员变量属性,依靠反射实现,破坏封装,只能依靠IOC容器实现注入,不严谨。
三、面向切面编程(AOP)
面向切面编程(AOC):将那些与业务无关,却为业务模块所共同调用的逻辑(例如事务处理、日志管理、权限控制等)封装抽取成一个可重用的模块,这个模块被命名为“切面”(Aspect),便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP是基于动态代理实现的:
○ 如果被代理的对象,已经实现某个接口,则 Spring AOP 会使用 JDK Proxy(反射),基于接口的方式,创建代理对象(JDK动态代理的核心是InvocationHandler接口和Proxy类);
○ 如果被代理的对象,没有实现某个接口,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib,基于继承的方式,生成一个被代理对象的子类来作为代理(Cglib动态代理的核心是MethodInterceptor接口和Enhancer类)。
AOP通知类型:AOP将抽取出来的共性功能称为通知。以通知在代码中的具体位置划分为前置通知、返回通知、异常通知、后置通知以及环绕通知。
AOP连接点(Join point):AOP将所有的方法都视为连接点,不管是接口里面的抽象方法,还是实现类里面的重写方法,都是连接点。
AOP切点(Pointcut):AOP将可能被抽取共性功能的方法称为切入点。切入点是连接点的子集。
AOP目标对象(Target): 就是挖掉功能的方法对应的类生成的对象,这种对象是无法直接完成最终工作的。
AOP织入(Weaving):就是将挖掉的功能回填的动态过程。
AOP切面:切点+通知。
我们通过实现对业务层加入日志的功能来体会面向切面编程:
IService接口和Service实现类:
public interface IService {
void insert(String name);
int delete(int num);
void select();
}
//--------------------------------------------
public class Service implements IService{
public void insert(String name) {
System.out.println("---业务新增---");
}
public int delete(int num) {
System.out.println("---业务删除---");
return 0;
}
public void select() {
System.out.println("---业务查询---");
}
}
Logger类:
public class logger {
//前置通知
public void beforMethod() {
System.out.println("前置通知:" + new Date());
}
//返回通知
public void afterReturnMethod() {
System.out.println("后置通知:" + new Date());
}
//异常通知
public void throwMethod() {
System.out.println("异常通知:" + new Date());
}
//后置通知
public void afterMethod() {
System.out.println("后置通知:" + new Date());
}
}
Test类:
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IService service = (IService) applicationContext.getBean("service");
service.insert("dytx");
service.delete(1);
service.select();
}
}
bean对象:
<!--注入Service类-->
<bean id="service" class="com.dytx.service.Service"></bean>
<!--注入logger类-->
<bean id="logger" class="com.dytx.log.logger"></bean>
<!--开启aop配置-->
<aop:config>
<!--切面-->
<aop:aspect id="qieMian" ref="logger">
<!--切点-->
<aop:pointcut id="qieDian" expression="execution(* com.dytx.service.Service.*(..))"/>
<!--前置通知-->
<aop:before method="beforMethod" pointcut-ref="qieDian"></aop:before>
<!--返回通知-->
<aop:after-returning method="afterReturnMethod" pointcut-ref="qieDian"></aop:after-returning>
<!--异常通知-->
<aop:after-throwing method="throwMethod" pointcut-ref="qieDian"></aop:after-throwing>
<!--后置通知-->
<aop:after method="afterMethod" pointcut-ref="qieDian"></aop:after>
</aop:aspect>
</aop:config>
执行结果:
从执行结果来看,我们能够切实的体会到面向切面编程的作用,减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
对于环绕通知,就是将以上四种通知封装到一个返回值Object类型的方法中,在bean对象中使用一个<aop:around>标签即可:
//环绕通知
public Object aroundMEthod(ProceedingJoinPoint point){
Object returnObj = null;
try {
System.out.println("环绕通知====>前置通知");
Object[] obj = point.getArgs();//切点方法的参数
returnObj = point.proceed(obj);
System.out.println("环绕通知====>返回通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知====>异常通知");
} finally {
System.out.println("环绕通知====>后置通知");
}
return returnObj;
}
<aop:around method="aroundMEthod" pointcut-ref="qieDian"></aop:around>