spring FramkWork知识点整理
Spring FramkWrok
1.介绍
- Spring是一个IOC(DI)和AOP容器框架
- 轻量级:Spring是非侵入性的 开发不依赖Spring的API
- 包含:
- 依赖注入 (DI)
- 面向切面编程 (AOP)
2.spring 的helloworld
-
applicationContext.xml中配置
<!-- 配置bean --> <bean id="helloworld" class="bean.HelloWorld"> <!-- name 对应类中set方法的名--> <property name="name2" value="Spring"></property> </bean>
-
Main方法中
// HelloWorld helloWorld = new HelloWorld(); // helloWorld.setName("JIAT"); //以上可以由spring完成 //1.创建Spring的IOC对象:会对xml中设置的bean创建对象调用构造方法,并且对property标签中的属性赋值 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //2.从IOC容器中获取Bean实例 // HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloworld"); //使用id获取 HelloWorld helloWorld = ctx.getBean(HelloWorld.class); //使用类型获取(同类型的bean无法区分) //3.调用 helloWorld.hello();
3.javaBean的注入
IOC & DI (使用的是工厂模式)
IOC: 反转资源获取的方向 原方向:组件向容器请求资源,容器返回资源;反方向:容器主动的将资源给到组件,组件要做的是选择一种合适的方式来接受资源
DI: IOC的另一种表述方式,组件以一些定义好的方式,接受来自如容器的资源注入
1.工厂方法注入bean
- IOC容器的初始化,使用beanfactory的子接口ApplicationContext,ApplicationContext有两个主要的实现类ClassPathXmlApplicationContext(从类路径下加载配置文件)和FileSystemXmlApplicationContext(从文件系统下加载配置文件)
- 从IOC容器中获取Bean实例
- HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloworld"); //使用id获取
- HelloWorld helloWorld = ctx.getBean(HelloWorld.class); //使用类型获取(同类型的bean无法区分)
2.依赖注入方式
1.在xml中bean标签属性
- Class: bean的全类名,通过反射的方式在IOC容器中创建bean,所以要求bean中必须有无参构造器
- Id: 标识容器中的bean,id唯一
2.注入方式
属性注入和构造器注入常用,工厂方法注入很少且不推荐
- 属性注入:利用setter方法注入bean属性值或者依赖的对象。
使用
- 构造方法注入:通过构造方法注入,保证bean在实例化之后就可以使用,在
中声明属性,没有name属性(该方法无需无参构造器)
<bean id="car" class="bean.CAR">
<constructor-arg value="Audi" index="0"></constructor-arg>
<constructor-arg value="ShangHai" index="1"></constructor-arg>
<constructor-arg value="300000" type="double"></constructor-arg> //price
</bean>
<!-- 使用构造器注入属性值可以指定参数的位置(index)和参数的类型(type)以区分重载构造器-->
<bean id="car2" class="bean.CAR">
<constructor-arg value="Baoma" type="java.lang.String"></constructor-arg>
<constructor-arg value="SHANGHAI" type="java.lang.String"></constructor-arg>
<constructor-arg value="240" type="int"></constructor-arg> //对应maxSpeed
</bean>
public CAR(String brand, String corp, double price) {
this.brand = brand;
this.corp = corp;
this.price = price;
}
public CAR(String brand, String corp, int maxSpeed) {
this.brand = brand;
this.corp = corp;
this.maxSpeed = maxSpeed;
}
细节:
字面值:可以直接使用value或者constructor-arg标签中子标签value的方式,若字面值中包括特殊字符,可以使用把字面值包裹起来
<bean id="car2" class="bean.CAR">
<constructor-arg value="Baoma" type="java.lang.String"></constructor-arg>
<constructor-arg type="java.lang.String">
<value><![CDATA[<SHANGHAI>]]></value>
</constructor-arg>
<constructor-arg value="240" type="int"></constructor-arg>
</bean>
-
引用其他的bean:
在property中使用ref直接引用其他bean的id
<property name="car" ref="car2"></property>
结果如下
Person{name='Tom', age=24, car=CAR{brand='Baoma', corp='
', price=0.0, maxSpeed=240}} -
创建内部bean(无法被其他的外部bean引用)
<bean id="person" class="bean.Person"> <property name="name" value="Tom"></property> <property name="age" value="24"></property> <!-- 内部bean--> <property name="car"> <bean class="bean.CAR"> <constructor-arg value="Ford"></constructor-arg> <constructor-arg value="China"></constructor-arg> <constructor-arg value="200"></constructor-arg> </bean> </property> </bean>
-
注入参数详解:null值和级联属性
可以使用专用的
为属性赋空值 <constructor-arg><null/></constructor-arg>
级联属性赋值:在对应的类中,要有该属性的set方法
<property name="car" ref="car2"></property> //需要有这一句先初始化,否则异常 <property name="car.price" value="300000"></property>
-
集合属性
- (list和数组)
-
配置Properties,每个 标签必须定义key属性
<bean id="dataSourse" class="DAO.DataSourse">
<property name="properties">
<props>
<prop key="user">root</prop>
<prop key="password">123</prop>
<prop key="jdbcUrl">jdbc:mysql:///test</prop>
<prop key="driverClass">com.mysql.jdbc.Driver</prop>
</props>
</property>
</bean>
-
单独配置集合bean,以可以被其他bean引用、
<!-- 配置独立的集合bean--> <util:list id="cars"> <ref bean ="car"/> <ref bean ="car2"/> </util:list> <bean id="person4" class="bean.PersonC"> <property name="name" value="Jack"></property> <property name="age" value="20"></property> <property name="cars" ref="cars"></property> </bean>
-
使用p命名空间(需要声明)
xmlns:p="http://www.springframework.org/schema/p"
<!-- 通过p命名空间为bean的属性赋值--> <bean id="person5" class="bean.PersonC" p:age="30" p:name="Queen" p:cars-ref="cars"></bean>
-
使用外部属性文件
Properties
user =root password = driverclass = com.mysql.jdbc.Driver jdbcurl = jdbc:mysql:///test
导入文件属性
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
获取文件属性用${}
<bean id="dataSourse" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${user}"></property> <property name="password" value="${password}"></property> <property name="driverClass" value="${driverclass}"></property> <property name="jdbcUrl" value="${jdbcurl}"></property> </bean>
3.自动装配
IOC容器可以自动装配bean,需要做的就是在bean的autowire属性里指定自动装配的模式
- ByType:根据类型自动装配,如果容器中有多个与目标bean类型一致的,将无法判定,不能执行自动装配
- ByName:根据名称自动装配,不许将目标bean的名称和属性名设置的完全相同
- Constructor:通过构造器自动装配:当bean中存在多个构造器时很复杂
<bean id="address" class="Autowire.Address" p:city="BeiJing" p:street="HuiLongGuan"></bean>
<bean id="car" class="Autowire.Car" p:brand="Audi" p:price="300000"></bean>
<bean id="person" class="Autowire.Person" p:name="Tom" autowire="byName"></bean>
//adderss和car的beanid都必须与person类中的属性名相同 没有匹配的赋空值
public class Person {
private String name;
private Address address;
private Car car;
缺点:
自动装配会配置bean中的所有属性,不够灵活
4.java bean
1.bean之间的关系
继承
<bean id="address" class="relation.Address" p:city="BeiJing" p:street="WuDaoKou" ></bean>
<!-- 继承,使用parent指定父bean的id-->
<bean id="address2" parent="address" p:street="DaZhongSi" ></bean>
定义了abstract的bean不能被实例化,只能作为模板被继承
<bean id="address" class="relation.Address" p:city="BeiJing" p:street="WuDaoKou" abstract="true"></bean>
<!-- 继承,使用parent指定父bean的id-->
<bean id="address2" parent="address" p:street="DaZhongSi" ></bean>
若某一个bean没有指定class属性,则该bean必须是一个抽象bean
<bean id="address" p:city="BeiJing" p:street="WuDaoKou" abstract="true"></bean>
依赖
<!--配置时,必须有一个关联的car,就是说person这个bean依赖于car这个bean,否则会报错-->
<bean id="car" class="relation.Car" p:brand="Audi" p:price="300000"></bean>
<bean id="person" class="relation.Person" p:name="Tom" p:address-ref="address2" depends-on="car"></bean>
依赖多个时,通过逗号 空格的方式配置bean名称
2.Bean的作用域
默认为单例: scope="singleton"
<!-- 使用bean的scope属性来配置bean的作用域
singleton:默认值,容器初始时创建bean实例,在整个容器的生命周期内只创建这一个bean,即为单例
prototype:原型的。容器初始化时不创建实例,而在每次请求时都创建一个新的bean实例,并返回
-->
<bean id="car" class="relation.Car" scope="prototype">
<property name="brand" value="Audi"></property>
<property name="price" value="300000"></property>
</bean>
public static void main(String[] args) {
//单例创建
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-scope.xml");
//原型创建
Car car = (Car) ctx.getBean("car");
Car car2 = (Car) ctx.getBean("car");
System.out.println(car==car2);
}
5.SPEL
1.介绍
Spring表达式语言:是一个支持运行时查询和操作对象图的强大的表达式语言
语法类似EL:SpEL使用#{}作为定界符,所有在大括号里的字符都被认为是SpEL
2. 用途:
- 通过Bean的id对bean进行引用
- 调用方法以及引用对象中的属性
- 计算表达式的值
- 正则表达式的匹配
3.应用
-
引用其他对象
<bean id="person1" class="Autowire.Person"> <property name="name" value="Mike"></property> <property name="address" value="#{address}"></property> <property name="car" value="#{car}"></property> </bean>
-
引用其他对象的属性
<bean id="car1" class="Autowire.Car"> <property name="brand" value="#{car.brand}"></property> </bean>
-
支持算数运算符
字符串+连接 <property name="city" value="#{adress.city+'--'+adress.street}"></property>
-
逻辑运算符:and,or,not,| , !
-
比较运算符(< , > , <= , >= , == , lt , gt , eq , le , ge)
If-else:表达式 ? ‘’ : ‘’ <property name="info" value="#{car.price>300000 ? '金领' : '白领'}"></property>
-
调用静态方法或静态属性
<!-- 使用类的静态属性--> <property name="tyrePerimeter" value="#{T(java.lang.Math).PI * 80}"></propert>
6.IOC容器管理
1.IOC容器可以管理Bean的生命周期
<bean id="car" class="cycyle.Car" init-method="init" destroy-method="destroy">
<property name="brand" value="Audi"></property>
</bean>
- Init和destory为类中自己定义的方法,作为初始化方法和关闭
- 当为单例时,容器创建就调用init方法,容器关闭即 close()时,执行destroy
- 当为原型时,只有实例化时才会调用init,每次实例化都会执行一次,容器关闭时也不会执行destroy
2.创建bean后置处理器(不需要指定id)
Bean后置处理器允许在调用初始化方法前后对bean进行额外的处理,对IOC容器中的所有bean实例逐一进行处理,而非单一实例
配置
<!-- 配置bean的后置处理器-->
<bean class="cycyle.MyBeanPostProcessor"></bean>
继承BeanPostProcessor(bean为bean对象,beanName为bean配置的id)
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("After init...."+ bean+","+beanName);
if ("car".equals(beanName)){ //过滤bean
Car car = new Car();
car.setBrand("BaoMa");
return car;
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Before init..." + bean+","+beanName);
return bean;
}
}
7.开发中配置bean的几种方式
1.通过工厂方法配置bean
1.静态工厂
public class StaticCarFactory {
//直接调用某一个类的静态方法,就可以返回bean的实例
private static Map<String, Car> cars = new HashMap<String, Car>();
static {
cars.put("audi" , new Car("audi",300000));
cars.put("ford" , new Car("ford",400000));
}
//静态工厂方法
public static Car getCar(String name){
return cars.get(name);
}
}
<!-- 通过静态工厂方法来配置bean,不是配置静态工厂方法实例
Class指向静态工厂全类名
Factory-method指向静态方法
constructor-arg用来传入静态方法参数
-->
<bean id="car1" class="factory.StaticCarFactory" factory-method="getCar">
<constructor-arg value="audi"></constructor-arg> //静态方法参数输入
</bean>
2.实例工厂
//实例工厂 的 方法,即先创建工厂本身再调用工厂的实例方法
public class InstanceCarFactory {
private Map<String,Car> cars = null;
public InstanceCarFactory() {
cars = new HashMap<String, Car>();
cars.put("audi",new Car("audi",300000));
cars.put("ford",new Car("ford",400000));
}
public Car getCar(String brand){ //静态方法
return cars.get(brand);
}
}
<!-- 配置工厂的实例
Factory-bean 指向实例工厂方法的全类名
Factory-method 指向实例工厂对象的方法
Constructor-arg 传入方法参数
-->
<bean id="carFactory" class="factory.InstanceCarFactory"></bean>
<!--通过实例工厂方法来配置bean-->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="ford"></constructor-arg>
</bean>
2.通过FactoryBean
<!-- 通过FactoryBean来配置Bean的实例
calss 指向FactoryBean的全类名
property 配置FactoryBean的属性
但实际返回的实例确实FactoryBean的getObject()方法返回的实例
-->
<bean id="car" class="factorybean.CarFactoryBean">
<property name="brand" value="BaoMa"></property>
</bean>
//自定义的FactoryBean需要实现FactoryBean<T>接口
public class CarFactoryBean implements FactoryBean<Car> {
private String brand;
public void setBrand(String brand) {
this.brand = brand;
}
//返回bean的对象
@Override
public Car getObject() throws Exception {
return new Car(brand,500000);
}
//返回bean的类型
@Override
public Class<?> getObjectType() {
return Car.class;
}
//返回是否为单例
@Override
public boolean isSingleton() {
return true;
}
}
3.基于注解的方式
1.Spring能从classpath下自动扫描,侦测和实例化具有特定注解的组件。
特定组件包括:
-
@Component:基本注解,表示了一个受Spring管理的组件
-
@Respository:标识持久层组件
-
@Service:标识服务层(业务层)组件
-
@Controller:标识表现层组件
@Service //注解 public class UserService { public void add(){ System.out.println("UserService add..."); } }
对于扫描到的组件,Spring有默认的命名策略:使用非限定类名,第一个字母小写,也可以在注解中通过value属性值标识组件名称
@Repository("userRepository") //自定义名字
2.当在组件类上使用了特定注解之后,还需要在Spring的配置文件中声明context:component-scan:
<!-- 指定spring IOC容器扫描的包-->
<context:component-scan base-package="xxx"></context:component-scan>
<!-- base-package:指定spring IOC容器扫描的包
resource-pattern:过滤作用,只扫描指定的资源
<context:include-filter> 子节点表示要包含的目标类
use-default-filters:默认为true,包涵repository、controller和service,实现只包含某个类需要与<context:include-filter>搭配use-default-filters="false"
<context:exclude-filter> 子节点表示要排除在外的目标类
annotation:expression识别的是某个类
assignable:expression识别的是某个接口,指向他的所有实现类
-->
<!-- <context:component-scan-->
<!-- base-package="annotation"-->
<!-- resource-pattern="repository/*.class"></context:component-scan>-->
<context:component-scan base-package="annotation" use-default-filters="false">
<!-- <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>-->
<!-- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>-->
<!-- <context:exclude-filter type="assignable" expression="annotation.repository.UserRepository"/>-->
<context:include-filter type="assignable" expression="annotation.repository.UserRepository"/>
</context:component-scan>
3.基于注解来配置Bean的属性
context:component-scan元素会自动注册AutowiredAnnotationBeanPostProcessor实例,该实例可以自动装配具有@Autowired 和 @Resource 、@Inject 注解的属性
@Autowired 注解自动装配具有兼容类型的单个bean属性
普通的字段以及一切具有参数的方法都可以应用@Autowired注解
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@Autowired
private UserService userService;
默认情况下,所有使用该注解的属性都需要被设置,当Spring找不到匹配的bean装配属性时,会抛出异常,若某一属性允许不被设置,可以设置注解的required属性为false
@Autowired(required = false)
private UserService userService;
当有多个类型相同的bean时,该注解会先找和注解中需要的bean名称相同的bean,若没有名称相同的bean且有多个相同类型,则会报错
-
装配的时候定义相同名字
-
利用@Qualifier的注解指定装配哪个bean:该注解还可以加到入参前面
@Autowired
@Qualifier("userRepositoryImpl")
private UserRepository userRepository;
@Resource 、@Inject与@Autowired相类似
@Resource注解要求提供一个bean名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为bean的名称
@Inject 注解按类型匹配注入的bean,但没有required属性
4.Spring 4.x泛型依赖注入:
在泛型父类中建立关系,在子类中也有这个关系
public class BaseService<T> {
@Autowired //父类中建立联系,其子类无需再进行装配
protected BaseRespository<T> respository;
public void add(){
System.out.println("add...");
System.out.println(respository);
}
}
8.在web中的应用
Spring在web应用中使用需要额外加入jar包:
Spring-web 和Spring-webmvc
创建IOC容器:(核心思路)
-
非WEB应用在main方法中直接创建
-
实际上,Spring配置文件的名字和位置也是可以配置的!将其配置到当前web应用的初始化参数中
<!-- 配置Spring配置文件的名称和位置-->
<context-param>
<param-name>configLocation</param-name>
<param-value>applicationContext.xml</param-value>
</context-param>
<!-- 启动IOC容器的监听器-->
<listener>
<listener-class>Listeners.SpringServletContextListener</listener-class>
</listener>
-
应该在web应用被服务器加载时就创建IOC容器:----监听器ServletContextListener init方法
-
在web应用的其他组件中如何来访问IOC容器
创建IOC容器后将其放入ServletContext(即application域中)的一个属性中
public void contextInitialized(ServletContextEvent sce) {
//获取初始化在web配置文件中的Spring配置文件的名称和位置
ServletContext servletContext = sce.getServletContext();
String config = servletContext.getInitParameter("configLocation");
//创建IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//放入ServletContext中
servletContext.setAttribute("ApplicationContext",ctx);
}
调用:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.从application域对象中得到IOC容器的引用
ServletContext servletContext = getServletContext();
ApplicationContext ctx = (ApplicationContext) servletContext.getAttribute("ApplicationContext");
//2.从IOC容器中得到需要的bean
Person person= (Person) ctx.getBean("person");
person.hello();
}
使用Spring封装好的接口来获取IOC
新建applicationContext.xml文件
在web配置文件中一下配置:org.springframework.web.context.ContextLoaderListener为Spring提供的API,已经完成了以上监听器的核心步骤,只需要直接从ServletContext中获取就好
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
获取IOC:使用WebApplicationContextUtils. getWebApplicationContext(application)获取
//从application中获取IOC实例
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(application);
//从IOC容器中得到bean
Person person = ctx.getBean(Person.class);
//使用bean
person.hello();
9.Spring AOP切面编程
1.介绍
应用:
可以解决由于非业务需求(日志和验证等 )加入而引起的代码混乱
可以解决修改需求时,需要修改多个模块中相同代码,即解决代码分散的问题
2.实现
使用动态代理:使用一个代理将对象包装起来
业务代码接口-------(实现类)----------à 业务代码(类加载器)
定义一个代理,传入代理的对象(即业务代码) 完成一个返回上述类的方法
//业务代码,即需要代理的类加载器
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i+j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i-j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i*j;
return result;
}
@Override
public int div(int i, int j) {
int result = i/j;
return result;
}
}
//业务接口
public interface ArithmeticCalculator {
int add(int i ,int j);
int sub(int i ,int j);
int mul(int i ,int j);
int div(int i ,int j);
}
//代理器
public class ArithmeticCalculatorLoggingProxy {
//要代理的对象
private ArithmeticCalculator target;
public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
this.target = target;
}
public ArithmeticCalculator getLoggingProxy(){
ArithmeticCalculator proxy = null;
//代理对象由哪一个类加载器负责加载
ClassLoader loader = target.getClass().getClassLoader();
//代理对象的类型,即其中有哪些方法
Class[] interfaces = new Class[]{ArithmeticCalculator.class};
//当调用代理对象其中的方法是,该执行的代码
InvocationHandler h = new InvocationHandler() {
//o : 正在返回的代理对象,一般情况下,在invoke方法中的不使用该对象
//method:正在被调用的方法
//args:调用方法时传入的参数
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
String methodName = method.getName();
System.out.println("The method "+methodName+" begins with "+ Arrays.asList(objects));
int result = (int) method.invoke(target,objects);
System.out.println("The method "+methodName+" ends with "+result);
return result;
}
};
proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces,h);
return proxy;
}
}
调用:
ArithmeticCalculator target = new ArithmeticCalculatorImpl();
ArithmeticCalculator proxy = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy(); //proxy为代理的加载器
3.AOP的概念描述
- AOP的主要编程对象是切面,而切面模块化横切关注点,在应用AOP编程时,需要定义公共功能,但是可以明确定义这个功能在哪里,以什么方式应用,而不必修改受影响的类,如此横切关注点就模块化到特殊的对象(切面)里
- 连接点:程序执行的某个特定位置,是个物理存在,即某个方法执行前,执行后,或抛出异常后等的点。由两个信息确认,一个是方法表示的程序执行点,一个是相对点的方位;即add()方法的连接点:执行点为add(),方位为add()方法执行前(后)的位置(一个类多个)
- 切点:AOP通过切点定位到特定的连接点,切点通过org.springframework.aop.Pointcut描述
4.基于注解的方式配置AOP
1.AspectJ
AspectJ:最完整最流行的AOP框架
在Spring2.0以上的版本中,可以使用基于AspectJ注解或基于XML配置的AOP
要在SpringIOC容器中启用AspectJ注解支持,只要在Bean配置文件中定义一个空的XML元素aop:aspectj-autoproxy
<!-- 使切面中的注解产生作用,调用目标方法时,自动的为方法所在的类生成代理对象,在目标方法执行前执行切面中的方法-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.定义切面类
带有@Aspect和@Component注解的类
通知有5个:Execution表达式@Before("execution(public int AOPImpl.ArithmeticCalculator.*(int ,int))")
修饰符 类型 包名 类名 方法名 (可以用*号来代替任意修饰符任意类型任意方法任意类,任意参数用..)
-
@Before 前置通知,方法执行前执行
//将这个类声明为切面: //1.将该类放入到IOC容器中 //2.再声明为一个切面 @Aspect @Component public class LoggingAspect { //日志切面 //声明该方法是个前置通知:在目标方法开始之前执行 @Before("execution(public int AOPImpl.ArithmeticCalculator.*(int ,int))") public void beforeMethod(JoinPoint joinPoint){ //JoinPoint 连接点,可以获取目标方法信息 String methodName = joinPoint.getSignature().getName(); //获取方法名 List<Object> args = Arrays.asList(joinPoint.getArgs()); //获取输入参数 System.out.println("The method"+methodName+ "begins with" + args); } }
-
@After 后置通知,方法执行后执行
//后置通知,无论是否异常都可以执行,无法访问目标方法执行的结果 @After("execution(public int AOPImpl.ArithmeticCalculator.*(int,int ))") public void afterMethod(JoinPoint joinPoint){ String methodName =joinPoint.getSignature().getName(); System.out.println("The method "+methodName+ " ends."); }
-
@AfterReturning 返回通知,在方法返回结果之后执行
/返回通知可以访问到方法的返回值,出现异常不会返回,定义一个returning @AfterReturning(value = "execution(public int AOPImpl.ArithmeticCalculator.*(..))",returning = "result") public void afterReturning(JoinPoint joinPoint,Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method "+methodName+ " ends with "+result); }
-
@AfterThrowing 异常通知,在方法抛出异常之后执行
//在目标方法出现异常时,会执行的代码,而且可以访问到一场对象,可以指定在出现特定异常时执行通知代码 @AfterThrowing(value = "execution(public int AOPImpl.ArithmeticCalculator.*(..))",throwing = "ex") public void afterThrowing(JoinPoint joinPoint,Exception ex){ //定义ex的类型可以指定特定异常 String methodName = joinPoint.getSignature().getName(); System.out.println("The method "+methodName+ " occurs excetion "+ex); }
-
@Around 环绕通知,围绕着方法执行
//环绕通知需要携带ProceedingJoinPoint类型的参数 //环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。 //且环绕通知必须有返回值,返回值即为目标方法的返回值 @Around("execution(public int AOPImpl.ArithmeticCalculator.*(..))") public Object aroundMethod(ProceedingJoinPoint pjd){ Object result = null; //执行目标方法 try { //前置通知 System.out.println("前置通知"); result = pjd.proceed(); System.out.println("返回通知"); } catch (Throwable throwable) { //异常通知 System.out.println("异常通知"); //throw new RuntimeException(throwable); 抛出异常 } //后置通知 System.out.println("后置通知"); return result; }
3.切面的优先级
-
切面的优先级(@Order 注解在相同目标方法的切点方法上)
-
在切面上注解@Order(1) 数字越小优先级越高
4.重用切面表达式
//定义一个方法,用于声明切入点表达式,一般的,该方法中再不需要填入其他的代码 //使用@Pointcut来声明切入点表达式 //后面的其他通知直接使用方法名来引用当前的切入点表达式 @Pointcut("execution(public int AOPImpl.ArithmeticCalculator.*(..))") public void declareJointPointExprssion(){}
引用
@Before("declareJointPointExprssion()")
-
5.基于xml配置文件来配置AOP(不需要注解)
<!-- 配置bean-->
<bean id="arithmeticCalculator" class="AOPXML.ArithmeticCalculatorImpl"></bean>
<!-- 配置切面的bean-->
<bean id="loggingAspect " class="AOPXML.LoggingAspect"></bean>
<!-- 配置AOP-->
<aop:config>
<!-- 配置切点表达式-->
<aop:pointcut id="pointcut" expression="execution(public int AOPXML.ArithmeticCalculator.*(..))"/>
<!-- 配置切面及通知-->
<aop:aspect ref="loggingAspect " order="1"> <!-- 指定切面方法和优先级-->
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"></aop:after-returning>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing>
<aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
</aop:aspect>
</aop:config>
10.Spring对JDBC的支持
1.JDBC Template
-
配置连接池
<!-- 导入资源文件--> <context:property-placeholder location="db.properties"/> <!-- 配置c3p0数据源--> <bean id="dataSourse" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.diverClass}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean>
-
配置jdbcTemplate
<!-- 配置Spring 的JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSourse"></property>
</bean>
- Dao中直接调用jdbcTemplate的方法
@Repository //将DAO配置到IOC容器中
public class EmployeeDao {
@Autowired
private JdbcTemplate jdbcTemplate;
- update(String sql, @Nullable Object... args)
//测试Update方法:执行INSERT,UPDATE,DELETE
public void testUpdate(){
String sql = "UPDATE employees SET last_name=? WHERE id =?";
jdbcTemplate.update(sql,"Jacke",4);
}
- batchUpdate(String sql, List<Object[]> batchArgs)
//测试batchUpdate()方法:执行update的批量更新
//batchArgs为一个Object[]的List类型:因为修改一条记录需要一个Object数组
public void testBatcheUpdate(){
String sql = "INSERT INTO employees(last_name,email,dept_id) VALUES(?,?,?)";
List<Object[]> batchArgs = new ArrayList<>();
batchArgs.add(new Object[]{"世文","aa@qq.com",1});
batchArgs.add(new Object[]{"kaixin","bb@qq.com",3});
batchArgs.add(new Object[]{"CC","cc@qq.com",2});
jdbcTemplate.batchUpdate(sql,batchArgs);
}
- queryForObject(String sql, RowMapper
rowMapper, @Nullable Object... args)
//从数据库中获取一条记录,实际得到一个对应的对象
//不是调用queryForObject(String sql, Class<T> requiredType, Object... args) 方法
//调用queryForObject(String sql, RowMapper<T> rowMapper, Object... args)方法
//1.其中RowMapper指定如何去映射结果集的行,常用的实现类为BeanPropertyRowMapper
//2.使用SQL中列的别名完成列名和类的属性名的映射(一一对应)类似last_name--> lastName
//3.不支持级联属性,JdbcTemplate到底是一个Jdbc的小工具,不是ORM框架
public void testQueryForObject(){
String sql = "SELECT id,last_name lastName,email FROM employees WHERE id = ?";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
Employee employee = jdbcTemplate.queryForObject(sql,rowMapper,1);
System.out.println(employee);
}
- query(String sql, RowMapper
rowMapper, @Nullable Object... args)
//查询实体类的实体
public void testQueryForList(){
String sql = "SELECT id,last_name lastName,email FROM employees WHERE id > ?";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
List<Employee> employees = jdbcTemplate.query(sql,rowMapper,4);
System.out.println(employees);
}
5.queryForObject(String sql, Class
//获取单个列的值,或做统计查询
//使用queryForObject(String sql, Class<T> requiredType) 方法
public void testQueryForObject2(){
String sql = "SELECT count(id) FROM employees";
long count = jdbcTemplate.queryForObject(sql,Long.class);
System.out.println(count);
}
JDBCTemplate类被设计成为线程安全的,所以可以在IOC容器中去声明它的单个实例,并将这个实例注入到所有的DAO实例中
2.NamedParameterJdbcTemplate
在Spring JDBC模板中使用具名参数,可以取代sql语句中的?占位符(当语句中多个参数时可以使代码容易维护),在NamedParameterJdbcTemplate中得到支持
- Xml中进行配置:
<!-- 配置NamedParameterJdbcTemplate,该对象可以使用具名参数,其没有无参构造器,必须制定参数-->
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSourse"></constructor-arg>
</bean>
- 两种方式 ,一种使用map,一种使用SqlParameterSource
//可以为参数命名,与map中的键名一一对应,在参数多的时候不受顺序影响,便于维护
public void testNamedPatameterJdbcTemplate(){
String sql = "INSERT INTO employees(last_name,email,dept_id) VALUES(:ln,:email,:deptid)";
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("email" ,"jdifj@qq.com");
paramMap.put("deptid",3);
paramMap.put("ln" ,2);
namedParameterJdbcTemplate.update(sql,paramMap);
}
//使用具名参数时,可以使用update(String sql, SqlParameterSource paramSource)方法进行更新操作
//1.语句中的参数名与对象属性名一致
//2.使用SqlParameterSource的BeanPropertySqlParameterSource实现类作为参数
public void testNamedPatameterJdbcTemplate2() {
String sql = "INSERT INTO employees(last_name,email,dept_id) VALUES(:lastName,:email,:dpid)";
Employee employee = new Employee();
employee.setLastName("jdif");
employee.setEmail("XIE@162.COM");
employee.setDpid(3);
SqlParameterSource paramSource = new BeanPropertySqlParameterSource(employee);
namedParameterJdbcTemplate.update(sql,paramSource);
}
11.Spring中的事务管理
1.事务
用来确保数据的完整性和一致性,他是一系列动作,被当作一个单独的工作单元,要么全部完成,要么全部不起作用,例如交易的时候,库存和用户余额的变化是同步的,要么一起完成,要么都不变
Spring的核心事务管理抽象:事务管理器
2.声明式事务:
配置文件
<!--创建命名空间-->
xmlns:tx="http://www.springframework.org/schema/tx"
配置:
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSourse"></constructor-arg>
</bean>
<!-- 启用事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
在启用事务的方法上加事务注解
//添加事务注解
@Transactional
@Override
public void purchase(String username, String isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新书的库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(username,price);
}
3.事务的属性
事务的传播行为,事务的隔离级别,事务的回滚,只读,事务的过期时间
1.传播行为
传播行为:一个事务方法被另一个事务方法调用时,必须指定事务应该如何运行在另一个事务中,例如:方法可能继续在现有的事务中运行,也可能开启一个新事务,并在自己的事务中运行
由传播属性指定,Spring定义了7种类传播行为:
- REQUIRED(默认) 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事物,并在自己的事务中运行 (整体停止,只要事务中的任意事务停止整个停止 exp:不能结账全部不买)
- REQUIRED_NEW 当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起 (事务中的事务通过时,部分代码正常运行 exp:不足结账,按账单顺序能买的买,不能则停止)
- SUPPORTS 如果有事务在运行,当前方法则在这个事务内运行,否则它可以不运行在事务中
- NOT_SUPPORTS 不能运行在事务中,如果有运行的事务,将它挂起
- MANDATORY 必须运行在事务中,如果没有正在运行的事务,抛出异常
- NEVER 不能运行在事务中,如果有运行的事务,抛出异常
- NESTED 如果有事务在运行,当前方法就应该在这个事务的嵌套事务中运行,否则,就启动一个事务,在自己的事务中运行
添加传播行为:
//propagation指定事物的传播行为,即当前事务方法被另外一个事务方法调用时
@Transactional(propagation = Propagation.REQUIRED)
2.隔离级别
最常用的isolation = Isolation.READ_COMMITTED
3.回滚(出现异常回滚使操作停止,一般不设置)
默认情况下Spring的声明式事务对所有的运行时异常进行回滚,也可以通过对应属性设置
noRollbackFor 设置那个异常不回滚
noRollbackFor = {UserAccountException.class}
4.只读
readOnly 指定是否只读,表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务,只读取数据库值的方法需要设置true
5.过期时间
timeout 指定强制回滚之前事务可以占用的时间,当方法运行时间超过事务设置的过期时间,将会强制回滚,以s为单位
timeout = 3 //过期时间三秒
4.使用xml来配置事务
- 先正常配置好bean
<!-- 配置bean-->
<bean id="bookShopDao" class="txxml.BookShopDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="bookShopService2" class="txxml.serviceImpl.BookShopServiceImpl">
<property name="bookShopDao" ref="bookShopDao"></property>
</bean>
<bean id="cashier" class="txxml.serviceImpl.CashierImpl">
<property name="bookShopService" ref="bookShopService2"></property>
</bean>
-
配置管理器
<!-- 配置事务管理器--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="dataSourse"></constructor-arg> </bean>
-
配置事务属性
<!-- 配置事务属性-->
<tx:advice id="tdAdvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<!-- 根据方法名指定事务的属性-->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<tx:method name="get*" read-only="true"></tx:method>
<tx:method name="find*" read-only="true"></tx:method>
<tx:method name="*"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 配置事务切入点,以及吧事务切入点和事务属性关联起来-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* txxml.service.*.*(..))"/>
<aop:advisor advice-ref="tdAdvice" pointcut-ref="txPointCut"></aop:advisor>
</aop:config>