Spring
Spring简介
Spring是什么
Spring是分层的JavaSE/EE应用full-stack轻量级开源框架,以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核
提供了展现层SpringMVC和持久层Spring JDBCTemplate以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架
Spring的优势
1.方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度耦合
用户也不必再为单例模式类、属性文件解析等这些底层的需求编写代码,可以更专注于上层的应用
2.AOP编程的支持
通过Spring的AOP功能,方便进行面向切面编程,许多不容易用传统OOP实现的功能可以通过AOP轻松实现
3.声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量
4.方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的工作,而是随手可做的事情
5.方便集成各种优秀框架、
Spring对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的支持
6.降低JavaEE API的使用难度
Sping对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的难度大为降低
7.Java源码是经典学习范例
Spring的源代码设计巧妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无疑是Java技术的最佳实践的范例
Spring的体系结构
Spring入门
Spring程序开发步骤
1.导入Spring开发的基本包坐标
2.编写Dao接口和实现类
3.创建Spring核心配置文件
4.在Spring配置文件中配置UserDaoImpl
5.使用Sping的API获得Bean实例
1.导入坐标 2.创建Bean 3.创建applicationContext.xml 4.在配置文件中进行配置 5.创建applicationContext对象getBean
Spring配置文件
Bean标签基本配置
用于配置对象交由Spring来创建
默认情况下他调用的是类中的无参数构造,如果没有无参构造函数则不能创建成功
基本属性:
id:Bean实例在Spring容器中的唯一标识
class:Bean的全限定名称
Bean标签范围配置
当scope的取值为singleton时
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
Bean的生命周期:
- 对象创建:当应用加载,创建容器时,对象就被创建了
- 对象运行:只要容器在,对象一直活着
- 对象销毁:当应用卸载,销毁容器时,对象就被销毁了
当scope的取值为prototype时
Bean的实例化个数:多个
Bean的实例化的时机:当调用getBean()方法时实例化Bean
- 对象创建:当使用对象时,创建新的对象实例
- 对象运行:只要对象在使用中,就一直活着
- 对象销毁:当对象长时间不用时,被Java垃圾回收器回收
Bean生命周期配置
- init-method:指定类中的初始化方法名称
- destroy-method:指定类中销毁方法名称
Bean实例化三种方式
- 无参构造方法实例化
- 工厂静态方法实例化
- 工厂实例方法实例化
Spring的依赖注入分析
目前UserService实例和UserDao实例都存在于Spring容器中,当前的做法是在容器外部获得UserService实例和UserDao实例,然后在程序中进行结合
因为UserService和UserDao都在Spring容器中,而最终程序直接使用的是UserService,所以可以在Spring容器中将UserDao设置到UserService内部
Bean的依赖注入概念
依赖注入:它是Spring框架核心IOC的具体实现
怎么将UserDao注入到UserService内部?
- 构造方法
- set方法
<bean id="userDao" class="com.cedric.demo.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.cedric.server.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
set方法注入
P命名空间注入本质也是set方法注入,但比起上方的方式要更加方便,主要体现在配置文件中
首先,需要引入P命名空间:
其次,需要修改注入方式
构造方法注入
修改方式
Bean依赖注入的数据类型
以上的操作都是注入引用Bean,除了对象的引用可以注入,普通数据类型,集合等都可以在容器中进行注入
注入数据的三种数据类型
- 普通数据类型
- 引用数据类型
- 集合数据类型
普通数据类型注入
集合数据库类型注入
引入其他配置文件(分模块开发)
实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载
Spring配置数据源
数据源(连接池)的作用
- 数据源(连接池)是提高程序性能出现的
- 事先实例化数据源,初始化部分连接资源
- 使用连接资源时从数据源中获取
- 使用完毕后将连接资源归还给数据源
常见的数据源(连接池):DBCP、C3P0、BoneCP、Druid等
数据源的开发步骤
1.导入数据源的坐标和数据库驱动坐标
2.创建数据源对象
3.设置数据源的基本连接数据
4.使用数据源获取连接资源和归还连接资源
手动创建c3p0数据源
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
@Test
// 测试手动创建c3p0数据源
public void test1() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306");
dataSource.setUser("root");
dataSource.setPassword("cedric1111");
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
手动创建Druid数据源
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
// 测试手动创建druid数据源
public void test2() throws Exception{
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306");
dataSource.setUsername("root");
dataSource.setPassword("cedric1111");
DruidPooledConnection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
抽取properties文件
@Test
// 测试手动创建c3p0数据源(加载properties配置文件)
public void test3() throws Exception {
// 读取配置文件
ResourceBundle resourceBundle = ResourceBundle.getBundle("jdbc");
String driver = resourceBundle.getString("jdbc.driver");
String url = resourceBundle.getString("jdbc.url");
String username = resourceBundle.getString("jdbc.username");
String password = resourceBundle.getString("jdbc.password");
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(username);
comboPooledDataSource.setPassword(password);
Connection connection = comboPooledDataSource.getConnection();
System.out.println(connection);
connection.close();
}
Spring配置数据源
可以将DataSource的创建权交由Spring容器去完成
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306"></property>
<property name="user" value="root"></property>
<property name="password" value="cedric1010"></property>
</bean>
<bean name="dataSource2" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306"></property>
<property name="username" value="root"></property>
<property name="password" value="cedric1010"></property>
</bean>
@Test
public void test5() throws Exception{
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) app.getBean("dataSource2");
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
@Test
// 测试Spring容器产生数据源对象
public void test4() throws Exception{
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = app.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
抽取jdbc配置文件
applicationContext.xml加载jdbc.properties配置文件获得连接信息
首先需要引入context命名空间和约束路径
- 命名空间: xmlns:context="http://www.springframework.org/schema/context"
- 约束路径: xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
Spring容器加载properties文件
<context:property-placeholder location="classpath:jdbc.properties"/>
<property name="" value="${key}"></property>
Spring注解开发
Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率
Spring原始注解
注意:使用Spring注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段、方法
<!--配置组件扫描-->
<context:component-scan base-package="com.cedric"/>
package com.cedric.dao.impl;
import com.cedric.dao.UserDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
// <bean id="userDao" class="com.cedric.dao.impl.UserDaoImpl"></bean>
//@Component("userDao")
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Value("${jdbc.driver}")
private String driver;
@Override
public void save() {
System.out.println(driver);
System.out.println("save running......");
}
@PostConstruct
public void init(){
System.out.println("Service对象的初始化方法");
}
@PreDestroy
public void destroy(){
System.out.println("Service对象的销毁方法");
}
}
package com.cedric.service.impl;
import com.cedric.dao.UserDao;
import com.cedric.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
// <bean id="userServer" class="com.cedric.service.impl.UserServiceImpl">
// @Component("userServer")
@Service("userServer")
//@Scope("singleton")
@Scope("prototype")
public class UserServiceImpl implements UserService {
// <property name="userDao" ref="userDao"></property>
//@Autowired // 按照数据类型从Spring容器中进行匹配
//@Qualifier("userDao") // 按照id值从容器中进行匹配的 但是此处@Qualifier结合@Autowired一起使用
@Resource(name="userDao") // @Resource相当于@Autowired + @Qualifier
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
}
package com.cedric.web;
import com.cedric.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserController {
public static void main(String[] args) {
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = app.getBean(UserService.class);
userService.save();
app.close();
}
}
Spring新注解
使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置文件有:
- 非自定义的Bean的配置:<bean
- 加载properties文件的配置:context:property-placeholder
- 组件扫描的配置:context:component-scan
- 引入其他文件: import
package com.cedric.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
// 标志该类是Spring的核心配置类
@Configuration()
//<context:component-scan base-package="com.cedric"/>
@ComponentScan("com.cedric")
//<import resource=""/>
@Import(DataSourceConfiguration.class)
public class SpringConfiguration {
}
package com.cedric.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
import java.sql.Connection;
//<context:property-placeholder location="classpath:jdbc.properties"/>
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfiguration {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String user;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource") //Spring会将当前方法的返回值以指定名称存储到Spring容器中
public DataSource getDataSource() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(user);
dataSource.setPassword(password);
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
return dataSource;
}
}
package com.cedric.service.impl;
import com.cedric.dao.UserDao;
import com.cedric.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
// <bean id="userServer" class="com.cedric.service.impl.UserServiceImpl">
// @Component("userServer")
@Service("userServer")
//@Scope("singleton")
@Scope("prototype")
public class UserServiceImpl implements UserService {
// <property name="userDao" ref="userDao"></property>
//@Autowired // 按照数据类型从Spring容器中进行匹配
//@Qualifier("userDao") // 按照id值从容器中进行匹配的 但是此处@Qualifier结合@Autowired一起使用
@Resource(name="userDao") // @Resource相当于@Autowired + @Qualifier
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
}
package com.cedric.web;
import com.cedric.config.SpringConfiguration;
import com.cedric.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class UserController {
public static void main(String[] args) {
//ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfiguration.class);
UserService userService = app.getBean(UserService.class);
userService.save();
}
}
Spring集成Junit
原始Junit测试Spring的问题
在测试类中,每个测试方法都有以下两行代码:
这两行代码的作用是获取容器,如果不写的话直接会提示空指针异常。所以不能轻易删掉
解决思路
- 让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它
- 将需要进行测试Bean直接在测试类中进行注入
Spring继承Junit步骤
1.导入spring集成Junit坐标
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
2.使用@Runwith注解替换原来的运行期
@RunWith(SpringJUnit4ClassRunner.class)
3.使用@ContextConfiguration指定配置文件或配置类
//@ContextConfiguration("classpath:applicationContext.xml")
@ContextConfiguration(classes = {SpringConfiguration.class})
4.使用@Autowired注入需要测试的对象
@Autowired
private UserService userService;
@Autowired
private DataSource dataSource;
5.创建测试方法进行测试
@Test
public void test1() throws SQLException {
userService.save();
dataSource.getConnection();
}
Spring JdbcTemplate基本使用
JdbcTemplate概述
他是Spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。Sping框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等
JdbcTemplate开发步骤
1.导入spring-jdbc和spring-tx坐标
2.创建数据库表和实体
3.创建JdbcTemplate对象
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
4.执行数据库操作
更新操作:
jdbcTemplate.update(sql,params)
查询操作:
jdbcTemplate.query(sql,Mapper,params)
jdbcTemplate.queryForObject(sql,Mapper,params)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
package com.cedric.main;
public class Account {
private String name;
private double money;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"name='" + name + '\'' +
", money=" + money +
'}';
}
}
package com.cedric.test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import java.beans.PropertyVetoException;
public class JdbcTemplateTest {
@Test
public void test2(){
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
JdbcTemplate jdbcTemplate = app.getBean(JdbcTemplate.class);
int row = jdbcTemplate.update("insert into account values (?,?)","wangwu",80000);
System.out.println(row);
}
@Test
// 测试JdbcTemplateTest开发步骤
public void test1() throws PropertyVetoException {
// 创建数据源对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/manager");
dataSource.setUser("root");
dataSource.setPassword("cedric1010");
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 设置数据源对象
jdbcTemplate.setDataSource(dataSource);
// 执行操作
int row = jdbcTemplate.update("insert into account values (?,?)","jack",600.0);
System.out.println(row);
}
}
package com.cedric.test;
import com.cedric.main.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
import java.util.Map;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateCRUDTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testQueryCount(){
Long count = jdbcTemplate.queryForObject("select count(*) from account", Long.class);
System.out.println(count);
}
@Test
public void testQueryOne(){
Account account = jdbcTemplate.queryForObject("select * from account where name=?", new BeanPropertyRowMapper<Account>(Account.class), "jack");
System.out.println(account);
}
@Test
public void testQueryAll(){
List<Account> accountList = jdbcTemplate.query("select * from account",new BeanPropertyRowMapper<Account>(Account.class));
System.out.println(accountList);
}
@Test
public void testUpdate(){
int row = jdbcTemplate.update("update account set money=? where name=?",40000,"jack");
System.out.println(row);
}
@Test
public void testDelete(){
jdbcTemplate.update("delete from account where name=?","wangwu");
}
}
Spring产生JdbcTemplate对象
我们可以将JdbcTemplate的创建权交给Spring,将数据源DataSource的创建权也交给Spring,在Spring容器内部将数据源DataSource注入到JdbcTemplate模板对象中,配置如下:
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
<!--加载jdbc.properties-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--数据源对象 DataSource-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--jdbc模板对象 JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
JdbcTemplate的常用操作
修改操作
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateCRUDTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testUpdate(){
int row = jdbcTemplate.update("update account set money=? where name=?",40000,"jack");
System.out.println(row);
}
**查询操作
@Test
public void testQueryOne(){
Account account = jdbcTemplate.queryForObject("select * from account where name=?", new BeanPropertyRowMapper<Account>(Account.class), "jack");
System.out.println(account);
}
@Test
public void testQueryAll(){
List<Account> accountList = jdbcTemplate.query("select * from account",new BeanPropertyRowMapper<Account>(Account.class));
System.out.println(accountList);
}
删除操作
@Test
public void testDelete(){
jdbcTemplate.update("delete from account where name=?","wangwu");
}
Spring AOP
AOP简介
什么是AOP
AOP为Aspect oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
利用AOP可以对业务逻辑的各个部分进行隔离,降低耦合,提高程序的可重用性,同时提高了开发效率
AOP的作用及其优势
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
优势:减少重复代码,提高开发效率,并且便于维护
AOP的底层实现
实际上,AOP的底层是通过Spring提供的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,再去调用目标对象的方法,从而完成功能的增强
AOP的动态代理技术
常用的动态代理技术
- JDK代理:基于接口的动态代理技术
package com.cedric.proxy.jdk;
public interface TargetInterface {
void save();
}
package com.cedric.proxy.jdk;
public class Target implements TargetInterface{
@Override
public void save() {
System.out.println("save running...");
}
}
package com.cedric.proxy.jdk;
public class Advice {
public void before(){
System.out.println("前置增强...");
}
public void afterReturning(){
System.out.println("后置增强....");
}
}
package com.cedric.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
// 目标对象
final Target target = new Target();
// 增强对象
Advice advice = new Advice();
// 返回值 就是动态生成的代理对象
TargetInterface proxy =(TargetInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标对象类加载器
target.getClass().getInterfaces(), //目标对象相同的接口字节码对象数组
new InvocationHandler() {
// 调用代理对象的任何一个方法 实际执行的都是invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.before(); //前置增强
Object invoke = method.invoke(target,args); //执行目标方法
advice.afterReturning(); //后置增强
return invoke;
}
}
);
// 调用代理对象的方法
proxy.save();
}
}
- cglib代理:基于父类的动态代理技术
package com.cedric.proxy.cglib;
public class Target {
public void save() {
System.out.println("save running...");
}
}
package com.cedric.proxy.cglib;
public class Advice {
public void before(){
System.out.println("前置增强...");
}
public void afterReturning(){
System.out.println("后置增强....");
}
}
package com.cedric.proxy.cglib;
import com.cedric.proxy.jdk.TargetInterface;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
// 目标对象
final Target target = new Target();
// 增强对象
Advice advice = new Advice();
// 返回值 就是动态生成的代理对象 基于cglib
// 1.创建增强器
Enhancer enhancer = new Enhancer();
// 2.设置父类(目标)
enhancer.setSuperclass(Target.class);
// 3.设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
advice.before();//执行前置
Object invoke = method.invoke(target,args);//执行目标
advice.afterReturning();//执行后置
return invoke;
}
});
// 4.创建代理对象
Target proxy =(Target)enhancer.create();
proxy.save();
}
}
AOP相关概念
Spring的AOP实现底层就是对上面的动态代理代码进行了封装,封装后我们只需要对需要关注的部分进行编写,并通过配置的方式完成指定目标的方法增强
相关术语
Target(目标对象):代理的目标对象 Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类 Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spirng中,这些点指的是方法,因为Spring支支持方法类型的连接点 Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义 Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情 Aspect(切面):是切入点和通知(引介)的结合 Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。Spirng采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
AOP开发的明确事项
需要编写的内容
- 编写业务核心代码(目标类的目标方法)
- 编写切面类,切面类中有通知(增强功能方法)
- 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
AOP底层使用的代理方式
在Spring中,框架会根据目标类是否实现了接口来决定发采用哪种动态代理的方式
基于XML的AOP开发
1.导入AOP相关坐标
2.创建目标接口和目标类
3.创建切面类(内部有增强方法)
4.将目标类和切面类的对象创建权交给spring
5.在applicationContext.xml中配置织入关系
6.测试代码
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>
<!--目标对象-->
<bean id="target" class="com.cedric.aop.Target"></bean>
<!--切面对象-->
<bean id="myAspect" class="com.cedric.aop.MyAspect"></bean>
<!--配置织入:告诉spring框架 哪些方法(切点)需要进行哪些增强(前置、后置...)-->
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<!--切面:切点+通知-->
<aop:before method="before" pointcut="execution(public void com.cedric.aop.Target.save())"/>
<aop:before method="before" pointcut="execution(* com.cedric.aop.*.*(..))"/>
</aop:aspect>
</aop:config>
测试
public interface TargetInterface {
void save();
}
public class Target implements TargetInterface{
@Override
public void save() {
System.out.println("save running.....");
}
public void before(){
System.out.println("前置增强........");
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
XML配置AOP详解
切点表达式写法
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用*代表任意
- 包名与类名之间的一个.代表当前包下的类,两个点..表示当前包及其子包下的类
- 参数列表可以使用两个点..表示任意个数,任意类型的参数列表
例如:
execution(void com.cedric.aop.Target.*(..)) execution(* com.cedric.aop.*.*(..)) execution(* com.cedric.aop..*.*(..))
通知的类型
通知的配置语法:
< aop:通知类型 method="切面类中方法名" pointcut="切面表达式"></aop:通知类型>
package com.cedric.aop;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
public void before(){
System.out.println("前置增强........");
}
public void afterReturning(){
System.out.println("后置增强.....");
}
//Proceeding JoinPoint:正在执行的连接点 == 切点
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强....");
Object proceed = pjp.proceed(); // 切点方法
System.out.println("环绕后增强....");
return proceed;
}
public void afterThrowing(){
System.out.println("异常抛出增强......");
}
public void after(){
System.out.println("最终异常.........");
}
}
<!--配置织入:告诉spring框架 哪些方法(切点)需要进行哪些增强(前置、后置...)-->
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<!--切面:切点+通知-->
<aop:before method="before" pointcut="execution(public void com.cedric.aop.Target.save())"/>
<aop:before method="before" pointcut="execution(* com.cedric.aop.*.*(..))"/>
<aop:after-returning method="afterReturning" pointcut="execution(* com.cedric.aop.*.*(..))"/>
<aop:around method="around" pointcut="execution(* com.cedric.aop.*.*(..)))"/>
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.cedric.aop.*.*(..)))"/>
<aop:after method="after" pointcut="execution(* com.cedric.aop.*.*(..)))"/>
<aop:around method="around" pointcut-ref="myPointcut"/>
<aop:after method="after" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
切点表达式的抽取
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用pointcut-ref属性代替pointccut属性来引用抽取后的切点表达式
<!--抽取切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(* com.cedric.aop.*.*(..)))"></aop:pointcut>
基于注解的AOP开发
基于注解的AOP开发步骤:
1.创建目标接口和目标类(内部有切点)
2.创建切面类(内部有增强方法)
3.将目标类和切面类的对象创建权交给spring
4.在切面类中使用注解配置织入关系
5.在配置文件中开启组件扫描和AOP的自动代理
6.测试
package com.cedric.anno;
import org.springframework.stereotype.Component;
@Component("target")
public class Target implements TargetInterface {
@Override
public void save() {
System.out.println("save running.....");
//int i = 1 / 0;
}
}
package com.cedric.anno;
public interface TargetInterface {
void save();
}
package com.cedric.anno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component("myAspect")
@Aspect //标注当前MyAspect是一个切面类
public class MyAspect {
//配置前置通知
@Before("execution(* com.cedric.anno.*.*(..))")
public void before(){
System.out.println("前置增强........");
}
public void afterReturning(){
System.out.println("后置增强.....");
}
// @Around("execution(* com.cedric.anno.*.*(..))")
//Proceeding JoinPoint:正在执行的连接点 == 切点
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强....");
Object proceed = pjp.proceed(); // 切点方法
System.out.println("环绕后增强....");
return proceed;
}
public void afterThrowing(){
System.out.println("异常抛出增强......");
}
@After("execution(* com.cedric.anno.*.*(..))")
public void after(){
System.out.println("最终异常.........");
}
@Pointcut("execution(* com.cedric.anno.*.*(..))")
public void pointcut(){}
}
package com.cedric.test;
import com.cedric.anno.TargetInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-anno.xml")
public class AnnoTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
<!--组件扫描-->
<context:component-scan base-package="com.cedric.anno"/>
<!--aop自动代理-->
<aop:aspectj-autoproxy/>
注解通知类型
Spring事务控制
编程式事务控制相关对象
PlatformTransactionManager
PlatformTransactionManager接口时spring的事务管理器,它里面提供了我们常用的操作事务的方法
TransactionDefinition
TransactionDefinition是事务的定义信息对象,里面有如下方法:
事务隔离级别
TransactionStatus
TransactionStatus接口提供的是事务具体的运行状态,方法介绍如下
基于XML的声明式事务控制
Spring的声明式事务顾名思义就是采用声明的方式来处理事务,这里所说的声明,就是指在配置文件中声明,用在Spring配置文件中声明的处理事务来代替代码式的处理事务
事务传播行为