一、Spring简介
1、Spring是什么
Spring是分层的Java SE/EE应用full-stack轻量级开源框架,以IoC(Incerse Of Control:反转控制)和AOP(Aspect OrientedProgramming:面向切面编程)为内核。
提供了[展现层SpringMVC]和[持久层Spring JDBCTemplate]以及[业务层事务管理]等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,组件成为使用最多的JavaEE企业应用开源框架。
2、Spring的体系结构
二、Spring入门
1、Spring程序开发步骤
① 导入Spring开发的基本包坐标
② 编写Dao接口和实现类
③ 创建Spring核心配置文件
④ 在Spring配置文件中配置UserDaoImpl
⑤ 使用Spring的API获得Bean实例
示例如下:
① 在pom.xml中导入Spring坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
② 创建Bean(Dao接口和实现类)
UserDao接口如下:
public interface UserDao {
public void save();
}
UserDaoImpl实现类如下:
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("save running...");
}
}
③、④ 创建Spring的核心配置文件applicationContext.xml,根据id标识类
<bean id="userDao" class="com.chu.dao.impl.UserDaoImpl"></bean>
⑤ 使用Spring的API创建ApplicationContext对象getBean获取实例对象。
public class UserDaoDemo {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) app.getBean("userDao");
userDao.save();
}
}
三、Spring的配置文件applicationContext.xml
1、Bean标签的基本配置
用于配置对象交由Spring来创建。
默认情况下它调用的是类中的无参构造器,如果没有无参构造器则不能创建成功。
基本属性:
id:Bean实例在Spring容器中的唯一标识
class:Bean的全限定名称
2、Bean标签范围配置
scope:指对象的作用范围,取值如下:
singleton:创建Spring容器(即加载配置文件)时,就调用无参构造器创建Bean对象。
prototype:创建Spring容器(即加载配置文件)时,不再创建对象,而是在每次getBean时创建Bean对象。
总结:
3、Bean生命周期配置
UserDaoImpl中加入init()初始化方法、destroy()销毁方法:
public void init(){
System.out.println("初始化方法");
}
public void destroy(){
System.out.println("销毁方法");
}
applicationContext.xml配置文件中指定方法:
<bean id="userDao" class="com.chu.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>
4、Bean实例化三种方式
① 无参构造方法实例化:
<bean id="userDao" class="com.chu.dao.impl.UserDaoImpl"></bean>
② 工厂静态方法实例化,直接调用静态工厂的方法
创建静态工厂返回userDao对象:
public class StaticFactory {
public static UserDao getUserDao(){
return new UserDaoImpl();
}
}
修改配置文件:
<!--改变全类名到工厂类,默认是调用工厂的无参构造器,但factory-method可以指明调用的方法-->
<bean id="userDao" class="com.chu.factory.StaticFactory" factory-method="getUserDao"></bean>
③ 工厂实例方法实例化,先创建工厂实例对象,再调用工厂方法
创建工厂实例返回userDao对象:
public class DynamicFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
配置文件:
<!--先用容器创建工厂实例对象-->
<bean id="factory" class="com.chu.factory.DynamicFactory"></bean>
<!--再用工厂实例对象创建userDao对象,并指定调用的方法-->
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean>
5、Bean的依赖注入分析
解决方法:
6、Bean的依赖注入概念和注入方式
依赖注入(Dependency Injection):它是Spring框架核心IOC的具体实现。
在编写程序时,通过控制反转,把对象的创建交给Spring,但是代码中不可能出现没有依赖的情况。IOC解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。
这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了。简单地说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
7、Bean的依赖注入方式:
例如:怎么把UserDao注入到UserService内部?
1) set方法(常用):
① 普通set注入
给UserServiceImpl赋userDao属性值,并加入set方法:
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save(){
userDao.save();
}
}
配置文件中把userDao对象配置给userServiceImpl类:
name的值是userServiceImpl的属性名(也是set方法名set后的第一个字母小写),ref的值是当前xml文件容器中的bean对象id。
<bean id="userDao" class="com.chu.dao.impl.UserDaoImpl"></bean>
<!--name是set方法名set后的第一个字母小写-->
<bean id="userService" class="com.chu.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
② P命名空间注入
P命名空间注入本质也是set方法注入,但比起上述set方法注入更加方便,主要体现在配置文件中,如下:
<!--此行代码要写入最上方的beans中-->
<beans xmlns:p="http://www.springframework.org/schema/p">
<bean id="userDao" class="com.chu.dao.impl.UserDaoImpl"></bean>
<!--name是set方法名set后的第一个字母小写-->
<!--<bean id="userService" class="com.chu.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>-->
<!--使用P命名空间注入-->
<bean id="userService" class="com.chu.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
2) 构造器:
给UserServiceImpl赋userDao属性值,并加入到有参构造器中:
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 有参构造器
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
// 无参构造器
public UserServiceImpl() {
}
@Override
public void save(){
userDao.save();
}
}
配置文件中把userDao对象配置给userServiceImpl类:
name的值是userServiceImpl有参构造器中的参数名,ref的值是当前xml文件容器中的bean对象id。
<bean id="userDao" class="com.chu.dao.impl.UserDaoImpl"></bean>
<!--name是有参构造器中的参数名,ref是当前容器中bean对象的id-->
<bean id="userService" class="com.chu.service.impl.UserServiceImpl">
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
8、Bean的依赖注入的数据类型
上面的操作,都是注入的引用类型Bean,除了对象的引用可以注入,普通数据类型、集合等也都可以在容器中进行注入。
注入数据的三种数据类型:
普通数据类型
引用数据类型
集合数据类型
普通数据类型注入:
加入普通数据name、age,并运用set注入方式注入值:
public class UserDaoImpl implements UserDao {
private String username;
private int age;
public void setUsername(String username) {
this.username = username;
}
public void setAge(int age) {
this.age = age;
}
//重写无参构造器
public UserDaoImpl() {
System.out.println("UserDaoImpl创建...");
}
@Override
public void save() {
System.out.println(username+"今年"+age+"岁了!");
System.out.println("save running...");
}
}
配置文件中,使用property标签,name代表属性名,value代表普通数据值:
<bean id="userDao" class="com.chu.dao.impl.UserDaoImpl">
<property name="username" value="zhangsan"/>
<property name="age" value="18"/>
</bean>
集合类型注入:
加入集合数据List、Map(其中值是user引用类型,要现在容器中创建Bean)、Properties并运用set注入方式注入值:
public class UserDaoImpl implements UserDao {
private List<String> stringList;
private Map<String, User> userMap;
private Properties properties;
public void setStringList(List<String> stringList) {
this.stringList = stringList;
}
public void setUserMap(Map<String, User> userMap) {
this.userMap = userMap;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
private String username;
private int age;
public void setUsername(String username) {
this.username = username;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void save() {
//System.out.println(username+"今年"+age+"岁了!");
System.out.println(stringList);
System.out.println(userMap);
System.out.println(properties);
System.out.println("save running...");
}
}
配置文件中,使用property标签,name代表属性名,value代表普通数据值不能再使用,采用list、map、props等标签:
注:
要注意List中vlaue值、Map中key值、value值是否是引用类型Bean,如果是就需要在容器中创建。
<bean id="userDao" class="com.chu.dao.impl.UserDaoImpl">
<property name="stringList" >
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
<property name="userMap">
<map>
<entry key="u1" value-ref="user1"></entry>
<entry key="u1" value-ref="user2"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="p1">ppp1</prop>
<prop key="p2">ppp2</prop>
<prop key="p3">ppp3</prop>
</props>
</property>
</bean>
<bean id="user1" class="com.chu.domain.User">
<property name="name" value="tom"/>
<property name="addr" value="beijing"/>
</bean>
<bean id="user2" class="com.chu.domain.User">
<property name="name" value="lucy"/>
<property name="addr" value="henan"/>
</bean>
9、引入其他配置文件(分模块开发)
实际开发中,Spring的配置内容非常多,就导致Spring配置很复杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载。
<import resource="applicationContext-xxx.xml" />
在applicationContext.xml文件中引入以下两个文件:
<import resource="applicationContext_user.xml"/>
<import resource="applicationContext_product.xml"/>
加载主文件时,份文件的内容也会被加载。
10、总结
四、Spring相关API
1、ApplicationContext的继承体系
紫色是接口,浅绿是抽象类,深绿是实现类。
2、ApplicationContext的实现类
3、getBean()方法的使用
第一种是根据传入的id(允许存在多个类型相同的bean),第二种根据传入的字节码对象类型(例如:UserService.class)。
4、知识要点
五、Spring:配置数据源、注解开发、继承Junit
1、Spring配置数据源
1)数据源(连接池)的作用
① 数据源(连接池)是提高程序性能出现的
② 事先实例化数据源,初始化部分连接资源
③ 使用连接资源时从数据源中获取
④ 使用完毕之后将连接资源归还给数据源
常见的数据源(连接池):DBCP、C3P0、BoneCP、Druid等。
2)数据源的手动创建
public class DataSourceTest {
@Test
//测试手动创建c3p0连接池
public void test1() throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true");
dataSource.setUser("root");
dataSource.setPassword("root");
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
@Test
//测试手动创建druid连接池
public void test2() throws Exception{
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true");
dataSource.setUsername("root");
dataSource.setPassword("root");
DruidPooledConnection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
3)抽取数据源jdbc.properties配置文件
jdbc.properties配置如下:
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true
username=root
password=root
driverClassName=com.mysql.cj.jdbc.Driver
测试如下:
@Test
//测试手动创建c3p0连接池(加载properties配置文件)
public void test3() throws Exception{
//读取配置文件 ResourceBundle.getBundle访问本地资源
ResourceBundle rb = ResourceBundle.getBundle("jdbc");
String driver = rb.getString("driverClassName");
String url = rb.getString("url");
String username = rb.getString("username");
String password = rb.getString("password");
//创建连接池对象,设置连接参数
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
4)Spring配置数据源
可以将DataSource的创建权交由Spring容器去完成。
首先在pom.xml中引入spring依赖(因为在测试中使用,故使用周期设为test):
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
<scope>test</scope>
</dependency>
再引入配置文件:
注:
name的值是找本身要创建的set方法后面的那一块,
且注意在xml中&需要使用&来替代。
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--name的值是找本身要创建的set方法后面的那一块,
且注意在xml中&需要使用&来替代-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
最后测试Spring容器创建连接池对象,配置文件写在applicationContext.xml文件中:
@Test
//测试Spring容器创建连接池对象 配置文件写入applicationContext.xml文件中
public void test4() throws Exception{
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
// 只需要一个同类型的bean可以使用
DataSource dataSource = app.getBean(DataSource.class);
// 如果不是就需要使用id方式获取
// DataSource dataSource1 = (DataSource) app.getBean("dataSource");
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
5)Spring配置数据源,抽取jdbc配置文件(使得通用)
jdbc.properties配置文件:
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true
username=root
password=4680
driverClassName=com.mysql.jdbc.Driver
applicationContext.xml配置文件(去获取jdbc.properties中的配置文件): 存在问题-xml读取配置文件时url时存在&会报错- - -未解决!
<?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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--加载外部的properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--name的值是找本身要创建的set方法后面的那一块,
且注意在xml中&需要使用&来替代-->
<property name="driverClass" value="${driverClassName}"></property>
<property name="jdbcUrl" value="${url}"></property>
<property name="user" value="${username}"></property>
<property name="password" value="${password}"></property>
</bean>
</beans>
最后测试Spring容器创建连接池对象,配置文件写在applicationContext.xml文件中:
@Test
//测试Spring容器创建连接池对象 配置文件写入applicationContext.xml文件中
public void test4() throws Exception{
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
// 只需要一个同类型的bean可以使用
DataSource dataSource = app.getBean(DataSource.class);
// 如果不是就需要使用id方式获取
// DataSource dataSource1 = (DataSource) app.getBean("dataSource");
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
2、Spring注解开发
Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。
1)Spring原始注解
Spring原始注解主要是替代<Bean>的配置。
之前的简单例子:
Dao层save方法,Service去调用,其中在配置文件中实现了将userDao注入到userService中。
其中原先配置文件如下所示:
<bean id="userDao" class="com.chu.dao.impl.UserDaoImpl"></bean>
<!--注入userDao到userService中-->
<bean id="userService" class="com.chu.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
使用原始注解对配置文件进行修改:
UserDaoImpl使用注解如下:
// <bean id="userDao" class="com.chu.dao.impl.UserDaoImpl"></bean>
// 在类上实例化Bean,若要id就加入字符串
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("save running...");
}
}
UserServiceImpl使用注解如下:
注:当使用xml文件创建Bean时,需要属性有set方法,但如果使用注解则可以不写set方法
使用@Resource注解(相当于@Qualifier+@Autowired),但可能出现爆红的现象,此时要在pom.xml配置文件中加入依赖:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
//<bean id="userService" class="com.chu.service.impl.UserServiceImpl">
// 同UserDaoImpl
@Service("userService")
@Scope("singleton") //创建一个Bean,需要多个则使用@Scope("prototype")
public class UserServiceImpl implements UserService {
//注入普通数据
@Value("${driverClassName}") //去容器中找对应的key,把key的值赋给driver
private String driver;
//把userDao注入到userService中
// <property name="userDao" ref="userDao"></property>
//@Autowired //在属性上用于根据类型依赖注入(只需要此类型的一个Bean的时候,就可以只使用此行)
//@Qualifier("userDao") // 结合@Autowired 根据名称依赖注入
@Resource(name="userDao")
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
@PostConstruct // 在构造器执行之后
public void init(){
System.out.println("Service对象的初始化方法");
}
@PreDestroy // 在销毁之前
public void destroy(){
System.out.println("Service对象的销毁方法");
}
}
但要注意:
要在applicationContext.xml配置文件中配置组件扫描。
<!--配置组件扫描-->
<context:component-scan base-package="com.chu"/>
2)Spring新注解
原始注解不能全部替代xml配置文件,还需要使用注解替代的配置如下:
新注解如下:
SpringCOnfiguration代码如下: 其中有加载分支配置到此核心配置中
// 标志该类是Spring的核心配置类
@Configuration
// 组件扫描 <context:component-scan base-package="com.chu"/>
@ComponentScan("com.chu")
// 加载分的配置加载到核心配置中<import resource=""/>
@Import({DataSourceConfiguration.class})
public class SpringConfiguration {
}
分支配置代码如下:
// 加载配置文件 <context:property-placeholder location="classpath:jdbc.properties"/>
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfiguration {
@Value("${driverClassName}")
private String driver;
@Value("${url}")
private String url;
@Value("${username}")
private String username;
@Value("${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(username);
dataSource.setPassword(password);
return dataSource;
}
}
测试如下:容器的创建就不能使用之前读取配置文件的做法。
@Test
//测试Spring容器创建连接池对象 使用新注解方式
public void test5() throws Exception{
//ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfiguration.class);
// 只需要一个同类型的bean可以使用
DataSource dataSource = app.getBean(DataSource.class);
// 如果不是就需要使用id方式获取
// DataSource dataSource1 = (DataSource) app.getBean("dataSource");
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
3、Spring整合Junit
1)原始Junit测试Spring的问题
2)上述问题解决思路
让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它;将需要进行测试Bean直接在测试类中进行注入。
3)Spring继承Junit步骤
① 导入spring继承Junit的坐标:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
②、③、④、⑤:
@RunWith(SpringJUnit4ClassRunner.class) //指定谁去跑
// 使用配置文件@ContextConfiguration("classpath:applicationContext.xml") //指定文件
@ContextConfiguration(classes = {SpringConfiguration.class}) //使用全注解方式
public class SpringJunitTest {
@Autowired
private UserService userService;
@Test
public void test1() throws Exception{
userService.save();
}
}
六、Spring的AOP
1、Spring的AOP简介
AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
1.1、AOP的作用及其优势
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。
优势:减少重复代码,提高开发效率,并且便于维护。
举例:
1.2、AOP的底层实现
AOP底层是通过Spring提供的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,再去调用目标对象的方法,从而完成功能的增强。
1.3、AOP的动态代理技术
常用的动态代理技术:
JDK代理:基于接口的动态代理技术
cglib代理:基于父类的动态代理技术
1)JDK动态代理的简单实现例子: 目标对象与代理对象是兄弟关系
目标对象:
public class Target implements TargetInterface{
@Override
public void save() {
System.out.println("save running...");
}
}
目标接口:
public interface TargetInterface {
public void save();
}
功能增强:
public class Advice {
public void before(){
System.out.println("前置增强...");
}
public void afterReturning(){
System.out.println("后置增强...");
}
}
代理对象:
public class ProxyTest {
public static void main(String[] args) {
//创建目标对象
final Target target = new Target();
//增强对象
final Advice advice = new Advice();
//返回值就是动态生成的代理对象 代理对象和目标对象是兄弟关系,只能用接口类型
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(), //目标对象类加载器
target.getClass().getInterfaces(), //目标对象相同的接口字节码对象数组
new InvocationHandler() {
@Override
//调用代理对象的任何方法,实质执行的都是invoke方法
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();
}
}
2)cglib动态代理的简单实现例子: 已经集成到Spring中了,不用再额外导包。目标对象与代理对象是父子关系
目标对象:
public class Target {
@Override
public void save() {
System.out.println("save running...");
}
}
功能增强:
public class Advice {
public void before(){
System.out.println("前置增强...");
}
public void afterReturning(){
System.out.println("后置增强...");
}
}
代理对象:
public class ProxyTest {
public static void main(String[] args) {
//创建目标对象
final Target target = new Target();
//增强对象
final 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 null;
}
});
//4、创建代理对象 代理对象与目标对象是父子关系,可以直接用
Target proxy = (Target) enhancer.create();
proxy.save();
}
}
1.4、AOP的相关概念
Target是目标对象;
Proxy是代理对象;
Joinpoint是连接点(即:可以被增强的方法);
Pointcut是切入点(即:真正被增强的方法);
Advice是通知/增强(即:去增强要被增强的方法的方法);
Aspect是切面(即:切入点和增强的结合);
Weaving是织入(即:切入点和增强结合的过程)
1.5、AOP开发明确的事项
1)需要编写的内容
① 编写核心业务代码(目标类的目标方法-切入点)
② 编写切面类,切面类中有通知(增强功能方法)
③ 在配置文件中,配置织入关系,即(将哪些通知与哪些连接点进行结合)
2)AOP技术实现的内容
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
3)AOP底层使用哪种代理方式
在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
1.6、知识要点总结
2、基于XML的AOP开发
2.1 快速入门
① 导入坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
② 创建目标接口和目标类
public interface TargetInterface {
public void save();
}
public class Target implements TargetInterface {
@Override
public void save() {
System.out.println("save running...");
}
}
③ 创建切面类
public class MyAspect {
public void before(){
System.out.println("前置增强...");
}
}
④ 目标类和切面类的对象创建权交给spring
⑤ 配置织入关系
增加:
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置目标对象-->
<bean id="target" class="com.chu.aop.Target"></bean>
<!--配置切面对象 此时只是个普通的Bean-->
<bean id="myAspect" class="com.chu.aop.MyAspect"></bean>
<!--配置织入:告诉spring哪些方法(切点)需要进行哪些增强(前置、后置...)-->
<aop:config>
<!--声明切面是谁-->
<aop:aspect ref="myAspect">
<!--切面=切点+通知(增强) 选择前置增强
method代表增强方法,pointcut代表切点表达式-->
<aop:before method="before" pointcut="execution(public void com.chu.aop.Target.save())"/>
</aop:aspect>
</aop:config>
⑥ 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
2.2 XML配置AOP详解
1)切点表达式的写法:
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
① 访问修饰符可以省略;
② 返回值类型、包名、类名、方法名可以使用星号*代表任意。
③ 包名与类名之间一个点.代表当前包下的类,两个点..表示当前包及其子包下的类
④ 参数列表可以使用两个点..表示任意个数,任意类型的参数列表
例如:
2)通知(增强)的类型:
通知的配置语法:
<aop:通知类型 method="切面类中方法名" pointcut="切点表达式"></aop:通知类型>
切面类中的方法:
public class MyAspect {
public void before(){
System.out.println("前置增强...");
}
public void afterRunning(){
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("最终增强...");
}
}
配置织入关系
增加:
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置目标对象-->
<bean id="target" class="com.chu.aop.Target"></bean>
<!--配置切面对象 此时只是个普通的Bean-->
<bean id="myAspect" class="com.chu.aop.MyAspect"></bean>
<!--配置织入:告诉spring哪些方法(切点)需要进行哪些增强(前置、后置...)-->
<aop:config>
<!--声明切面是谁-->
<aop:aspect ref="myAspect">
<!--切面=切点+通知(增强) 选择前置增强
method代表增强方法,pointcut代表切点表达式-->
<!--<aop:before method="before" pointcut="execution(public void com.chu.aop.Target.save())"/>-->
<aop:before method="before" pointcut="execution(* com.chu.aop.*.*(..))"></aop:before>
<aop:after-returning method="afterRunning" pointcut="execution(* com.chu.aop.*.*(..))"></aop:after-returning>
<aop:around method="around" pointcut="execution(* com.chu.aop.*.*(..))"></aop:around>
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.chu.aop.*.*(..))"></aop:after-throwing>
<aop:after method="after" pointcut="execution(* com.chu.aop.*.*(..))"></aop:after>
</aop:aspect>
</aop:config>
3)切点表达式的抽取:
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用pointcut-ref属性代替pointcut属性来引用抽取后的切点表达式。
<!--配置目标对象-->
<bean id="target" class="com.chu.aop.Target"></bean>
<!--配置切面对象-->
<bean id="myAspect" class="com.chu.aop.MyAspect"></bean>
<!--配置织入:告诉spring哪些方法(切点)需要进行哪些增强(前置、后置...)-->
<aop:config>
<!--声明切面是谁-->
<aop:aspect ref="myAspect">
<!--切面=切点+通知(增强)选择前置增强
method代表增强方法,pointcut代表切点表达式-->
<!--<aop:before method="before" pointcut="execution(public void com.chu.aop.Target.save())"/>-->
<!--抽取切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(* com.chu.aop.*.*(..))"/>
<aop:before method="before" pointcut-ref="myPointcut"></aop:before>
<aop:after-returning method="afterRunning" pointcut-ref="myPointcut"></aop:after-returning>
<aop:around method="around" pointcut-ref="myPointcut"></aop:around>
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"></aop:after-throwing>
<aop:after method="after" pointcut-ref="myPointcut"></aop:after>
</aop:aspect>
</aop:config>
2.3 知识要点
3、基于注解的AOP开发
3.1 快速入门
① 创建目标接口和目标类
public interface TargetInterface {
public void save();
}
public class Target implements TargetInterface {
@Override
public void save() {
System.out.println("save running...");
}
}
② 创建切面类
public class MyAspect {
public void before(){
System.out.println("前置增强...");
}
}
③、④使用注解将目标类和切面类的对象创建权交给spring
目标类:
@Component("target")
public class Target implements TargetInterface {
@Override
public void save() {
System.out.println("save running...");
//int i=1/0; 模拟异常
}
}
切面类: 只演示了一个前置增强的织入关系,注意使用 @Aspect 标注此类为切面类。
@Component("myAspect")
@Aspect //标注MyAspect是一个切面类
public class MyAspect {
//配置强制增强
@Before("execution(* com.chu.anno.*.*(..))")
public void before(){
System.out.println("前置增强...");
}
public void afterRunning(){
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("最终增强...");
}
}
⑤ 注意:开启组件扫描、aop自动代理
<!--开启组件扫描-->
<context:component-scan base-package="com.chu.anno"></context:component-scan>
<!--aop自动代理-->
<aop:aspectj-autoproxy/>
⑥ 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext_anno.xml")
public class AnnoTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
3.2 注解配置AOP详解
1)注解通知(增强)的类型
@Component("myAspect")
@Aspect //标注MyAspect是一个切面类
public class MyAspect {
//配置强制增强
//@Before("execution(* com.chu.anno.*.*(..))")
public void before(){
System.out.println("前置增强...");
}
public void afterRunning(){
System.out.println("后置增强...");
}
// 配置环绕增强
//Proceeding JoinPoint:正在执行的连接点==切点
@Around("execution(* com.chu.anno.*.*(..))")
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.chu.anno.*.*(..))")
public void after(){
System.out.println("最终增强...");
}
}
2)切点表达式的抽取
同xml配置aop一样,可以将切点表达式抽取。抽取方式是在定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在增强注解中进行引用。具体如下:
@Component("myAspect")
@Aspect //标注MyAspect是一个切面类
public class MyAspect {
// 配置环绕增强
//Proceeding JoinPoint:正在执行的连接点==切点
//@Around("execution(* com.chu.anno.*.*(..))")
@Around("MyAspect.pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前置增强...");
Object proceed = pjp.proceed(); // 执行切点方法
System.out.println("环绕后置增强...");
return proceed;
}
//配置最终增强
//@After("execution(* com.chu.anno.*.*(..))")
@After("pointcut()")
public void after(){
System.out.println("最终增强...");
}
//定义切点表达式
@Pointcut("execution(* com.chu.anno.*.*(..))")
public void pointcut(){}
}
3.3 知识要点
七、Spring JdbcTemplate
1、JdbcTemplate概述
它是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。spring框架提供了很多的操作模板类。
例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。
1.1 JdbcTemplate开发步骤
spring-tx是操作事务。
① 导入spring-jdbc和spring-tx坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
② 创建数据库表和实体
数据库表:
实体:
public class Account {
private String name;
private double money;
public Account(String name, double money) {
this.name = name;
this.money = money;
}
public Account() {
}
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 +
'}';
}
}
③ 创建JdbcTemplate对象
④ 执行数据库操作
public class JdbcTemplateTest {
@Test
//测试JdbcTemplate开发步骤
public void test1() throws Exception{
//创建数据源(连接池)对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true");
dataSource.setUser("root");
dataSource.setPassword("4680");
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 设置数据源(连接池)对象(知道数据库在哪)
jdbcTemplate.setDataSource(dataSource);
//执行操作
int row = jdbcTemplate.update("insert into account values (?,?)", "tom", 666666);
System.out.println(row);
}
}
1.2 Spring产生JdbcTemplate对象
可以将JdbcTemplate的创建权交给Spring,将数据源DataSource的创建全也交给Spring,在Spring容器内部将数据源DataSource注入到JdbcTemplate模板对象中,配置如下:
将创建连接池的bean注入到jdbcTemplate中:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true"></property>
<property name="user" value="root"></property>
<property name="password" value="4680"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
@Test
//测试JdbcTemplate开发步骤 使用spring创建对象
public void test2() throws Exception{
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
JdbcTemplate jdbcTemplate = app.getBean(JdbcTemplate.class);
//执行操作
int row = jdbcTemplate.update("insert into account values (?,?)", "www",999);
System.out.println(row);
}
1.3 JdbcTemplate的常用操作
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateCRUDTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testUpdate(){
jdbcTemplate.update("update account set money=? where name =?",10000,"tom");
}
@Test
public void testDelete(){
jdbcTemplate.update("delete from account where name =?","tom");
}
@Test
public void testQueryAll(){
List<Account> accountList = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class));
System.out.println(accountList);
}
@Test
public void testQueryOne(){
Account account = jdbcTemplate.queryForObject("select * from account where name=?", new BeanPropertyRowMapper<Account>(Account.class), "www");
System.out.println(account);
}
@Test
public void testQueryCount(){
Long count = jdbcTemplate.queryForObject("select count(*) from account", Long.class);
System.out.println(count);
}
}
1.4 知识要点
2、Spring的事务控制
2.1 编程式事务控制相关对象
1)PlatformTransactionManager:平台事务管理器
2)TransactionDefinition:事务的定义信息对象
① 事务的隔离级别
② 事务传播行为
A调B,B看A…
3)TransactionStatus 提供事务的具体的运行状态
2.2 基于XML的声明式事务控制
1)什么是声明式事务控制
Spring的声明式事务就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在Spring配置文件中声明式的处理事务来代替代码式的处理事务。
声明式事务处理的作用:
① 事务管理不侵入开发组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可。
② 在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便。
注意:Spring声明式事务控制底层就是AOP
2)声明式事务控制的实现
声明式事务控制明确事项:
谁是切点?
谁是增强(通知)?
配置切面?
<?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:tx="http://www.springframework.org/schema/tx"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true"/>
<property name="user" value="root"/>
<property name="password" value="4680"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="accountDao" class="com.chu.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!--目标对象 内部的方法就是切点-->
<bean id="accountService" class="com.chu.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--通知 事务的增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--设置事务的属性信息的-->
<tx:attributes>
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="save" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="findAll" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
<tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置事务的aop织入-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.chu.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
2.3 基于注解的声明式事务控制
1)注解声明式事务控制的实现
AccountDaoImpl使用注解:
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired //把JdbcTemplate注入到accountDao中,使其可以使用
private JdbcTemplate jdbcTemplate;
public void out(String outMan, double money) {
jdbcTemplate.update("update account set money=money-? where name=?",money,outMan);
}
public void in(String inMan, double money) {
jdbcTemplate.update("update account set money=money+? where name=?",money,inMan);
}
}
AccountServiceImpl使用注解:
@Service("accountService")
@Transactional(isolation = Isolation.REPEATABLE_READ)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
//在需要进行事务控制的类或者方法上修饰
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public void transfer(String outMan, String inMan, double money) {
accountDao.out(outMan,money);
//int i = 1/0;
accountDao.in(inMan,money);
}
//@Transactional(isolation = Isolation.DEFAULT)
public void xxx(){}
}
applicationContext.xml文件配置如下:
含有非自定义类的bean创建,以及利用到注解必须配置的组件扫描、利用到事务的事务的注解驱动。
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
<!--组件扫描-->
<context:component-scan base-package="com.chu"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&rewriteBatchedStatements=true"/>
<property name="user" value="root"/>
<property name="password" value="4680"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
2)注解配置声明式事务控制解析
3)知识要点