配合此文一起食用[跟着刚哥学习Spring框架]。
非侵入式设计:不需要继承框架自身的类,这样更换框架也可以使用原来的代码。
IOC(控制反转):面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。 举例:Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。
测试小项目中的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:mvc="http://www.springframework.org/schema/mvc"
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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<bean id="helloworld" class="HelloWorld">
<property name="name" value="Spring"></property>
</bean>
</beans>
只有中间的<bean id>
标签是需要自主编写的:
<bean id="helloworld" class="HelloWorld">
<property name="name" value="Spring"></property>
</bean>
在main函数中,getBean方法传递的参数信息是“helloworld”,在xml文件中查询到bean id值为为“helloworld”时,HelloWorld类的name成员变量的初始化函数的参数赋值为"Spring"。
Bean:在Spring中,那些组成应用程序的主体,及由Spring IoC容器所管理的对象,都被称之为bean。
ps:Spring Ioc容器至少包含一个bean定义,但大多数情况下会有多个bean定义。bean定义与应用程序中实际使用的对象一一对应。
BeanFactory:IoC容器的顶级接口,是IoC容器的最基础实现,也是访问Spring容器的根接口,负责对bean的创建,访问等工作。
举例:
Object getBean(String name):返回Spring容器中id为name的Bean实例。
ApplicationContext:
大部分使用Spring框架的开发者所使用的方式。
两个实现类的使用方式:
(1)ClassPathXmlApplicationContext:从类路径下加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
(2)FileSystemXmlApplicationContext:从文件系统中加载配置文件
String ctxConfig = "文件绝对路径";
ApplicationContext ctx = new FileSystemXmlApplicationContext(ctxConfig);
<bean id="helloworld" class="HelloWorld">
<property name="name" value="Spring"></property>
</bean>
通过id(唯一)和class指定。
ps:要求Bean中必须有无参的构造器。
(1)属性注入:通过setter方法注入属性值。
<bean id="helloworld" class="HelloWorld">
<property name="name" value="Spring"></property>
</bean>
属性注入使用Property元素,使用name指定Bean的属性名称,使用value指定Bean的属性的值。
此方法必须要有setter!
(2)构造器注册:通过构造方法注入Bean的属性和值.
创建一个Car类:
public class Car {
private String brand;
private int speed;
private String color;
public Car(String brand, int speed, String color) {
this.brand = brand;
this.speed = speed;
this.color = color;
}
@Override
public String toString() {
return "Car [brand=" + brand +
", speed=" + speed +
", color=" + color + "]";
}
}
在main函数中加入:
Car car = (Car)ctx.getBean(Car.class);
System.out.println(car);
在配置文件中加入:
<bean id="car" class="Car">
<constructor-arg value="Audi"></constructor-arg>
<constructor-arg value="120"></constructor-arg>
<constructor-arg value="black"></constructor-arg>
</bean>
或者使用index属性:
<constructor-arg value="Audi" index="0"></constructor-arg>
或者使用type属性:
<constructor-arg value="Audi" type="java.lang.String"></constructor-arg>
(1)引用外部Bean:
<!--引用外部Bean-->
<bean id="p1" class="Person">
<property name="name" value="mayu"></property>
<property name="age" value="18"></property>
<!--使用 property的ref属性建立Bean之间的引用关系-->
<property name="car" ref="car"></property>
</bean>
被引用的外部Bean为:
<bean id="car" class="Car">
<constructor-arg value="Audi"></constructor-arg>
<constructor-arg value="120"></constructor-arg>
<constructor-arg value="black"></constructor-arg>
</bean>
通过ref的值为"car",检索bean id也为"car"的bean类。
使用 property的ref属性建立Bean之间的引用关系.
(2)引用内部类:
<bean id="p2" class="Person">
<property name="name" value="mayu"></property>
<property name="age" value="18"></property>
<!--内部Bean-->
<property name="car">
<bean class="Car">
<constructor-arg value="Benz"></constructor-arg>
<constructor-arg value="60"></constructor-arg>
<constructor-arg value="white"></constructor-arg>
</bean>
</property>
</bean>
<bean id="p1" class="Person">
<property name="name" value="mayu"></property>
<property name="age" value="18"></property>
<!--引用外部Bean-->
<property name="car" ref="car"></property>
<!--级联属性赋值-->
<property name="car.price" value="300000"></property>
</bean>
要注意的点:
1.在Car类中记得给car.price设置setter;
2.在Person类中要给car成员变量设置getter(调用的car.price为私有变量,public变量可以不用设置getter);
3.在Bean类中要先初始car。
<bean id="p3" class="Person">
<property name="name" value="mayu"></property>
<property name="age" value="18"></property>
<!--集合属性,使用ref来配置子节点信息-->
<property name="cars">
<list>
<ref bean="car1"></ref>
<ref bean="car2"></ref>
<ref bean="car3"></ref>
</list>
</property>
</bean>
Q:为什么在bean id为p1的bean中赋值price为300000会影响到它上面的bean id为car1的bean类?
A:请看第8点:Bean的作用域。默认情况下Spring返回的实例都是单例的:一旦容器中注册了实例对象,应用程序需要的时候,就直接给予,不会重复创建。
<!--通过P命名空间为Bean属性赋值,相对于传统的配置方式更加简洁 -->
<bean id="p4" class="Person" p:name="mayu" p:age="18" p:car-ref="car1"></bean>
Person p1 = (Person)ctx.getBean("p1", Person.class);
Person p2 = (Person)ctx.getBean("p1", Person.class);
System.out.println(p1 == p2);
输出结果为True。
说明默认为单例模式的,这个看scope属性。使用Bean的scope属性来配置Bean的作用域的,scope有两个重要的属性值。
singleton:默认值,容器初始化时创建Bean实例,在整个容器的生命周期内只创建一个Bean,单例的。
prototype:原型的,容器初始化时不创建Bean的实例,而在每次请求时都创建一个新的Bean的实例,并返回。
使用property-placeholder 属性占位符。
外部文件db.properties内容:
name=lulu
age=20
xml文件:
<context:property-placeholder location="db.properties">
</context:property-placeholder>
<bean id="p5" class="Person">
<property name="name" value="${name}"></property>
<property name="age" value="${age}"></property>
</bean>
首先再导入一个jar包,名为:spring-aop-4.3.6.RELEASE.jar。
然后创建如下包和类、接口:
+-- anotation
+-- controller
|-- UserController
+-- repository
|-- UserRepository(接口)
|-- UserRepositoryImlp
+-- service
|-- UserService
|--Main
组件扫描**:spring自动扫描classpath,侦测和实例化具有特定注解的组件。
1.@Componenet:基本注解;
2.@Respository:标识持久层组件;
3.@Service:标识业务层组件;
4.@Controller:标识表现层组件。
这四个注解目前的功能都是一样的,注解名的不同为了能够让标记类本身的用途更加清晰。
Spring 有默认的命名策略: 使用非限定类名, 第一个字母小写. 也可以在注解中通过 value 属性值标识组件的名称。
比如在此Demo中,UserController类名前的@Component
等同于于@Component(value="userController")
。使用getBean("userController")
可以实例化一个该类对象。
常用:
@Component(value = "car")
相当于在配置文件中<bean id="car" class="……"></bean
。
@Scope(value = "prototype")
配置创建对象是否是以单列模式进行创建。
之后还需要在配置xml文件configAutowire.xml中加上一句:
<context:component-scan base-package="anotation" />
需要扫描多个包时, 可以使用逗号分隔。
Main函数内容:
ApplicationContext ctx = new ClassPathXmlApplicationContext("configAutowire.xml");
UserController userController = (UserController) ctx.getBean("userController");
userController.excute();
会依次构造UserController、UserService和UserRepositoryImlp实例。
Q:注解方式配置Bean,是不是没办法给类成员变量赋值?
A:...
参考此文一起食用Spring AOP(面向切面示例)。
切面(aspect):横切关注点被模块化的特殊对象。
通知(advice):切面必须要完成的工作。切面中的每个方向称之为通知。通知是在切面对象中的。
目标(target):被通知的对象。
代理(proxy):向目标对象应用通知后创建的对象。
连接点(joinpoint):目标对象的程序执行的某个特定位置。如某个方法调用前,调用后的位置。包括两个信息:目标程序的哪个方法?方法执行 前还是执行后?
切点(pointcut):每个类会有多个连接点,AOP通过切点定位到特定的边接点。类比,连接点相当于数所成方圆 中的记录,而切点相当于查询条件。一个切点匹配多个连接点。
接口:
public interface Human {
public void eat();
public void sleep();
}
类:
@Componentpublic
class Mayu implements Human {
@Override
public void eat() {
System.out.println("mayu吃饭了。");
}
@Override
public void sleep() {
System.out.println("mayu睡觉了。");
}
}
切面类:
@Aspect //声明该类是切面类
@Component//配置文件中启动自动扫描功能
public class HumanAspect {
@Before("execution(* aop.*.eat(..))") //在吃饭前先洗手
public void wash() {
System.out.println("先洗手啦~");
}
@After("execution(* aop.*.eat(..))") //吃饭后刷牙
public void brush() {
System.out.println("然后刷牙啦~");
}
@Before("execution(* aop.*.sleep(..))") //睡觉前先看会书
public void read() {
System.out.println("先看会书~ ");
}
}
xml配置中加入:
<context:component-scan base-package="aop" />
<!-- 配置文件中启动AspectJ的注解功能 ,默认是false,要将其改为true -->
<aop:aspectj-autoproxy proxy-target-class="true" />
Main函数构造一个Mayu类的Bean实例:
ApplicationContext context = new ClassPathXmlApplicationContext("configAop.xml");
Mayu m = (Mayu)context.getBean(Mayu.class);
m.eat();
m.sleep();
输出结果:
先洗手啦~
mayu吃饭了。
然后刷牙啦~
先看会书~
mayu睡觉了。
"execution(* aop.*.eat(..))"
该行代码含义:
1.第一个*号:表示返回类型,*号表示所有的类型;
2.包名:表示需要拦截的包名;
3.第二个*号:通配符;
4.eat(..)
中的..:..代表所有形参,不管有多少。
Q:在xml配置中加入<!--<bean class="aop.Mayu" />-->
之后,出现编译错误:
No qualifying bean of type 'aop.Mayu' available: expected single matching bean but found 2: mayu,aop.Mayu#0
这是为何?
A:...