Spring框架的IOC、DI、AOP应用详解
什么是Spring
是一个为简化企业级应用开发的开源框架,作为IOC(DI)、AOP容器。
特性
轻量级:占用空间小;非侵入性(是否被框架绑架):通过配置的方法,使代码中不引用框架的类,删除jar包后不会报错;
控制反转、依赖注入:将设计好的对象交给容器,该对象的属性也由容器进行注入;
面向切面编程:
容器:管理Java对象(所有的类)的生命周期;
框架:使用简单的组件配置组合成一个复杂的应用;
一站式:在IOC和AOP基础上可以整合各种企业的开源框架和第三方类库,Spring本身也提供了视图层的SpringMVC和持久层的Spring JDBC;
相关术语
Ioc(控制反转 Inversion of Control)
Di(依赖注入 Dependency Injection)
Aop(面向切面编程 Aspect Oriented Programming)
以上都是一种方法论,不是Spring专有
Dao(数据访问对象 Data Access Object)
Orm(对象关系映射 (Object Relational Mapping)
Service(服务、业务逻辑 等于 Business)
MVC(模型-视图-控制器 Model View Controller)
IOC—控制反转、DI—依赖注入
把创建实例的控制权交给框架,由框架创建实例(控制反转)并把实例分发给调用的程序(依赖注入)。由Spring容器来控制对象的生命周期,默认是单例模式的,目的是层和层之间的解耦。
如何实现的解耦
依赖:如果在一个类中想要调用另一个对象的非静态方法,必须先创建这个对象,不创建时编译报错,此时就称这个对象是这个类的依赖。
某一个类中的依赖在正常编译的情况下,这个类才有可能正常编译。由此,便产生了耦合。
IOC为此而生,不管所依赖的对象是否存在,都能够编译成功:
通过实现接口的方式(面向接口编程),类中不再声明实现类,而是声明该类的接口。
IOC示例代码
- 新建Java项目
- 导入相关jar包:Spring框架jar包及commons-logging.jar(https://mvnrepository.com/下载)
- 创建接口
package t;
public interface HelloWorld
{
public void sayHello();
}
- 创建实现类
package t;
public class HelloWorldImpl implements HelloWorld
{
@Override
public void sayHello()
{
System.out.println("helloworld");
}
}
- 编写配置文件
<?xml version="1.0" encoding="utf-8"?>
<!-- 上面为xml文件的头,指明xml的版本号和当前文件的编码,必须在第一行 -->
<!-- 声明spring配置文件的跟标签beans -->
<!-- 通过xmlns这个命名空间属性声明当前xml文档需要用到的标签所在的包 -->
<!-- xmlns:xsi声明xsi属性前缀所在的命名空间 -->
<!-- xsi:schemaLocation声明当前xml文件里用标签的合法性校验 -->
<!-- xsi:schemaLocation属性的值包含“命名空间和校验文件” -->
<!-- 扩展名是xsd的文件是xml的语法校验文件 -->
<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-4.0.xsd">
<!-- 用bean标签声明一个HelloWorldImpl类的实例, 并且把实例的名字定义为helloWorld-->
<!-- 其中需要注意,class属性里一定是实现类;id属性在整个配置文件里必须唯一-->
<bean id="helloWorld" class="t.HelloWorldImpl"/>
</beans>
- 编写测试类
package t;
import org.springframework.context.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test
{
public static void main(String[] args)
{
//通过src目录(即classpath)下的t目录下的IOC.xml配置文件,创建spring容器
AbstractApplicationContext applicationContext = new ClassPathXmlApplicationContext("t/IOC.xml");
//去容器里取到id=helloWorldBean的实例,并且把这个实例声明为接口的类型
HelloWorldImpl bean = (HelloWorldImpl)applicationContext.getBean("helloWorld");
bean.sayHello();
applicationContext .close();//关闭容器
}
}
DI(依赖注入)
把有依赖关系的类的实例注入为指定类的属性值。
需要注入的类和被注入的类都需要交个Spring容器来管理。
在Controller层中声明service层的接口,注入service的实现类,在Service层中注入Dao层的接口,注入dao的实现类。
构造器注入
- 有一个属性
- 属性要在构造方法中被赋值
- 创建需要有属性被注入的类
public class TextPrinter {
private Formater formater;//该属性被注入
private int size;
public TextPrinter(Formater formater,int size){
this.formater=formater;
this.size=size;
}
public void print(String info){
System.out.println("Set size="+this.size);
this.formater.execute(info);
}
}
- 注入属性的类
public class Formater {
public void execute(String info) {
System.out.println("Formater = "+info);
}
}
- 配置文件里,配置注入标签
<!-- 声明id为f的bean实例 -->
<bean id="f" class="s1.Formater"/>
<!-- 声明id为tp的bean实例 -->
<bean id="tp" class="s1.TextPrinter">
<!-- 通过constructor-arg调用tp实例的构造方法进行注入 -->
<!--
index : 标明构造方法里参数的下标,即为第几个参数注入
ref子标签代表注入一个实例(引用);bean : 指明一个已经在配置文件中配置好的bean的id
type : 标明这个参数的类型
value : 直接给这个参数注入一个具体的值
-->
<constructor-arg index="0">
<ref bean="f"/>
</constructor-arg>
<constructor-arg index="1" type="int" value="5"/>
</bean>
- 写测试类测试上面的注入结果
AbstractApplicationContext context = new ClassPathXmlApplicationContext("f.xml");
TextPrinter bean = (TextPrinter) context.getBean("tp");
bean.print("Spring 4");
context.close();
属性注入
- 有至少一个属性
- 这个属性至少有公有set方法
- 创建需要有属性被注入的类
public class TextPrinter2 {
private Formater formater;//被注入的属性
private int size;//被注入的属性
public Formater getFormater() {
return formater;
}
//一定要有这个方法
public void setFormater(Formater formater) {
this.formater = formater;
}
public int getSize() {
return size;
}
//一定要有这个方法
public void setSize(int size) {
this.size = size;
}
public void print(String info){
System.out.println("Set size="+this.size);
this.formater.execute(info);
}
}
- 注入属性的类 参照上面
- 配置文件里配置属性注入标签
...
<bean id="tp2" class="s1.TextPrinter2">
<!--
通过property标签进行属性注入
name : 标明类里被注入的属性的变量名
ref子标签同上
value子标签 type属性同上
-->
<property name="formater">
<ref bean="f"/>
</property>
<property name="size">
<value type="int">5</value>
</property>
</bean>
...
- 写测试类测试上面的属性注入结果 参考上面
集合类型的注入
- 数组
- list
- set
- map
- Properties
- 创建需要有集合属性被注入的类
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Department {
private String name;
private String [] empName;//数组
private List<Employee> empList;//list集合
private Set<Employee> empsets;//set集合
private Map<String,Employee> empMaps;//map集合
private Properties pp;//Properties的使用
public Set<Employee> getEmpsets() {
return empsets;
}
public void setEmpsets(Set<Employee> empsets) {
this.empsets = empsets;
}
public String[] getEmpName() {
return empName;
}
public void setEmpName(String[] empName) {
this.empName = empName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Employee> getEmpList() {
return empList;
}
public void setEmpList(List<Employee> empList) {
this.empList = empList;
}
public Map<String, Employee> getEmpMaps() {
return empMaps;
}
public void setEmpMaps(Map<String, Employee> empMaps) {
this.empMaps = empMaps;
}
public Properties getPp() {
return pp;
}
public void setPp(Properties pp) {
this.pp = pp;
}
}
- 创建集合里元素的实例类
public class Employee {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 在配置文件中配置集合属性的注入
...
<!-- 声明被注入的实例emp1 -->
<bean id="emp1" class="s1.Employee">
<property name="name" value="北京"/>
<property name="id" value="1"/>
</bean>
<!-- 声明被注入的实例emp2 -->
<bean id="emp2" class="s1.Employee">
<property name="name" value="天津"/>
<property name="id" value="2"/>
</bean>
<bean id="department" class="s1.Department">
<!-- 用list子标签给数组注入值 -->
<property name="empName">
<list>
<value>小明</value>
<value>小明小明</value>
<value>小明小明小明小明</value>
</list>
</property>
<!-- 用list子标签给list集合属性注入,注入的是Employee类的实例 -->
<property name="empList">
<list>
<ref bean="emp2"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
</list>
</property>
<!-- 用set子标签给set集合属性注入,注入的是Employee类的实例 -->
<property name="empsets">
<set>
<ref bean="emp1"/>
<ref bean="emp2"/>
<ref bean="emp2"/>
<ref bean="emp2"/>
<ref bean="emp2"/>
</set>
</property>
<!-- 用map和entry子标签给map集合属性注入,注入的是Employee类的实例,同时给每个实例一个key -->
<property name="empMaps">
<map>
<entry key="11" value-ref="emp1"/>
<entry key="22" value-ref="emp2"/>
<entry key="22" value-ref="emp1"/>
</map>
</property>
<!-- 用给props和prop子标签给property属性集合注入,注入的key和value都是String -->
<property name="pp">
<props>
<prop key="pp1">abcd</prop>
<prop key="pp2">hello</prop>
</props>
</property>
</bean>
...
- 写测试类测试上面的集合属性注入结果
...
Department department=(Department) context.getBean("department");
for(String emName:department.getEmpName()){
System.out.println(emName);
}
System.out.println("**********取出list集合数据*****");
for(Employee e : department.getEmpList()){
System.out.println("name="+e.getName()+" "+e.getId());
}
System.out.println("**********取出set集合数据*****");
for(Employee e : department.getEmpsets()){
System.out.println("name="+e.getName());
}
System.out.println("*******取出map集合数据方法一****");
Map<String,Employee> empmaps=department.getEmpMaps();
Iterator it=empmaps.keySet().iterator();
while(it.hasNext()){
String key=(String) it.next();
Employee emp=empmaps.get(key);
System.out.println("key="+key+" "+emp.getName());
}
System.out.println("*******取出map集合数据方法二****");
Set<Map.Entry<String, employee>> entries = department.getEmpMaps().entrySet();
for (Map.Entry<String, employee> e:entries
) {
System.out.println(e.getKey()+" "+e.getValue().getId()+" "+e.getValue().getName());
}
System.out.println("*******取出map集合数据方法三****");
Set<String> keySet = department.getEmpMaps().keySet();
for (String s:keySet
) {
System.out.print(s+" ");
employee employee = d.getEmpmap().get(s);
System.out.println(employee.getId()+" "+employee.getName());
}
System.out.println("**********取出Properties数据*****");
Properties pp = d.getPp();
System.out.println(pp.getProperty("name"));
System.out.println(pp.getProperty("url"));
Enumeration<?> enumeration = pp.propertyNames();
while(enumeration.hasMoreElements()){
System.out.println(enumeration.nextElement());
}
...
注解的方式
语法 : @注解名("参数")
用注解不能有构造器,否则会报错
ioc,替代bean标签的注解
@Component - 其他类
@Controller - servlet
@Service - service
@Repository - dao
注解在class声明上方,同样的功能,分四个单词,不同层的类用不同单词类注解
@Service("MyBean") 相当于 <bean id="MyBean" class="被注解的类"/>
@Service("MyBean")
public class DIHW {
private HelloWorld hImpl;
public void print(){
hImpl.sayHello(" d i ");
}
}
di,替代property和ref标签的注解
- @Resource - javax
- @AutoWired @Qualifier - spring
注解在需要被注入的属性上面,可以声明被注入实例的id,或者默认
[@Resource(name="MyBean")] 相当于
[@AutoWired @Qualifier("hello313Impl")] 相当于
<property name="注解下面的属性名">
<ref bean="MyBean"/>
</property>
@Autowired
@Qualifier("MyBean")
或
@Resource(name="MyBean")
//@Resource(type=MyBean.class)
private HelloWorld hImpl;
di注解默认的注入规则
AutoWired注解的默认规则——按照接口类型自动装配
只写这个注解,产生的id按照驼峰命名法命名,按照被注入的属性的接口类型来找实例,如果找不到,或找到多个都报错;
如果想按照名字来找实例,或者有多个类实现这个接口,必须引入Qualifier注解
Resource注解的默认规则——按照属性名自动装配
1.如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
2.如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
3.如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
4.如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配在按照类型装配。
自动装配和扫描
spring配置文件的头部的通用定义,如果要使用注入,要引入新的命名空间和新的校验文件
<?xml version="1.0" encoding="UTF-8"?>
...
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
">
...
</beans>
启动注解,并且自动扫描那个类被注解,需要在spring的配置文件中添加标签
...
<context:annotation-config/>
<!--声明4个类帮助系统识别注解-->
<context:component-scan base-package="要扫描的包"/>
<!--使用 <context:component-scan/> 后,就可以将 <context:annotation-config/> 移除了--!>
...
AOP—面向切面编程(对事务进行控制)
多个类中有相同的代码块,为减少代码量,将此代码块取出,作为一个切面,切面的前后位置叫做切点,将此切面交给Spring框架管理,自动执行此段代码,减少了方法中的代码量。
相关概念
1.切面(Aspect):需要插入的代码块
2.连接点(JoinPoint):
3.切点(Pointcut):指定某个方法作为切面切的地方
4.通知(Advice):指定切面在切点的插入位置
前置通知(before advice)
后置通知(after return advice)
环绕通知(around advice)
异常通知(after throwing advice)
5.目标对象(target object)
6.AOP代理(aop proxy)
XML和注解方式AOP
xml方式
- 新建两个类
//被切入的类
public class Car {
public void go(){
System.out.println("go go go!");
}
}
//存放切入代码的类
public class CarLogger {
public void beforeRun(){
System.out.println("之前");
}
public void afterRun(){
System.out.println("之后");
}
}
- 创建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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<bean id="car" class="aop.Car" />
<bean id="logger" class="aop.CarLogger" />
<aop:config>
<aop:aspect ref="logger">
<aop:pointcut expression="execution(* aop.Car.go(..))" id="go"/>
<!--切点表达式:找到切点,“..”为参数任意。“*”为返回值任意-->
<aop:before pointcut-ref="go" method="beforeRun" />
<aop:after pointcut-ref="go" method="afterRun" />
</aop:aspect>
</aop:config>
</beans>
- 创建测试类
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAop {
public static void main( String[] args ){
ApplicationContext context = new ClassPathXmlApplicationContext("aop-config.xml");
Car car=(Car) context.getBean("car");
car.go();
}
}
注解方式
- 同上新建两个类
- 修改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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<bean id="car" class="aop.Car" />
<bean id="logger" class="aop.CarLogger" />
<!--自动完成创建代理织入切面的工作-->
<aop:aspectj-autoproxy/>
</beans>
- 修改CarLogger类如下
@Aspect
public class CarLogger {
@Before("execution(* aop.Car.go(..))")
public void beforeRun(){
System.out.println("之前");
}
@After("execution(* aop.Car.go(..))")
public void afterRun(){
System.out.println("之后");
}
}
或
@Aspect
public class CarLogger {
@Pointcut("execution(* aop.Car.go(..))")
public void anyMethod(){}
@Before("anyMethod()")
public void beforeRun(){
System.out.println("之前");
}
@After("anyMethod()")
public void afterRun(){
System.out.println("之后");
}
}