9、代理模式
- 给某一个对象提供一个代理,需要使用时再由代理对象控制对原对象的引用。
- 代理模式建议新建一个与原服务对象接口相同的代理类, 然后更新应用以将代理对象传递给所有原始对象客户端。代理类接收到客户端请求后会创建实际的服务对象, 并将所有工作委派给它。
9.1、静态代理
1、角色分析:
- 真实角色:实现方法的类
- 抽象角色:包含真实角色方法的抽象类
- 代理角色:代理真实角色,增加附属操作
- 客户:访问代理对象
2、使用静态代理
//真实角色
public class Host implements Rent {
public void rent() {
System.out.println("出租房子");
}
}
//抽象角色
public interface Rent {
void rent();
}
//代理角色
@Data
@AllArgsConstructor
public class Proxy {
private Host host;
public void rent(){
host.rent();
}
public void lookHouse(){
System.out.println("看房");
}
}
//客户
public class Client {
public static void main(String[] args) {
Host host = new Host();
Proxy proxy = new Proxy(host);
proxy.lookHouse();
proxy.rent();
}
}
3、作用
- 真实角色不需要进行额外操作,公共业务可以交由代理角色实现。
- 集中公共业务修改。
9.2、动态代理
- 静态代理不灵活一旦确定了代码,如果委托类新增一个方法,而这个方法又需要增强,那么就必须在代理类里重写一个带增强的方法。
- JDK动态接口代理,动态代理可以灵活替换代理方法,需要使用这个对象时再由代理类利用反射动态创建。
- 动态就是在程序运行时生成的,而不是编译时。
- Proxy:提供创建动态代理类(生成动态代理实例)和实例的静态方法。
- InvocationHandler:调用处理程序并返回结果
1、创建 InvocationHandler 处理程序
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
public Object getProxy() {
//动态代理的本质是反射
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}
//处理代理实例并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
lookHouse();
Object result = method.invoke(rent, args);
return result;
}
public void lookHouse() {
System.out.println("看房");
}
}
2、使用动态代理
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理角色
ProxyInvocationHandler handler = new ProxyInvocationHandler();
//调用处理程序处理接口对象
handler.setRent(host);
Rent proxy = (Rent) handler.getProxy();
proxy.rent();
}
}
3、处理程序包装成工具类
public class ProxyUtils implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy() {
//动态代理的本质是反射
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
//处理代理实例并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);
return result;
}
}
10、AOP
AOP:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
- AOP底层原理是动态代理。
- 在一个项目创建流程中的其中一部分插入其他内容。
- 在不影响源代码或者业务的情况下实现动态增强功能。主要用来进行事务控制。
- 适用于权限,缓存,日志,事务等场景。
10.1、使用 Spring 的 API 接口实现
1、导入aop依赖包
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.0</version>
</dependency>
</dependencies>
2、通知
public class BeforeLog implements MethodBeforeAdvice {
//method:要执行的目标对象的方法
//args:参数
//target:目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行!");
}
}
public class AfterLog implements AfterReturningAdvice {
//returnValue:返回值
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+",返回结果为"+returnValue);
}
}
3、编写接口和实现类
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了");
}
public void delete() {
System.out.println("删除了");
}
public void update() {
System.out.println("更新了");
}
public void select() {
System.out.println("查询了");
}
}
4、导入aop的约束,把bean注册到spring中,配置aop。
<?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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--把bean注册到spring中-->
<bean id="userService" class="com.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.log.BeforeLog"/>
<bean id="afterLog" class="com.log.AfterLog"/>
<!--配置aop-->
<aop:config>
<!--切入点:execution表达式:execution(修饰词,返回值,类名,方法名,参数)-->
<aop:pointcut id="pointcut" expression="execution(* com.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
5、测试aop
shpublic static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理的是接口,所以是UserService,而不是实现类
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
10.2、自定义切面实现
1、通知
public class Log {
public void before(){
System.out.println("before");
}
public void after(){
System.out.println("after");
}
}
2、applicationContext.xml
<bean id="log" class="com.log.Log"/>
<aop:config>
<!--自定义切面-->
<aop:aspect ref="log">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
10.3、注解实现
1、通知
@Aspect //标注类为切面
public class AnnotationPointCut {
@Before("execution(* com.service.UserServiceImpl.*(..))")
public void before() {
System.out.println("before");
}
@After("execution(* com.service.UserServiceImpl.*(..))")
public void after() {
System.out.println("after");
}
@Around("execution(* com.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint point) throws Throwable{
System.out.println("beforeAround");
//执行方法
Object proceed = point.proceed();
System.out.println("afterAround");
}
}
输出:
beforeAround
before
方法
after
afterAround
2、applicationContext.xml
<!--bean的注册也可以使用注解实现-->
<bean id="annotationPointCut" class="com.log.AnnotationPointCut"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>
11、整合Mybatis
11.1、回顾mybatis
1、导入jar包
<dependencies>
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
<!--aop织入-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.0</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--整合依赖,要求mybatis3.5+,spring5.0+,java8+-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<!--spring操作数据库驱动-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
</dependencies>
<!--解决静态资源过滤问题-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
2、编写配置文件(resources)
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&useSSL=true
username=root
password=123456
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<package name="com.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.mapper.UserMapper"/>
</mappers>
</configuration>
3、实体类(com.pojo)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
4、mapper(com.mapper)
public interface UserMapper {
List<User> select();
}
5、mapper.xml(com.mapper)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.UserMapper">
<select id="select" resultType="user">
select * from user;
</select>
</mapper>
6、测试
public class MyTest {
@Test
public void test1() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.select();
for (User user : userList) {
System.out.println(user);
}
}
}
11.2、SqlSessionTemplate整合
1、配置 spring-dao.xml,配置 dataSource,sqlSessionFactory,sqlSession
<?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">
<!--dataSource-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&useSSL=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--配置这个可以省略mybatis-config.xml的environment-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--配置这个可以省略mybatis-config.xml的mapper-->
<property name="mapperLocations" value="classpath:com/mapper/*.xml"/>
</bean>
<!--SqlSessionTemplate:sqlSession模板,其实就是sqlSession,但是线程安全-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--因为没有set方法,所以使用构造方法注入参数-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<!--以上这些配置除了一些参数,基本都是默认配置-->
</beans>
2、因为mybatis无法自动配置,所以需要有 set 方法,编写mapper的实现类
public class UserMapperImpl implements UserMapper{
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> select() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.select();
}
}
3、配置 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--引用spring-dao.xml-->
<import resource="spring-dao.xml"/>
<!--把mapper的实现类注入到spring容器,这里并不是配置mybatis-config.xml的mapper-->
<bean id="userMapper" class="com.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
4、测试
@Test
public void test1() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = (UserMapper) context.getBean("userMapper");
List<User> users = userMapper.select();
for (User u:users) {
System.out.println(u);
}
}
11.3、SqlSessionDaoSupport整合
1、配置 spring-dao.xml,配置 dataSource,sqlSessionFactory
<?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">
<!--dataSource-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&useSSL=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--配置这个可以省略mybatis-config.xml的environment-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/mapper/*.xml"/>
</bean>
<!--SqlSessionDaoSupport省略了这部分-->
<!--<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>-->
</beans>
2、编写mapper的实现类,继承了 SqlSessionDaoSupport
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> select() {
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.select();
}
}
3、配置 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">
<!--引用spring-dao.xml-->
<import resource="spring-dao.xml"/>
<!--把mapper的实现类注入到spring容器,这里并不是配置mybatis-config.xml的mapper-->
<bean id="userMapper2" class="com.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>
4、测试
@Test
public void test1() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//注意这里要更改获取的bean为userMapper2
UserMapper userMapper = (UserMapper) context.getBean("userMapper2");
List<User> users = userMapper.select();
for (User u:users) {
System.out.println(u);
}
}
12、声明式事务
使用AOP,交由容器管理事务。
12.1、回顾事务
把一组业务当成一个业务来做。
ACID原则
- 原子性:要么都成功,要么都失败。
- 一致性:事务前后数据完整性一致。
- 隔离性:多个用户访问同个数据库,数据库为每个用户开启单独的事务,这些事务互不影响。
- 持久性:事务一旦提交不可逆转,持久化到数据库。
12.2、使用事务
12.2.1、环境配置
1、增加和删除(UserMapper)
public interface UserMapper {
List<User> select();
int addUser(User user);
int deleteUser(@Param("id")int id);
}
2、编写 UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.UserMapper">
<select id="select" resultType="user">
select * from user;
</select>
<insert id="addUser" parameterType="User">
insert into mybatis.user(name, pwd) value (#{name},#{pwd})
</insert>
<!--这里人为使sql出错,用来测试事务-->
<delete id="deleteUser" parameterType="int">
deletes from mybatis.user where id=#{id}
</delete>
</mapper>
3、编写实现类
public class UserMapperImpl implements UserMapper{
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> select() {
/*因为使用lombok的有参构造注解,所以这里配置id,
但是表的设置id是自增的,所以这里的id写什么都是无效的*/
User user = new User(0, "user99", "123456");
addUser(user); //先增加一个user
deleteUser(1); //再删除id为1的user
/*如果事务成功,这两个业务都会实现,
但是因为人为使sql出错,所以这里应该是事务回滚,两个业务都不生效*/
return sqlSession.getMapper(UserMapper.class).select();
}
@Override
public int addUser(User user) {
return sqlSession.getMapper(UserMapper.class).addUser(user);
}
@Override
public int deleteUser(int id) {
return sqlSession.getMapper(UserMapper.class).deleteUser(id);
}
}
4、测试
public class MyTset {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = (UserMapper) context.getBean("userMapper");
List<User> users = userMapper.select();
for (User u:users) {
System.out.println(u);
}
}
}
//这个测试会因为删除的sql错误而报错,但是插入的sql执行成功,我们的目的是测试事务是否回滚
12.2.2、使用
1、开启 Spring 的事务处理功能
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
2、结合AOP实现事务织入
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的方法-->
<tx:attributes>
<!--REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务-->
<!--所有方法都使用REQUIRED处理事务-->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入点-->
<aop:config>
<!--mapper下面的所有类的所有方法都使用该事务-->
<aop:pointcut id="txPointCut" expression="execution(* com.mapper.*.*(..))"/>
<!--使用事务通知-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
测试后报错,发现插入和删除的 sql 都没有被执行,更改正确的sql后成功进行了插入和删除。