认识Spring4
Spring是J2EE的一个全栈式框架,是一个非侵入式,控制反转,依赖注入,面向切面编程的框架。
控制反转,以前依赖关系都是依赖实现,反转则是依赖于抽象,即面向接口编程
面向切面编程,将日志,安全,事务管理理解成一个切面,以前是将这些写在逻辑代码中,现在则独立,实现复用,只需要指定在那个地方插入切面过滤即可。
Spring虽然在不断发展,但是核心功能已经固定:Spring AOP,Spring ORM,Spring Web,SpringWebMVC,Spring Dao,Spring Context,Spring Core
Spring IOC
面向接口编程
- 实现登陆功能
Spring提倡面向接口编程,依赖注入的基本思想:明确的定义组件接口,独立开发各个组件,根据组件的依赖关系组装运行。
- 编写Dao层
编写Dao
package cn.edu.scnu.dao;
public interface UserInfoDao {
public boolean login(String username,String password);
}
编写DaoImpl
package cn.edu.scnu.dao.impl;
import cn.edu.scnu.dao.UserInfoDao;
public class UserInfoDaoImpl implements UserInfoDao {
@Override
public boolean login(String username, String password) {
if(username.equals("admin")&&password.equals("123456")){
return true;
}else {
return false;
}
}
}
- 编写Service层
通过接口实现Bean的关联,并不需要使用具体实现类
编写Service
package cn.edu.scnu.service;
public interface UserInfoService {
public boolean login(String username,String password);
}
编写ServiceImpl
package cn.edu.scnu.service.impl;
import cn.edu.scnu.dao.UserInfoDao;
import cn.edu.scnu.service.UserInfoService;
public class UserInfoServiceImpl implements UserInfoService {
//使用接口声明对象,提供settter方法,用于依赖注入,
private UserInfoDao userInfoDao;
@Override
public boolean login(String username, String password) {
return userInfoDao.login(username,password);
}
public void setUserInfoDao(UserInfoDao userInfoDao) {
this.userInfoDao = userInfoDao;
}
}
- 创建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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--通过配置文件将实现类注入-->
<bean id="userInfoDao" class="cn.edu.scnu.dao.impl.UserInfoDaoImpl"/>
<bean id="userInfoService" class="cn.edu.scnu.service.impl.UserInfoServiceImpl">
<property name="userInfoDao" ref="userInfoDao"/>
</bean>
</beans>
- 测试
package cn.edu.scnu;
import cn.edu.scnu.service.UserInfoService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInfoService userInfoService = (UserInfoService) context.getBean("userInfoService");
boolean bool = userInfoService.login("admin","123456");
System.out.println(bool);
}
}
Bean的装配模式
==Spring容器==
1.手工创建容器
BeanFactory称为IOC容器,而ApplicationContext扩展了BeanFactory容器,成为了Spring的首选容器。
* **ClassPathXmlApplicationContext**:从类路径中寻找配置文件。
* **FileSystemXmlApplicationContext**:从文件系统中寻找配置文件。
* **XmlWebApplicationContext**:从Web应用中寻找指定的XML配置文件。
2. Web项目实例化容器
- 基于ContextLoaderListenter实现
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<!--只适用于Servlet2.4及以上规范。-->
<!--指定Spring配置文件的位置,多个配置文件以逗号隔开-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml,applicationContext2.xml</param-value>
</context-param>
<!--指定以Listerner方式启动spring容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListerner</listener-class>
</listener>
</web-app>
- 基于ContextLoaderServlet实现
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<!--只适用于Servlet2.4及以上规范。-->
<!--指定Spring配置文件的位置,多个配置文件以逗号隔开-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml,applicationContext2.xml</param-value>
</context-param>
<!--指定以Servlet方式启动Spring容器-->
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
</web-app>
==Bean的作用域==
Spring框架支持5种作用域,有三种作用域是当开发者使用基于web的ApplicationContext的时候才生效的。
作用域 | 描述 |
---|---|
单例(singleton) | (默认)每一个Spring IoC容器都拥有唯一的一个实例对象 |
原型(prototype) | 一个Bean定义,任意多个对象 |
请求(request) | 一个HTTP请求会产生一个Bean对象,也就是说,每一个HTTP请求都有自己的Bean实例。只在基于web的Spring ApplicationContext中可用 |
会话(session) | 限定一个Bean的作用域为HTTPsession的生命周期。同样,只有基于web的Spring ApplicationContext才能使用 |
全局会话(global session) | 限定一个Bean的作用域为全局HTTPSession的生命周期。通常用于门户网站场景,同样,只有基于web的Spring ApplicationContext可用 |
应用(application) | 限定一个Bean的作用域为ServletContext的生命周期。同样,只有基于web的Spring ApplicationContext可用 |
测试单例模式和原型模式
- pojo
package cn.edu.scnu.pojo;
public class HelloWorld {
private String msg;
public void print(){
System.out.println(this.msg);
}
public void setMsg(String msg) {
this.msg = msg;
}
}
- 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="hello" class="cn.edu.scnu.pojo.HelloWorld" scope="singleton">
<property name="msg" value="单例模式"/>
</bean>
<bean id="world" class="cn.edu.scnu.pojo.HelloWorld" scope="prototype">
<property name="msg" value="原型模式"/>
</bean>
</beans>
- 测试
class App2{
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
// 单例模式
HelloWorld helloWorld1 = (HelloWorld) context.getBean("hello");
HelloWorld helloWorld2 = (HelloWorld) context.getBean("hello");
System.out.println(helloWorld1==helloWorld2); //true
// 原型模式
HelloWorld helloWorld3 = (HelloWorld) context.getBean("world");
HelloWorld helloWorld4 = (HelloWorld) context.getBean("world");
System.out.println(helloWorld3==helloWorld4); //false
}
}
==Bean的装配方式==
Spring支持多种Bean的装配方式,基于XML的装配,基于注解的装配,自动装配,基于javaConfig的装配,其中以xml功能最强。
基于xml的装配方式的关键字
关键字 | 应用 |
---|---|
beans | 整个配置文件的根节点 |
bean | 定义Bean,其中包括{id/name,class,scope} |
constructor-arg | 构造方法注入 |
property | setter方法注入 |
ref | 指定Bean的引用 |
value | 指定常量 |
list | 指定list或者数组类型属性 |
set | 指定set类型属性注入 |
map | 指定Map类型属性注入 |
entry | map标记的子标记,其中key属性指定键,ref或者value指定值。 |
pojo
1. Department.java
<?xml version="1.0" encoding="UTF-8"?>
<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.xsd">
<!--指定初始化方法和销毁方法,使用setter方法注入-->
<!--初始化方法,生成容器就会启动-->
<bean id="dept0" class="cn.edu.scnu.pojo.Department" init-method="init" destroy-method="destroy">
<property name="dept_name" value="张三"/>
</bean>
<bean id="dept1" class="cn.edu.scnu.pojo.Department" init-method="init" destroy-method="destroy">
<property name="dept_name" value="张si"/>
</bean>
<bean id="dept2" class="cn.edu.scnu.pojo.Department" init-method="init" destroy-method="destroy">
<property name="dept_name" value="张三"/>
</bean>
<!--
private String username; //常量
private Department dept; //Bean
private List<String> emList; //list
private Map<Integer,String> emMap; //map
private Set<Department> emSet; //set
-->
<bean id="employee" class="cn.edu.scnu.pojo.Employee">
<constructor-arg index="0" value="里斯"/>
<constructor-arg index="1" ref="dept1"/>
<constructor-arg index="2">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</constructor-arg>
<constructor-arg index="3">
<map>
<entry key="1" value="hello"></entry>
<entry key="2" value="world"/>
</map>
</constructor-arg>
<constructor-arg index="4">
<set>
<ref bean="dept0"/>
<ref bean="dept1"/>
<ref bean="dept2"/>
</set>
</constructor-arg>
</bean>
</beans>
- Employee.java
package cn.edu.scnu.pojo;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Employee {
private String username; //常量
private Department dept; //Bean
private List<String> emList; //list
private Map<Integer,String> emMap; //map
private Set<Department> emSet; //set
public Employee(String username, Department dept, List<String> emList, Map<Integer, String> emMap, Set<Department> emSet) {
this.username = username;
this.dept = dept;
this.emList = emList;
this.emMap = emMap;
this.emSet = emSet;
}
@Override
public String toString() {
return "Employee{" +
"username='" + username + '\'' +
", dept=" + dept +
", emList=" + emList +
", emMap=" + emMap +
", emSet=" + emSet +
'}';
}
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--指定初始化方法和销毁方法,使用setter方法注入-->
<!--初始化方法,生成容器就会启动-->
<bean id="dept0" class="cn.edu.scnu.pojo.Department" init-method="init" destroy-method="destroy">
<property name="dept_name" value="张三"/>
</bean>
<bean id="dept1" class="cn.edu.scnu.pojo.Department" init-method="init" destroy-method="destroy">
<property name="dept_name" value="张si"/>
</bean>
<bean id="dept2" class="cn.edu.scnu.pojo.Department" init-method="init" destroy-method="destroy">
<property name="dept_name" value="张三"/>
</bean>
<!--
private String username; //常量
private Department dept; //Bean
private List<String> emList; //list
private Map<Integer,String> emMap; //map
private Set<Department> emSet; //set
-->
<bean id="employee" class="cn.edu.scnu.pojo.Employee">
<constructor-arg index="0" value="里斯"/>
<constructor-arg index="1" ref="dept1"/>
<constructor-arg index="2">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</constructor-arg>
<constructor-arg index="3">
<map>
<entry key="1" value="hello"></entry>
<entry key="2" value="world"/>
</map>
</constructor-arg>
<constructor-arg index="4">
<set>
<ref bean="dept0"/>
<ref bean="dept1"/>
<ref bean="dept2"/>
</set>
</constructor-arg>
</bean>
</beans>
测试
class App3{
public static void main(String[] args) {
//启动容器会初始化Bean,调用初始化方法
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
Department department = (Department) context.getBean("dept0");
System.out.println(department.toString());
Employee employee = (Employee) context.getBean("employee");
System.out.println(employee.toString());
((ClassPathXmlApplicationContext) context).close();//容器关闭会调用销毁方法
}
}
测试结果
初始化方法
初始化方法
初始化方法
Department{dept_name='张三'}
五月 10, 2018 1:37:32 下午 org.springframework.context.support.ClassPathXmlApplicationContext doClose
Employee{username='里斯', dept=Department{dept_name='张si'}, emList=[aaa, bbb, ccc], emMap={1=hello, 2=world}, emSet=[Department{dept_name='张三'}, Department{dept_name='张si'}, Department{dept_name='张三'}]}
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@685f4c2e: startup date [Thu May 10 13:37:32 CST 2018]; root of context hierarchy
销毁方法
销毁方法
销毁方法
==基于Annotation的Bean的装配==
Spring中定义的几种Annotation注解
注解 | 应用 |
---|---|
@Component | 表示一个组件,应用于任何层次 |
@Repository | 用于Dao层的组件 |
@Service | 用于Service业务层的组件 |
@Controller | 用于表示层的组件 |
@Autowired | 用于对Bean的自动注入,默认按照类型装配 |
@Resource | 用于对Bean的自动逐日,默认按照Bean的名称装配,有name和type两个属性指定是用名称还是类型 |
@Qualifier | 与@Autowired注解配合。实现通过Bean的名称装配 |
实例
应用在实现类中
@Component("userInfoDao")
@Repository("userInfoDao")
@Service("userInfoService")
@Autowired //默认按照类型装配
@Autowired
@Qualifier("userInfoDao")
@Resource(name="userInfoDao")
@Resource //默认ByName
@Resource(type=UserInfoDaoImpl.class) //通过类型注入
通过类型注入,这个类型必须唯一,否则会出现异常。
Spring AOP
面向切面编程,是一种编程范式,通过对通用模块的分离,允许模块化。
当前流行的AOP框架:spring AOP,AspectJ
通常情况下,日志处理,事务处理,参数验证,等通用模块,进行分离,集中处理。
AOP的术语
术语 | 说明 |
---|---|
切面 | 本质上就是一个普通的java类,比如日志处理类 |
通知 | 是切面的实现,即切面是java类,通知是切面类中的方法 |
切入点 | 指定要拦截的方法 |
目标对象 | 那些被通知的对象,即普通的业务逻辑的对象 |
代理对象 | 通知应用到对象后,被动态创建的对象,即逻辑对象加上切面实现的功能 |
织入 | 将切面应用到目标对象,从而创建一个新的代理对象的过程 |
各种通知
通知 | 定义和应用 |
---|---|
前置通知(before advice) | 在业务方法前执行,不影响业务方法执行。 |
后置通知(after advice) | 在业务方法执行后执行,业务方法异常不会影响通知执行 |
返回通知(after return advice) | 在业务方法执行后执行,业务方法异常会影响通知执行 |
异常通知(after throwing advice) | 在业务方法抛出异常时执行 |
环绕通知(around advice) | 最强大的通知。可以围绕业务方法的前后 |
环绕通知
1. 参数类型:proceedingJoinPoint
2. 调用ProceedingJoinPoint.proceed()方法执行被代理的方法
3. 环绕通知方法需要有返回值。
4. 不推荐使用环绕通知
==五种通知执行顺序:前置通知→环绕通知后置通知→返回通知==
面向切面编程实例
业务类
1. ProductService
package cn.edu.scnu.aop;
public interface ProductService {
public void init();
}
- ProductServiceImpl
package cn.edu.scnu.aop;
public class ProductServiceImpl implements ProductService{
@Override
public void init() {
// System.out.println("这是业务逻辑"+10/0);
System.out.println("这是业务逻辑");
}
}
切面类
1. LogAdvice
package cn.edu.scnu.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import java.util.Arrays;
import java.util.List;
/**
* @author lay
* 这是切面类
*/
public class LogAdvice {
//此方法作为前置通知
public void myBeforeAdvice(JoinPoint joinPoint){
//获取业务方法参数
System.out.println("前置通知");
}
//后置通知方法,后置通知不能访问目标方法执行后,返回的结果
public void myAfterAdvice(JoinPoint joinPoint){
System.out.println("后置通知");
}
//返回通知
//目标方法正常执行结束,才能执行的通知,因此可以接受返回结果
public void myAfterReturnAdvice(JoinPoint joinPoint,Object result){
System.out.println("返回通知");
}
//异常通知
//目标方法异常,才会执行的通知,可以接收返回异常
public void afterThrowAdvice(JoinPoint joinPoint,Exception e){
System.out.println("异常");
// e.printStackTrace();
}
//环绕通知
/*
1. 参数类型:proceedingJoinPoint
2. 调用ProceedingJoinPoint.proceed()方法执行被代理的方法
3. 环绕通知方法需要有返回值。
4. 不推荐使用环绕通知
*/
public Object aroundAdvice(ProceedingJoinPoint joinPoint){
Object result = null;
try{
//前置通知
System.out.println("around前置通知");
result = joinPoint.proceed();
//返回通知
System.out.println("around返回通知");
} catch (Throwable throwable) {
//异常通知
System.out.println("around异常通知");
throwable.printStackTrace();
}
//后置通知
System.out.println("around后置通知");
return result;
}
}
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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置业务Bean,即目标对象-->
<bean id="productService" class="cn.edu.scnu.aop.ProductServiceImpl"></bean>
<!--配置切面Bean-->
<bean id="logAdvice" class="cn.edu.scnu.aop.LogAdvice"></bean>
<!--配置面向切面编程 aop
配置切面,在切面里需要指定:
1. 指定要拦截的方法,即切入点。
2. 指定拦截方式,即配置通知
-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logaop" ref="logAdvice">
<!--配置切入点-->
<aop:pointcut id="logpointcut" expression="execution(* cn.edu.scnu.aop.ProductService.init(..))"/>
<!--设置通知-->
<aop:before method="myBeforeAdvice" pointcut-ref="logpointcut"/>
<aop:after method="myAfterAdvice" pointcut-ref="logpointcut"/>
<aop:after-returning method="myAfterReturnAdvice" pointcut-ref="logpointcut" returning="result"/>
<aop:after-throwing method="afterThrowAdvice" pointcut-ref="logpointcut" throwing="e"/>
</aop:aspect>
</aop:config>
<aop:config>
<!--配置切面-->
<aop:aspect id="logaop" ref="logAdvice">
<!--配置切入点-->
<aop:pointcut id="logpointcut" expression="execution(* cn.edu.scnu.aop.ProductService.init(..))"/>
<!--设置通知-->
<aop:around method="aroundAdvice" pointcut-ref="logpointcut"/>
</aop:aspect>
</aop:config>
</beans>
测试
class App4{
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext3.xml");
ProductService productService = (ProductService) context.getBean("productService");
productService.init();
}
}
基于注解的Aop配置
切面
package cn.edu.scnu.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author lay
* 这是切面类
*/
@Aspect
@Component
public class LogAnnAdvice {
//定义切入点
@Pointcut("execution(* cn.edu.scnu.aop.ProductService.init(..))")
public void init(){}
//此方法作为前置通知
@Before("init()")
public void myBeforeAdvice(JoinPoint joinPoint){
//获取业务方法参数
System.out.println("前置通知");
}
@After("init()")
//后置通知方法,后置通知不能访问目标方法执行后,返回的结果
public void myAfterAdvice(JoinPoint joinPoint){
System.out.println("后置通知");
}
//返回通知
//目标方法正常执行结束,才能执行的通知,因此可以接受返回结果
public void myAfterReturnAdvice(JoinPoint joinPoint,Object result){
System.out.println("返回通知");
}
//异常通知
//目标方法异常,才会执行的通知,可以接收返回异常
@AfterThrowing(value = "init()",throwing = "e")
public void afterThrowAdvice(JoinPoint joinPoint,Exception e){
System.out.println("异常");
// e.printStackTrace();
}
//环绕通知
/*
1. 参数类型:proceedingJoinPoint
2. 调用ProceedingJoinPoint.proceed()方法执行被代理的方法
3. 环绕通知方法需要有返回值。
4. 不推荐使用环绕通知
*/
@Around("init()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint){
Object result = null;
try{
//前置通知
System.out.println("around前置通知");
result = joinPoint.proceed();
//返回通知
System.out.println("around返回通知");
} catch (Throwable throwable) {
//异常通知
System.out.println("around异常通知");
throwable.printStackTrace();
}
//后置通知
System.out.println("around后置通知");
return result;
}
}
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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置包自动扫描-->
<context:component-scan base-package="cn.edu.scnu.aop"/>
<!--开启AspectJ切面注解处理器-->
<aop:aspectj-autoproxy/>
</beans>
测试
class App5{
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext4.xml");
ProductService productService = (ProductService) context.getBean("productService");
productService.init();
}
}